Formatted src/services/curated_data

This commit is contained in:
Aaron Po
2026-07-19 04:10:33 -04:00
parent d055a634c2
commit be90522eae
2 changed files with 184 additions and 182 deletions

View File

@@ -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;
} }

View File

@@ -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_;
} }