mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Formatted src/services/curated_data
This commit is contained in:
@@ -21,71 +21,71 @@
|
|||||||
|
|
||||||
static std::string ReadRequiredString(const boost::json::object& object,
|
static std::string ReadRequiredString(const boost::json::object& object,
|
||||||
const char* key) {
|
const char* key) {
|
||||||
const boost::json::value* value = object.if_contains(key);
|
const boost::json::value* value = object.if_contains(key);
|
||||||
if (value == nullptr || !value->is_string()) {
|
if (value == nullptr || !value->is_string()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
std::format("Missing or invalid string field: {}", key));
|
std::format("Missing or invalid string field: {}", key));
|
||||||
}
|
}
|
||||||
const std::string_view text = value->as_string();
|
const std::string_view text = value->as_string();
|
||||||
return std::string(text);
|
return std::string(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<std::string> ReadRequiredStringArray(
|
static std::vector<std::string> ReadRequiredStringArray(
|
||||||
const boost::json::object& object, const char* key) {
|
const boost::json::object& object, const char* key) {
|
||||||
const boost::json::value* value = object.if_contains(key);
|
const boost::json::value* value = object.if_contains(key);
|
||||||
if (value == nullptr || !value->is_array()) {
|
if (value == nullptr || !value->is_array()) {
|
||||||
throw std::runtime_error(
|
|
||||||
std::format("Missing or invalid string array field: {}", key));
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& array = value->as_array();
|
|
||||||
std::vector<std::string> items;
|
|
||||||
items.reserve(array.size());
|
|
||||||
for (const auto& item : array) {
|
|
||||||
if (!item.is_string()) {
|
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
std::format("Missing or invalid string array field: {}", key));
|
std::format("Missing or invalid string array field: {}", key));
|
||||||
}
|
}
|
||||||
items.emplace_back(item.as_string());
|
|
||||||
}
|
const auto& array = value->as_array();
|
||||||
return items;
|
std::vector<std::string> items;
|
||||||
|
items.reserve(array.size());
|
||||||
|
for (const auto& item : array) {
|
||||||
|
if (!item.is_string()) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::format("Missing or invalid string array field: {}", key));
|
||||||
|
}
|
||||||
|
items.emplace_back(item.as_string());
|
||||||
|
}
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PostalCodeSpec ReadPostalCodeSpec(const boost::json::object& object) {
|
static PostalCodeSpec ReadPostalCodeSpec(const boost::json::object& object) {
|
||||||
const boost::json::value* value = object.if_contains("postal_code");
|
const boost::json::value* value = object.if_contains("postal_code");
|
||||||
if (value == nullptr || !value->is_object()) {
|
if (value == nullptr || !value->is_object()) {
|
||||||
throw std::runtime_error("Missing or invalid object field: postal_code");
|
throw std::runtime_error("Missing or invalid object field: postal_code");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& postal_code = value->as_object();
|
const auto& postal_code = value->as_object();
|
||||||
return PostalCodeSpec{
|
return PostalCodeSpec{
|
||||||
.country_format_regex =
|
.country_format_regex =
|
||||||
ReadRequiredString(postal_code, "country_format_regex"),
|
ReadRequiredString(postal_code, "country_format_regex"),
|
||||||
.city_regexes = ReadRequiredStringArray(postal_code, "city_regex"),
|
.city_regexes = ReadRequiredStringArray(postal_code, "city_regex"),
|
||||||
.examples = ReadRequiredStringArray(postal_code, "examples"),
|
.examples = ReadRequiredStringArray(postal_code, "examples"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
boost::json::value ParseJsonFile(const std::filesystem::path& filepath,
|
boost::json::value ParseJsonFile(const std::filesystem::path& filepath,
|
||||||
const char* what) {
|
const char* what) {
|
||||||
std::ifstream input(filepath);
|
std::ifstream input(filepath);
|
||||||
if (!input.is_open()) {
|
if (!input.is_open()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
std::format("Failed to open {} file: {}", what, filepath.string()));
|
std::format("Failed to open {} file: {}", what, filepath.string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::stringstream buffer;
|
std::stringstream buffer;
|
||||||
buffer << input.rdbuf();
|
buffer << input.rdbuf();
|
||||||
|
|
||||||
boost::system::error_code error;
|
boost::system::error_code error;
|
||||||
boost::json::value root = boost::json::parse(buffer.str(), error);
|
boost::json::value root = boost::json::parse(buffer.str(), error);
|
||||||
if (error) {
|
if (error) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
std::format("Failed to parse {} JSON: {}", what, error.message()));
|
std::format("Failed to parse {} JSON: {}", what, error.message()));
|
||||||
}
|
}
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,18 +95,18 @@ boost::json::value ParseJsonFile(const std::filesystem::path& filepath,
|
|||||||
*/
|
*/
|
||||||
std::string ReadFirstOfStringArray(const boost::json::object& object,
|
std::string ReadFirstOfStringArray(const boost::json::object& object,
|
||||||
const char* key, const char* fallback_key) {
|
const char* key, const char* fallback_key) {
|
||||||
for (const char* candidate_key : {key, fallback_key}) {
|
for (const char* candidate_key : {key, fallback_key}) {
|
||||||
const boost::json::value* value = object.if_contains(candidate_key);
|
const boost::json::value* value = object.if_contains(candidate_key);
|
||||||
if (value == nullptr || !value->is_array()) {
|
if (value == nullptr || !value->is_array()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto& array = value->as_array();
|
const auto& array = value->as_array();
|
||||||
if (!array.empty() && array.front().is_string()) {
|
if (!array.empty() && array.front().is_string()) {
|
||||||
return std::string(array.front().as_string());
|
return std::string(array.front().as_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
std::format("Missing or invalid string array field: {}", key));
|
std::format("Missing or invalid string array field: {}", key));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -115,154 +115,156 @@ CuratedJsonDataService::CuratedJsonDataService(CuratedDataFilePaths filepaths)
|
|||||||
: filepaths_(std::move(filepaths)) {}
|
: filepaths_(std::move(filepaths)) {}
|
||||||
|
|
||||||
const LocationsList& CuratedJsonDataService::LoadLocations() {
|
const LocationsList& CuratedJsonDataService::LoadLocations() {
|
||||||
if (!cache_.locations.empty()) {
|
if (!cache_.locations.empty()) {
|
||||||
return cache_.locations;
|
return cache_.locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boost::json::value root =
|
const boost::json::value root =
|
||||||
ParseJsonFile(filepaths_.locations_path, "locations");
|
ParseJsonFile(filepaths_.locations_path, "locations");
|
||||||
|
|
||||||
if (!root.is_array()) {
|
if (!root.is_array()) {
|
||||||
throw std::runtime_error(
|
|
||||||
"Invalid locations JSON: root element must be an array");
|
|
||||||
}
|
|
||||||
|
|
||||||
LocationsList locations;
|
|
||||||
const auto& items = root.as_array();
|
|
||||||
locations.reserve(items.size());
|
|
||||||
|
|
||||||
for (const auto& item : items) {
|
|
||||||
if (!item.is_object()) {
|
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Invalid locations JSON: each entry must be an object");
|
"Invalid locations JSON: root element must be an array");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& object = item.as_object();
|
LocationsList locations;
|
||||||
locations.push_back(City{
|
const auto& items = root.as_array();
|
||||||
.city = ReadRequiredString(object, "city"),
|
locations.reserve(items.size());
|
||||||
.state_province = ReadRequiredString(object, "state_province"),
|
|
||||||
.iso3166_2 = ReadRequiredString(object, "iso3166_2"),
|
for (const auto& item : items) {
|
||||||
.country = ReadRequiredString(object, "country"),
|
if (!item.is_object()) {
|
||||||
.iso3166_1 = ReadRequiredString(object, "iso3166_1"),
|
throw std::runtime_error(
|
||||||
.local_languages = ReadRequiredStringArray(object, "local_languages"),
|
"Invalid locations JSON: each entry must be an object");
|
||||||
.postal_code = ReadPostalCodeSpec(object),
|
}
|
||||||
});
|
|
||||||
}
|
const auto& object = item.as_object();
|
||||||
cache_.locations = std::move(locations);
|
locations.push_back(City{
|
||||||
return cache_.locations;
|
.city = ReadRequiredString(object, "city"),
|
||||||
|
.state_province = ReadRequiredString(object, "state_province"),
|
||||||
|
.iso3166_2 = ReadRequiredString(object, "iso3166_2"),
|
||||||
|
.country = ReadRequiredString(object, "country"),
|
||||||
|
.iso3166_1 = ReadRequiredString(object, "iso3166_1"),
|
||||||
|
.local_languages = ReadRequiredStringArray(object, "local_languages"),
|
||||||
|
.postal_code = ReadPostalCodeSpec(object),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cache_.locations = std::move(locations);
|
||||||
|
return cache_.locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PersonasList& CuratedJsonDataService::LoadPersonas() {
|
const PersonasList& CuratedJsonDataService::LoadPersonas() {
|
||||||
if (!cache_.personas.empty()) {
|
if (!cache_.personas.empty()) {
|
||||||
return cache_.personas;
|
return cache_.personas;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boost::json::value root =
|
const boost::json::value root =
|
||||||
ParseJsonFile(filepaths_.personas_path, "personas");
|
ParseJsonFile(filepaths_.personas_path, "personas");
|
||||||
|
|
||||||
if (!root.is_array()) {
|
if (!root.is_array()) {
|
||||||
throw std::runtime_error(
|
|
||||||
"Invalid personas JSON: root element must be an array");
|
|
||||||
}
|
|
||||||
|
|
||||||
PersonasList personas;
|
|
||||||
const auto& items = root.as_array();
|
|
||||||
personas.reserve(items.size());
|
|
||||||
|
|
||||||
for (const auto& item : items) {
|
|
||||||
if (!item.is_object()) {
|
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Invalid personas JSON: each entry must be an object");
|
"Invalid personas JSON: root element must be an array");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& object = item.as_object();
|
PersonasList personas;
|
||||||
personas.push_back(UserPersona{
|
const auto& items = root.as_array();
|
||||||
.name = ReadRequiredString(object, "name"),
|
personas.reserve(items.size());
|
||||||
.description = ReadRequiredString(object, "description"),
|
|
||||||
.style_affinities = ReadRequiredStringArray(object, "style_affinities"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_.personas = std::move(personas);
|
for (const auto& item : items) {
|
||||||
return cache_.personas;
|
if (!item.is_object()) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Invalid personas JSON: each entry must be an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& object = item.as_object();
|
||||||
|
personas.push_back(UserPersona{
|
||||||
|
.name = ReadRequiredString(object, "name"),
|
||||||
|
.description = ReadRequiredString(object, "description"),
|
||||||
|
.style_affinities =
|
||||||
|
ReadRequiredStringArray(object, "style_affinities"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_.personas = std::move(personas);
|
||||||
|
return cache_.personas;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ForenamesByCountryMap& CuratedJsonDataService::LoadForenamesByCountry() {
|
const ForenamesByCountryMap& CuratedJsonDataService::LoadForenamesByCountry() {
|
||||||
if (!cache_.forenames_by_country.empty()) {
|
if (!cache_.forenames_by_country.empty()) {
|
||||||
return cache_.forenames_by_country;
|
return cache_.forenames_by_country;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boost::json::value root =
|
const boost::json::value root =
|
||||||
ParseJsonFile(filepaths_.forenames_path, "forenames-by-country");
|
ParseJsonFile(filepaths_.forenames_path, "forenames-by-country");
|
||||||
|
|
||||||
if (!root.is_object()) {
|
if (!root.is_object()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Invalid forenames-by-country JSON: root element must be an object "
|
"Invalid forenames-by-country JSON: root element must be an object "
|
||||||
"keyed by ISO 3166-1 country code");
|
"keyed by ISO 3166-1 country code");
|
||||||
}
|
}
|
||||||
|
|
||||||
ForenamesByCountryMap forenames_by_country;
|
ForenamesByCountryMap forenames_by_country;
|
||||||
for (const auto& [country_code, regions] : root.as_object()) {
|
for (const auto& [country_code, regions] : root.as_object()) {
|
||||||
if (!regions.is_array()) {
|
if (!regions.is_array()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
ForenameList entries;
|
|
||||||
for (const auto& region : regions.as_array()) {
|
|
||||||
if (!region.is_object()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
const boost::json::value* names = region.as_object().if_contains("names");
|
ForenameList entries;
|
||||||
if (names == nullptr || !names->is_array()) {
|
for (const auto& region : regions.as_array()) {
|
||||||
continue;
|
if (!region.is_object()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const boost::json::value* names =
|
||||||
|
region.as_object().if_contains("names");
|
||||||
|
if (names == nullptr || !names->is_array()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const auto& name_value : names->as_array()) {
|
||||||
|
if (!name_value.is_object()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& name_object = name_value.as_object();
|
||||||
|
entries.emplace_back(ForenameEntry{
|
||||||
|
.name = ReadFirstOfStringArray(name_object, "romanized",
|
||||||
|
"localized"),
|
||||||
|
.gender = ReadRequiredString(name_object, "gender"),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const auto& name_value : names->as_array()) {
|
forenames_by_country.emplace(country_code, std::move(entries));
|
||||||
if (!name_value.is_object()) {
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto& name_object = name_value.as_object();
|
|
||||||
entries.emplace_back(ForenameEntry{
|
|
||||||
.name =
|
|
||||||
ReadFirstOfStringArray(name_object, "romanized", "localized"),
|
|
||||||
.gender = ReadRequiredString(name_object, "gender"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
forenames_by_country.emplace(country_code, std::move(entries));
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_.forenames_by_country = std::move(forenames_by_country);
|
cache_.forenames_by_country = std::move(forenames_by_country);
|
||||||
return cache_.forenames_by_country;
|
return cache_.forenames_by_country;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SurnamesByCountryMap& CuratedJsonDataService::LoadSurnamesByCountry() {
|
const SurnamesByCountryMap& CuratedJsonDataService::LoadSurnamesByCountry() {
|
||||||
if (!cache_.surnames_by_country.empty()) {
|
if (!cache_.surnames_by_country.empty()) {
|
||||||
return cache_.surnames_by_country;
|
return cache_.surnames_by_country;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boost::json::value root =
|
const boost::json::value root =
|
||||||
ParseJsonFile(filepaths_.surnames_path, "surnames-by-country");
|
ParseJsonFile(filepaths_.surnames_path, "surnames-by-country");
|
||||||
|
|
||||||
if (!root.is_object()) {
|
if (!root.is_object()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"Invalid surnames-by-country JSON: root element must be an object "
|
"Invalid surnames-by-country JSON: root element must be an object "
|
||||||
"keyed by ISO 3166-1 country code");
|
"keyed by ISO 3166-1 country code");
|
||||||
}
|
}
|
||||||
|
|
||||||
SurnamesByCountryMap surnames_by_country;
|
SurnamesByCountryMap surnames_by_country;
|
||||||
for (const auto& [country_code, name_entries] : root.as_object()) {
|
for (const auto& [country_code, name_entries] : root.as_object()) {
|
||||||
if (!name_entries.is_array()) continue;
|
if (!name_entries.is_array()) continue;
|
||||||
|
|
||||||
SurnameList surnames;
|
SurnameList surnames;
|
||||||
for (const auto& name_value : name_entries.as_array()) {
|
for (const auto& name_value : name_entries.as_array()) {
|
||||||
if (!name_value.is_object()) {
|
if (!name_value.is_object()) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
surnames.emplace_back(ReadFirstOfStringArray(
|
||||||
|
name_value.as_object(), "romanized", "localized"));
|
||||||
}
|
}
|
||||||
surnames.emplace_back(ReadFirstOfStringArray(name_value.as_object(),
|
surnames_by_country.emplace(country_code, std::move(surnames));
|
||||||
"romanized", "localized"));
|
}
|
||||||
}
|
|
||||||
surnames_by_country.emplace(country_code, std::move(surnames));
|
|
||||||
}
|
|
||||||
|
|
||||||
cache_.surnames_by_country = std::move(surnames_by_country);
|
cache_.surnames_by_country = std::move(surnames_by_country);
|
||||||
return cache_.surnames_by_country;
|
return cache_.surnames_by_country;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,15 +92,15 @@ MockCuratedDataService::MockCuratedDataService()
|
|||||||
} {}
|
} {}
|
||||||
|
|
||||||
const LocationsList& MockCuratedDataService::LoadLocations() {
|
const LocationsList& MockCuratedDataService::LoadLocations() {
|
||||||
return locations_;
|
return locations_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PersonasList& MockCuratedDataService::LoadPersonas() { return personas_; }
|
const PersonasList& MockCuratedDataService::LoadPersonas() { return personas_; }
|
||||||
|
|
||||||
const ForenamesByCountryMap& MockCuratedDataService::LoadForenamesByCountry() {
|
const ForenamesByCountryMap& MockCuratedDataService::LoadForenamesByCountry() {
|
||||||
return forenames_by_country_;
|
return forenames_by_country_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SurnamesByCountryMap& MockCuratedDataService::LoadSurnamesByCountry() {
|
const SurnamesByCountryMap& MockCuratedDataService::LoadSurnamesByCountry() {
|
||||||
return surnames_by_country_;
|
return surnames_by_country_;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user