Data model for birthdates.
Basic class to represent a birthdate, with rudimentary validation and
unit tests.
Change-Id: I56bcc0b8334f00fe36dcae05815ebd28f5fe2694
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3503904
Reviewed-by: Matthias Körber <koerber@google.com>
Reviewed-by: Christoph Schwering <schwering@google.com>
Commit-Queue: Florian Leimgruber <fleimgruber@google.com>
Cr-Commit-Position: refs/heads/main@{#981077}
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 59be3ca..be20f26 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -117,6 +117,8 @@
"data_model/autofill_structured_address_utils.h",
"data_model/autofillable_data.cc",
"data_model/autofillable_data.h",
+ "data_model/birthdate.cc",
+ "data_model/birthdate.h",
"data_model/borrowed_transliterator.cc",
"data_model/borrowed_transliterator.h",
"data_model/contact_info.cc",
@@ -742,6 +744,7 @@
"data_model/autofill_structured_address_test_utils.h",
"data_model/autofill_structured_address_unittest.cc",
"data_model/autofill_structured_address_utils_unittest.cc",
+ "data_model/birthdate_unittest.cc",
"data_model/borrowed_transliterator_unittest.cc",
"data_model/contact_info_unittest.cc",
"data_model/credit_card_unittest.cc",
diff --git a/components/autofill/core/browser/data_model/birthdate.cc b/components/autofill/core/browser/data_model/birthdate.cc
new file mode 100644
index 0000000..bb49400
--- /dev/null
+++ b/components/autofill/core/browser/data_model/birthdate.cc
@@ -0,0 +1,74 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/data_model/birthdate.h"
+
+#include "base/notreached.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/autofill/core/browser/autofill_type.h"
+
+namespace autofill {
+
+using structured_address::VerificationStatus;
+
+bool operator==(const Birthdate& a, const Birthdate& b) {
+ return a.day_ == b.day_ && a.month_ == b.month_ && a.year_ == b.year_;
+}
+
+std::u16string Birthdate::GetRawInfo(ServerFieldType type) const {
+ DCHECK_EQ(AutofillType(type).group(), FieldTypeGroup::kBirthdateField);
+
+ auto ToStringOrEmpty = [](int n) {
+ return n != 0 ? base::NumberToString16(n) : std::u16string();
+ };
+
+ switch (type) {
+ case BIRTHDATE_DAY:
+ return ToStringOrEmpty(day_);
+ case BIRTHDATE_MONTH:
+ return ToStringOrEmpty(month_);
+ case BIRTHDATE_YEAR_4_DIGITS:
+ return ToStringOrEmpty(year_);
+ default:
+ return std::u16string();
+ }
+}
+
+void Birthdate::SetRawInfoWithVerificationStatus(ServerFieldType type,
+ const std::u16string& value,
+ VerificationStatus status) {
+ DCHECK_EQ(AutofillType(type).group(), FieldTypeGroup::kBirthdateField);
+
+ // If |value| is not a number, |StringToInt()| sets it to 0, which will clear
+ // the field.
+ int int_value;
+ base::StringToInt(value, &int_value);
+
+ auto ValueIfInRangeOrZero = [int_value](int lower_bound, int upper_bound) {
+ return lower_bound <= int_value && int_value <= upper_bound ? int_value : 0;
+ };
+ // Set the appropriate field to |int_value| if it passes some rudimentary
+ // validation. Otherwise clear it.
+ switch (type) {
+ case BIRTHDATE_DAY:
+ day_ = ValueIfInRangeOrZero(1, 31);
+ break;
+ case BIRTHDATE_MONTH:
+ month_ = ValueIfInRangeOrZero(1, 12);
+ break;
+ case BIRTHDATE_YEAR_4_DIGITS:
+ year_ = ValueIfInRangeOrZero(1900, 9999);
+ break;
+ default:
+ NOTREACHED();
+ }
+}
+
+void Birthdate::GetSupportedTypes(ServerFieldTypeSet* supported_types) const {
+ supported_types->insert(BIRTHDATE_DAY);
+ supported_types->insert(BIRTHDATE_MONTH);
+ supported_types->insert(BIRTHDATE_YEAR_4_DIGITS);
+}
+
+} // namespace autofill
diff --git a/components/autofill/core/browser/data_model/birthdate.h b/components/autofill/core/browser/data_model/birthdate.h
new file mode 100644
index 0000000..24184b3
--- /dev/null
+++ b/components/autofill/core/browser/data_model/birthdate.h
@@ -0,0 +1,47 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_DATA_MODEL_BIRTHDATE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_DATA_MODEL_BIRTHDATE_H_
+
+#include <string>
+
+#include "components/autofill/core/browser/data_model/form_group.h"
+
+namespace autofill {
+
+// A form group that stores birthdate information.
+class Birthdate : public FormGroup {
+ public:
+ Birthdate() = default;
+ Birthdate(const Birthdate& other) = default;
+ Birthdate& operator=(const Birthdate& other) = default;
+
+ friend bool operator==(const Birthdate& a, const Birthdate& b);
+ friend bool operator!=(const Birthdate& a, const Birthdate& b) {
+ return !(a == b);
+ }
+
+ // FormGroup:
+ std::u16string GetRawInfo(ServerFieldType type) const override;
+
+ void SetRawInfoWithVerificationStatus(
+ ServerFieldType type,
+ const std::u16string& value,
+ structured_address::VerificationStatus status) override;
+
+ private:
+ // FormGroup:
+ void GetSupportedTypes(ServerFieldTypeSet* supported_types) const override;
+
+ // Zero represents an unset value.
+ int day_ = 0;
+ int month_ = 0;
+ // 4 digits.
+ int year_ = 0;
+};
+
+} // namespace autofill
+
+#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_DATA_MODEL_BIRTHDATE_H_
diff --git a/components/autofill/core/browser/data_model/birthdate_unittest.cc b/components/autofill/core/browser/data_model/birthdate_unittest.cc
new file mode 100644
index 0000000..6598bbf
--- /dev/null
+++ b/components/autofill/core/browser/data_model/birthdate_unittest.cc
@@ -0,0 +1,76 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/data_model/birthdate.h"
+
+#include "components/autofill/core/browser/field_types.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+namespace {
+
+Birthdate CreateBirthdate(const std::u16string& day,
+ const std::u16string& month,
+ const std::u16string& year) {
+ Birthdate birthdate;
+ birthdate.SetRawInfo(BIRTHDATE_DAY, day);
+ birthdate.SetRawInfo(BIRTHDATE_MONTH, month);
+ birthdate.SetRawInfo(BIRTHDATE_YEAR_4_DIGITS, year);
+ return birthdate;
+}
+
+void VerifyValues(const Birthdate& birthdate,
+ const std::u16string& day,
+ const std::u16string& month,
+ const std::u16string& year) {
+ EXPECT_EQ(birthdate.GetRawInfo(BIRTHDATE_DAY), day);
+ EXPECT_EQ(birthdate.GetRawInfo(BIRTHDATE_MONTH), month);
+ EXPECT_EQ(birthdate.GetRawInfo(BIRTHDATE_YEAR_4_DIGITS), year);
+}
+
+// Expect that setting |field| to |value| clears the |field|. This is used to
+// test invalid and empty values.
+void SetFieldAndExpectEmpty(ServerFieldType field,
+ const std::u16string& value) {
+ Birthdate birthdate = CreateBirthdate(u"14", u"3", u"1997");
+ VerifyValues(birthdate, u"14", u"3", u"1997");
+ birthdate.SetRawInfo(field, value);
+ EXPECT_TRUE(birthdate.GetRawInfo(field).empty());
+}
+
+} // anonymous namespace
+
+// Tests writing and reading various valid birthdates.
+TEST(BirthdateTest, ValidBirthdate) {
+ Birthdate birthdate = CreateBirthdate(u"14", u"3", u"1997");
+ VerifyValues(birthdate, u"14", u"3", u"1997");
+
+ // Leading zeros are fine, but they are trimmed internally.
+ birthdate = CreateBirthdate(u"07", u"06", u"1997");
+ VerifyValues(birthdate, u"7", u"6", u"1997");
+
+ // Leading and trailing spaces are fine too.
+ birthdate = CreateBirthdate(u"24 ", u" 12", u" 2022 ");
+ VerifyValues(birthdate, u"24", u"12", u"2022");
+}
+
+// Tests that invalid values clear the corresponding fields.
+TEST(BirthdateTest, Validation) {
+ SetFieldAndExpectEmpty(BIRTHDATE_DAY, u"42");
+ SetFieldAndExpectEmpty(BIRTHDATE_DAY, u"NaN");
+ SetFieldAndExpectEmpty(BIRTHDATE_MONTH, u"13");
+ SetFieldAndExpectEmpty(BIRTHDATE_MONTH, u"a");
+ SetFieldAndExpectEmpty(BIRTHDATE_YEAR_4_DIGITS, u"12345");
+ SetFieldAndExpectEmpty(BIRTHDATE_YEAR_4_DIGITS, u"1234");
+}
+
+// Tests that empty values clear the corresponding fields.
+TEST(BirthdateTest, Clear) {
+ for (const ServerFieldType component :
+ {BIRTHDATE_DAY, BIRTHDATE_MONTH, BIRTHDATE_YEAR_4_DIGITS}) {
+ SetFieldAndExpectEmpty(component, u"");
+ }
+}
+
+} // namespace autofill