diff --git a/tooling/pipeline/src/services/curated_data/curated_json_data_service.cc b/tooling/pipeline/src/services/curated_data/curated_json_data_service.cc index cf8b11f3..c85a7820 100644 --- a/tooling/pipeline/src/services/curated_data/curated_json_data_service.cc +++ b/tooling/pipeline/src/services/curated_data/curated_json_data_service.cc @@ -21,71 +21,71 @@ static std::string ReadRequiredString(const boost::json::object& object, const char* key) { - const boost::json::value* value = object.if_contains(key); - if (value == nullptr || !value->is_string()) { - throw std::runtime_error( - std::format("Missing or invalid string field: {}", key)); - } - const std::string_view text = value->as_string(); - return std::string(text); + const boost::json::value* value = object.if_contains(key); + if (value == nullptr || !value->is_string()) { + throw std::runtime_error( + std::format("Missing or invalid string field: {}", key)); + } + const std::string_view text = value->as_string(); + return std::string(text); } static std::vector ReadRequiredStringArray( const boost::json::object& object, const char* key) { - const boost::json::value* value = object.if_contains(key); - 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 items; - items.reserve(array.size()); - for (const auto& item : array) { - if (!item.is_string()) { + const boost::json::value* value = object.if_contains(key); + if (value == nullptr || !value->is_array()) { throw std::runtime_error( std::format("Missing or invalid string array field: {}", key)); - } - items.emplace_back(item.as_string()); - } - return items; + } + + const auto& array = value->as_array(); + std::vector 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) { - const boost::json::value* value = object.if_contains("postal_code"); - if (value == nullptr || !value->is_object()) { - throw std::runtime_error("Missing or invalid object field: postal_code"); - } + const boost::json::value* value = object.if_contains("postal_code"); + if (value == nullptr || !value->is_object()) { + throw std::runtime_error("Missing or invalid object field: postal_code"); + } - const auto& postal_code = value->as_object(); - return PostalCodeSpec{ - .country_format_regex = - ReadRequiredString(postal_code, "country_format_regex"), - .city_regexes = ReadRequiredStringArray(postal_code, "city_regex"), - .examples = ReadRequiredStringArray(postal_code, "examples"), - }; + const auto& postal_code = value->as_object(); + return PostalCodeSpec{ + .country_format_regex = + ReadRequiredString(postal_code, "country_format_regex"), + .city_regexes = ReadRequiredStringArray(postal_code, "city_regex"), + .examples = ReadRequiredStringArray(postal_code, "examples"), + }; } namespace { boost::json::value ParseJsonFile(const std::filesystem::path& filepath, const char* what) { - std::ifstream input(filepath); - if (!input.is_open()) { - throw std::runtime_error( - std::format("Failed to open {} file: {}", what, filepath.string())); - } + std::ifstream input(filepath); + if (!input.is_open()) { + throw std::runtime_error( + std::format("Failed to open {} file: {}", what, filepath.string())); + } - std::stringstream buffer; - buffer << input.rdbuf(); + std::stringstream buffer; + buffer << input.rdbuf(); - boost::system::error_code error; - boost::json::value root = boost::json::parse(buffer.str(), error); - if (error) { - throw std::runtime_error( - std::format("Failed to parse {} JSON: {}", what, error.message())); - } - return root; + boost::system::error_code error; + boost::json::value root = boost::json::parse(buffer.str(), error); + if (error) { + throw std::runtime_error( + std::format("Failed to parse {} JSON: {}", what, error.message())); + } + return root; } /** @@ -95,18 +95,18 @@ boost::json::value ParseJsonFile(const std::filesystem::path& filepath, */ std::string ReadFirstOfStringArray(const boost::json::object& object, const char* key, const char* fallback_key) { - for (const char* candidate_key : {key, fallback_key}) { - const boost::json::value* value = object.if_contains(candidate_key); - if (value == nullptr || !value->is_array()) { - continue; - } - const auto& array = value->as_array(); - if (!array.empty() && array.front().is_string()) { - return std::string(array.front().as_string()); - } - } - throw std::runtime_error( - std::format("Missing or invalid string array field: {}", key)); + for (const char* candidate_key : {key, fallback_key}) { + const boost::json::value* value = object.if_contains(candidate_key); + if (value == nullptr || !value->is_array()) { + continue; + } + const auto& array = value->as_array(); + if (!array.empty() && array.front().is_string()) { + return std::string(array.front().as_string()); + } + } + throw std::runtime_error( + std::format("Missing or invalid string array field: {}", key)); } } // namespace @@ -115,154 +115,156 @@ CuratedJsonDataService::CuratedJsonDataService(CuratedDataFilePaths filepaths) : filepaths_(std::move(filepaths)) {} const LocationsList& CuratedJsonDataService::LoadLocations() { - if (!cache_.locations.empty()) { - return cache_.locations; - } + if (!cache_.locations.empty()) { + return cache_.locations; + } - const boost::json::value root = - ParseJsonFile(filepaths_.locations_path, "locations"); + const boost::json::value root = + ParseJsonFile(filepaths_.locations_path, "locations"); - 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()) { + if (!root.is_array()) { 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(); - locations.push_back(City{ - .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; + 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( + "Invalid locations JSON: each entry must be an object"); + } + + const auto& object = item.as_object(); + locations.push_back(City{ + .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() { - if (!cache_.personas.empty()) { - return cache_.personas; - } + if (!cache_.personas.empty()) { + return cache_.personas; + } - const boost::json::value root = - ParseJsonFile(filepaths_.personas_path, "personas"); + const boost::json::value root = + ParseJsonFile(filepaths_.personas_path, "personas"); - 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()) { + if (!root.is_array()) { 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(); - personas.push_back(UserPersona{ - .name = ReadRequiredString(object, "name"), - .description = ReadRequiredString(object, "description"), - .style_affinities = ReadRequiredStringArray(object, "style_affinities"), - }); - } + PersonasList personas; + const auto& items = root.as_array(); + personas.reserve(items.size()); - cache_.personas = std::move(personas); - return cache_.personas; + for (const auto& item : items) { + 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() { - if (!cache_.forenames_by_country.empty()) { - return cache_.forenames_by_country; - } + if (!cache_.forenames_by_country.empty()) { + return cache_.forenames_by_country; + } - const boost::json::value root = - ParseJsonFile(filepaths_.forenames_path, "forenames-by-country"); + const boost::json::value root = + ParseJsonFile(filepaths_.forenames_path, "forenames-by-country"); - if (!root.is_object()) { - throw std::runtime_error( - "Invalid forenames-by-country JSON: root element must be an object " - "keyed by ISO 3166-1 country code"); - } + if (!root.is_object()) { + throw std::runtime_error( + "Invalid forenames-by-country JSON: root element must be an object " + "keyed by ISO 3166-1 country code"); + } - ForenamesByCountryMap forenames_by_country; - for (const auto& [country_code, regions] : root.as_object()) { - if (!regions.is_array()) { - continue; - } - ForenameList entries; - for (const auto& region : regions.as_array()) { - if (!region.is_object()) { - continue; + ForenamesByCountryMap forenames_by_country; + for (const auto& [country_code, regions] : root.as_object()) { + if (!regions.is_array()) { + continue; } - const boost::json::value* names = region.as_object().if_contains("names"); - if (names == nullptr || !names->is_array()) { - 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"); + 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()) { - 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)); - } + forenames_by_country.emplace(country_code, std::move(entries)); + } - cache_.forenames_by_country = std::move(forenames_by_country); - return cache_.forenames_by_country; + cache_.forenames_by_country = std::move(forenames_by_country); + return cache_.forenames_by_country; } const SurnamesByCountryMap& CuratedJsonDataService::LoadSurnamesByCountry() { - if (!cache_.surnames_by_country.empty()) { - return cache_.surnames_by_country; - } + if (!cache_.surnames_by_country.empty()) { + return cache_.surnames_by_country; + } - const boost::json::value root = - ParseJsonFile(filepaths_.surnames_path, "surnames-by-country"); + const boost::json::value root = + ParseJsonFile(filepaths_.surnames_path, "surnames-by-country"); - if (!root.is_object()) { - throw std::runtime_error( - "Invalid surnames-by-country JSON: root element must be an object " - "keyed by ISO 3166-1 country code"); - } + if (!root.is_object()) { + throw std::runtime_error( + "Invalid surnames-by-country JSON: root element must be an object " + "keyed by ISO 3166-1 country code"); + } - SurnamesByCountryMap surnames_by_country; - for (const auto& [country_code, name_entries] : root.as_object()) { - if (!name_entries.is_array()) continue; + SurnamesByCountryMap surnames_by_country; + for (const auto& [country_code, name_entries] : root.as_object()) { + if (!name_entries.is_array()) continue; - SurnameList surnames; - for (const auto& name_value : name_entries.as_array()) { - if (!name_value.is_object()) { - continue; + SurnameList surnames; + for (const auto& name_value : name_entries.as_array()) { + if (!name_value.is_object()) { + continue; + } + surnames.emplace_back(ReadFirstOfStringArray( + name_value.as_object(), "romanized", "localized")); } - surnames.emplace_back(ReadFirstOfStringArray(name_value.as_object(), - "romanized", "localized")); - } - surnames_by_country.emplace(country_code, std::move(surnames)); - } + surnames_by_country.emplace(country_code, std::move(surnames)); + } - cache_.surnames_by_country = std::move(surnames_by_country); - return cache_.surnames_by_country; + cache_.surnames_by_country = std::move(surnames_by_country); + return cache_.surnames_by_country; } diff --git a/tooling/pipeline/src/services/curated_data/mock_curated_data_service.cc b/tooling/pipeline/src/services/curated_data/mock_curated_data_service.cc index 3332fed7..3833b52b 100644 --- a/tooling/pipeline/src/services/curated_data/mock_curated_data_service.cc +++ b/tooling/pipeline/src/services/curated_data/mock_curated_data_service.cc @@ -92,15 +92,15 @@ MockCuratedDataService::MockCuratedDataService() } {} const LocationsList& MockCuratedDataService::LoadLocations() { - return locations_; + return locations_; } const PersonasList& MockCuratedDataService::LoadPersonas() { return personas_; } const ForenamesByCountryMap& MockCuratedDataService::LoadForenamesByCountry() { - return forenames_by_country_; + return forenames_by_country_; } const SurnamesByCountryMap& MockCuratedDataService::LoadSurnamesByCountry() { - return surnames_by_country_; + return surnames_by_country_; }