mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 18:14:44 +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,
|
||||
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<std::string> 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<std::string> 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<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) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user