diff --git a/include/faker-cxx/Helper.h b/include/faker-cxx/Helper.h index f986b3bcb..9bd46a616 100644 --- a/include/faker-cxx/Helper.h +++ b/include/faker-cxx/Helper.h @@ -263,7 +263,7 @@ class Helper * Helper::replaceSymbolWithNumber("Your pin is: !####") // "29841" * @endcode */ - static std::string replaceSymbolWithNumber(std::string str, const char& symbol = '#'); + static std::string replaceSymbolWithNumber(const std::string& str, const char& symbol = '#'); /** * @brief Returns credit card schema with replaced symbols and patterns in a credit card including Luhn checksum diff --git a/include/faker-cxx/Person.h b/include/faker-cxx/Person.h index 489888d9a..153f4013d 100644 --- a/include/faker-cxx/Person.h +++ b/include/faker-cxx/Person.h @@ -5,6 +5,7 @@ #include "faker-cxx/types/Country.h" #include "faker-cxx/types/Sex.h" +#include "faker-cxx/types/SsnCountry.h" namespace faker { @@ -206,6 +207,20 @@ class Person */ static std::string nationality(); + /** + * @brief Returns a random SSN. + * + * @param country The optional country to use. + * + * @returns Social Security Number. + * + * @code + * Person::ssn() // "437-12-6854" + * Person::ssn(SsnCountry::Polish) // "95111901567" + * @endcode + */ + static std::string ssn(std::optional country); + /** * @brief Returns a random Western Zodiac * diff --git a/include/faker-cxx/types/SsnCountry.h b/include/faker-cxx/types/SsnCountry.h new file mode 100644 index 000000000..4d34529a0 --- /dev/null +++ b/include/faker-cxx/types/SsnCountry.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace faker +{ +enum class SsnCountry +{ + Poland, + UnitedStates, + UnitedKingdom, + Germany, + France, + Italy, + Spain, + India, +}; + +const std::vector supportedSsnCountries{ + SsnCountry::Poland, SsnCountry::UnitedStates, SsnCountry::UnitedKingdom, SsnCountry::Germany, + SsnCountry::France, SsnCountry::Italy, SsnCountry::Spain, SsnCountry::India, +}; + +inline std::string toString(SsnCountry country) +{ + std::map countryToStringMapping{ + {SsnCountry::UnitedStates, "UnitedStates"}, + {SsnCountry::UnitedKingdom, "UnitedKingdom"}, + {SsnCountry::Poland, "Poland"}, + {SsnCountry::Italy, "Italy"}, + {SsnCountry::France, "France"}, + {SsnCountry::Germany, "Germany"}, + {SsnCountry::India, "India"}, + {SsnCountry::Spain, "Spain"}, + }; + + return countryToStringMapping.at(country); +} + +inline std::ostream& operator<<(std::ostream& os, SsnCountry country) +{ + return os << toString(country); +} + +} diff --git a/src/modules/helper/Helper.cpp b/src/modules/helper/Helper.cpp index e18a42c5d..8553a79ab 100644 --- a/src/modules/helper/Helper.cpp +++ b/src/modules/helper/Helper.cpp @@ -22,20 +22,27 @@ std::string Helper::shuffleString(std::string data) return data; } -std::string Helper::replaceSymbolWithNumber(std::string str, const char& symbol) +std::string Helper::replaceSymbolWithNumber(const std::string& str, const char& symbol) { - for (char& ch : str) + std::string result; + + for (const auto& ch : str) { if (ch == symbol) { - ch = static_cast(Number::integer(0, 9) + '0'); + result += std::to_string(Number::integer(0, 9)); } else if (ch == '!') { - ch = static_cast(Number::integer(2, 9) + '0'); + result += std::to_string(Number::integer(2, 9)); + } + else + { + result += ch; } } - return str; + + return result; } std::string Helper::replaceCreditCardSymbols(const std::string& inputString, char symbol) diff --git a/src/modules/person/Person.cpp b/src/modules/person/Person.cpp index 0602f2fab..59732e154 100644 --- a/src/modules/person/Person.cpp +++ b/src/modules/person/Person.cpp @@ -53,6 +53,7 @@ #include "data/slovakia/SlovakPeopleNames.h" #include "data/slovenia/SlovenianPeopleNames.h" #include "data/spain/SpanishPeopleNames.h" +#include "data/SsnFormats.h" #include "data/sweden/SwedishPeopleNames.h" #include "data/switzerland/SwissPeopleNames.h" #include "data/turkey/TurkishPeopleNames.h" @@ -61,13 +62,13 @@ #include "data/ZodiacSigns.h" #include "faker-cxx/Helper.h" #include "faker-cxx/Internet.h" +#include "faker-cxx/String.h" #include "faker-cxx/Word.h" + namespace faker { namespace { -const std::vector sexes{"Male", "Female"}; - const std::map countryToPeopleNamesMapping{ {Country::England, englishPeopleNames}, {Country::France, frenchPeopleNames}, {Country::Germany, germanPeopleNames}, {Country::Italy, italianPeopleNames}, @@ -311,9 +312,11 @@ std::string Person::suffix() std::string Person::sex(Language language) { - std::string chosenSex = Helper::arrayElement(sexes); + const std::vector sexes{"Male", "Female"}; + + const auto chosenSex = Helper::arrayElement(sexes); - Sex sexEnum = chosenSex == "Male" ? Sex::Male : Sex::Female; + const auto sexEnum = chosenSex == "Male" ? Sex::Male : Sex::Female; return translateSex(sexEnum, language); } @@ -358,6 +361,39 @@ std::string Person::nationality() return Helper::arrayElement(nationalities); } +std::string Person::ssn(std::optional country) +{ + const auto ssnCountry = country ? *country : Helper::arrayElement(supportedSsnCountries); + + const auto& ssnFormat = ssnFormats.at(ssnCountry); + + auto ssnWithoutRegexes = Helper::regexpStyleStringParse(ssnFormat); + + std::string ssn; + + for (const auto& ssnFormatCharacter : ssnWithoutRegexes) + { + if (ssnFormatCharacter == 'L') + { + ssn += String::alpha(1, StringCasing::Upper); + } + else if (ssnFormatCharacter == 'F') + { + ssn += String::alphanumeric(1, StringCasing::Upper); + } + else if (ssnFormatCharacter == '#') + { + ssn += std::to_string(Number::integer(0, 9)); + } + else + { + ssn += ssnFormatCharacter; + } + } + + return ssn; +} + std::string Person::westernZodiac() { return Helper::arrayElement(westernZodiacs); diff --git a/src/modules/person/PersonTest.cpp b/src/modules/person/PersonTest.cpp index 89fc00b76..d9786a916 100644 --- a/src/modules/person/PersonTest.cpp +++ b/src/modules/person/PersonTest.cpp @@ -486,11 +486,11 @@ class PersonSexSuite : public TestWithParam> TEST_P(PersonSexSuite, shouldTranslateSexCorrectly) { - Language language = GetParam().first; - Sex sex = GetParam().second; + const auto language = GetParam().first; + const auto sex = GetParam().second; - std::string expectedTranslation = sexTranslations.at(language).at(sex); - std::string actualTranslation = translateSex(sex, language); + const auto expectedTranslation = sexTranslations.at(language).at(sex); + const auto actualTranslation = translateSex(sex, language); ASSERT_EQ(expectedTranslation, actualTranslation); } @@ -526,3 +526,29 @@ INSTANTIATE_TEST_SUITE_P(TestPersonSexTranslation, PersonSexSuite, testing::Valu auto param = info.param; return toString(param.first) + "_" + toString(param.second); }); + +const std::map ssnLengths{ + {SsnCountry::Poland, 11}, {SsnCountry::UnitedStates, 11}, {SsnCountry::UnitedKingdom, 13}, + {SsnCountry::Germany, 12}, {SsnCountry::France, 19}, {SsnCountry::Italy, 19}, + {SsnCountry::Spain, 10}, {SsnCountry::India, 10}, +}; + +class PersonSsnSuite : public TestWithParam +{ +}; + +// TODO: add more precise tests +TEST_P(PersonSsnSuite, shouldGenerateSsn) +{ + const auto country = GetParam(); + + const auto ssn = Person::ssn(country); + + const auto expectedSsnLength = ssnLengths.at(country); + + ASSERT_EQ(ssn.size(), expectedSsnLength); +} + +INSTANTIATE_TEST_SUITE_P(TestPersonSsn, PersonSsnSuite, testing::ValuesIn(supportedSsnCountries), + [](const testing::TestParamInfo& info) + { return "shouldGenerate" + toString(info.param) + "Ssn"; }); diff --git a/src/modules/person/data/SsnFormats.h b/src/modules/person/data/SsnFormats.h new file mode 100644 index 000000000..d27ea0c8e --- /dev/null +++ b/src/modules/person/data/SsnFormats.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +#include "faker-cxx/types/SsnCountry.h" + +namespace faker +{ +const std::map ssnFormats{ + {SsnCountry::Poland, "##[0-1][0-2][0-2]######"}, + {SsnCountry::UnitedStates, "###-##-####"}, + // TODO: handle letters + {SsnCountry::UnitedKingdom, "LL ## ## ## L"}, + // TODO: handle conditional values like if year starts with 2 then second number must be 0-3 + {SsnCountry::Germany, "####[0-2]#[0-1][0-2][1-2][5-9]##"}, + {SsnCountry::France, "## [0-1][0-2] [0-2]# ### ### ##"}, + // TODO: add alfa-numeric support + {SsnCountry::Italy, "FFFF FFFF FFFF FFFF"}, + {SsnCountry::Spain, "X########L"}, + {SsnCountry::India, "LLLLL####L"}, +}; +}