Repository: moov-io/customers
Pull request #163 (2020-09-16 21:02:56)

customers: accept yyyy-mm-dd formatted birthDates
Requested from @joshsadler 


Patch:
diff --git a/api/client.yaml b/api/client.yaml
index 13cfa15..d67ad85 100644
--- a/api/client.yaml
+++ b/api/client.yaml
@@ -1130,10 +1130,8 @@ components:
           $ref: '#/components/schemas/CustomerType'
         birthDate:
           type: string
-          format: date-time
           description: Legal date of birth
-          example: '2016-08-29T09:12:33.001Z'
-          nullable: true
+          example: '2016-08-29'
         email:
           type: string
           maximum: 500
@@ -1161,9 +1159,8 @@ components:
       required:
         - firstName
         - lastName
-        - birthDate
+        - type
         - email
-        - addresses
     Customer:
       type: object
       properties:
@@ -1196,10 +1193,8 @@ components:
           $ref: '#/components/schemas/CustomerType'
         birthDate:
           type: string
-          format: date-time
           description: Legal date of birth
-          example: '2016-08-29T09:12:33.001Z'
-          nullable: true
+          example: '2016-08-29'
         status:
           $ref: '#/components/schemas/CustomerStatus'
         email:
@@ -1235,7 +1230,6 @@ components:
         - firstName
         - lastName
         - type
-        - birthDate
         - status
         - email
         - createdAt
diff --git a/cmd/server/customers.go b/cmd/server/customers.go
index 32ad8a3..0b02102 100644
--- a/cmd/server/customers.go
+++ b/cmd/server/customers.go
@@ -19,6 +19,7 @@ import (
 	"github.com/moov-io/customers/cmd/server/route"
 	"github.com/moov-io/customers/internal/usstates"
 	"github.com/moov-io/customers/pkg/client"
+	"github.com/moov-io/customers/pkg/model"
 
 	"github.com/go-kit/kit/log"
 	"github.com/gorilla/mux"
@@ -89,7 +90,7 @@ type customerRequest struct {
 	NickName   string              `json:"nickName"`
 	Suffix     string              `json:"suffix"`
 	Type       client.CustomerType `json:"type"`
-	BirthDate  *time.Time          `json:"birthDate"`
+	BirthDate  model.YYYYMMDD      `json:"birthDate"`
 	Email      string              `json:"email"`
 	SSN        string              `json:"SSN"`
 	Phones     []phone             `json:"phones"`
@@ -169,7 +170,7 @@ func (req customerRequest) asCustomer(storage *ssnStorage) (*client.Customer, *S
 		NickName:   req.NickName,
 		Suffix:     req.Suffix,
 		Type:       req.Type,
-		BirthDate:  req.BirthDate,
+		BirthDate:  string(req.BirthDate),
 		Email:      req.Email,
 		Status:     client.UNKNOWN,
 		Metadata:   req.Metadata,
diff --git a/pkg/client/api/openapi.yaml b/pkg/client/api/openapi.yaml
index 24bf6ba..722cf21 100644
--- a/pkg/client/api/openapi.yaml
+++ b/pkg/client/api/openapi.yaml
@@ -1402,7 +1402,7 @@ components:
           type: Home
         middleName: Flex
         suffix: suffix
-        birthDate: 2016-08-29T09:12:33.001Z
+        birthDate: 2016-08-29
         email: email
         SSN: 111-11-1111
       properties:
@@ -1431,9 +1431,7 @@ components:
           $ref: '#/components/schemas/CustomerType'
         birthDate:
           description: Legal date of birth
-          example: 2016-08-29T09:12:33.001Z
-          format: date-time
-          nullable: true
+          example: 2016-08-29
           type: string
         email:
           description: Primary email address of customer name@domain.com
@@ -1461,11 +1459,10 @@ components:
             paygateID: 23beb5fd
           type: object
       required:
-      - addresses
-      - birthDate
       - email
       - firstName
       - lastName
+      - type
       type: object
     Customer:
       example:
@@ -1500,7 +1497,7 @@ components:
           number: +1.818.555.1212
           type: Home
         suffix: suffix
-        birthDate: 2016-08-29T09:12:33.001Z
+        birthDate: 2016-08-29
         firstName: Robert
         createdAt: 2016-08-29T09:12:33.001Z
         customerID: e210a9d6
@@ -1537,9 +1534,7 @@ components:
           $ref: '#/components/schemas/CustomerType'
         birthDate:
           description: Legal date of birth
-          example: 2016-08-29T09:12:33.001Z
-          format: date-time
-          nullable: true
+          example: 2016-08-29
           type: string
         status:
           $ref: '#/components/schemas/CustomerStatus'
@@ -1573,7 +1568,6 @@ components:
           format: date-time
           type: string
       required:
-      - birthDate
       - createdAt
       - customerID
       - email
diff --git a/pkg/client/docs/CreateCustomer.md b/pkg/client/docs/CreateCustomer.md
index fd9b8b1..026d4cd 100644
--- a/pkg/client/docs/CreateCustomer.md
+++ b/pkg/client/docs/CreateCustomer.md
@@ -9,12 +9,12 @@ Name | Type | Description | Notes
 **LastName** | **string** | Surname or Last Name | 
 **NickName** | **string** | Name Customer is preferred to be called | [optional] 
 **Suffix** | **string** | Customers name suffix. \&quot;Jr\&quot;, \&quot;PH.D.\&quot; | [optional] 
-**Type** | [**CustomerType**](CustomerType.md) |  | [optional] 
-**BirthDate** | Pointer to [**time.Time**](time.Time.md) | Legal date of birth | 
+**Type** | [**CustomerType**](CustomerType.md) |  | 
+**BirthDate** | **string** | Legal date of birth | [optional] 
 **Email** | **string** | Primary email address of customer name@domain.com | 
 **SSN** | **string** | Customer Social Security Number (SSN) | [optional] 
 **Phones** | [**[]CreatePhone**](CreatePhone.md) |  | [optional] 
-**Addresses** | [**[]CreateCustomerAddress**](CreateCustomerAddress.md) |  | 
+**Addresses** | [**[]CreateCustomerAddress**](CreateCustomerAddress.md) |  | [optional] 
 **Metadata** | **map[string]string** | Map of unique keys associated to values to act as foreign key relationships or arbitrary data associated to a Customer. | [optional] 
 
 [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
diff --git a/pkg/client/docs/Customer.md b/pkg/client/docs/Customer.md
index 3eed386..2ffb0d2 100644
--- a/pkg/client/docs/Customer.md
+++ b/pkg/client/docs/Customer.md
@@ -11,7 +11,7 @@ Name | Type | Description | Notes
 **NickName** | **string** | Name Customer is preferred to be called | [optional] 
 **Suffix** | **string** | Customers name suffix. \&quot;Jr\&quot;, \&quot;PH.D.\&quot; | [optional] 
 **Type** | [**CustomerType**](CustomerType.md) |  | 
-**BirthDate** | Pointer to [**time.Time**](time.Time.md) | Legal date of birth | 
+**BirthDate** | **string** | Legal date of birth | [optional] 
 **Status** | [**CustomerStatus**](CustomerStatus.md) |  | 
 **Email** | **string** | Primary email address of customer name@domain.com | 
 **Phones** | [**[]Phone**](Phone.md) |  | [optional] 
diff --git a/pkg/client/model_create_customer.go b/pkg/client/model_create_customer.go
index c940c85..30baf97 100644
--- a/pkg/client/model_create_customer.go
+++ b/pkg/client/model_create_customer.go
@@ -9,10 +9,6 @@
 
 package client
 
-import (
-	"time"
-)
-
 // CreateCustomer struct for CreateCustomer
 type CreateCustomer struct {
 	// Given Name or First Name
@@ -25,15 +21,15 @@ type CreateCustomer struct {
 	NickName string `json:"nickName,omitempty"`
 	// Customers name suffix. \"Jr\", \"PH.D.\"
 	Suffix string       `json:"suffix,omitempty"`
-	Type   CustomerType `json:"type,omitempty"`
+	Type   CustomerType `json:"type"`
 	// Legal date of birth
-	BirthDate *time.Time `json:"birthDate"`
+	BirthDate string `json:"birthDate,omitempty"`
 	// Primary email address of customer name@domain.com
 	Email string `json:"email"`
 	// Customer Social Security Number (SSN)
 	SSN       string                  `json:"SSN,omitempty"`
 	Phones    []CreatePhone           `json:"phones,omitempty"`
-	Addresses []CreateCustomerAddress `json:"addresses"`
+	Addresses []CreateCustomerAddress `json:"addresses,omitempty"`
 	// Map of unique keys associated to values to act as foreign key relationships or arbitrary data associated to a Customer.
 	Metadata map[string]string `json:"metadata,omitempty"`
 }
diff --git a/pkg/client/model_customer.go b/pkg/client/model_customer.go
index 5b9d606..3b43e01 100644
--- a/pkg/client/model_customer.go
+++ b/pkg/client/model_customer.go
@@ -29,7 +29,7 @@ type Customer struct {
 	Suffix string       `json:"suffix,omitempty"`
 	Type   CustomerType `json:"type"`
 	// Legal date of birth
-	BirthDate *time.Time     `json:"birthDate"`
+	BirthDate string         `json:"birthDate,omitempty"`
 	Status    CustomerStatus `json:"status"`
 	// Primary email address of customer name@domain.com
 	Email     string            `json:"email"`
diff --git a/pkg/model/yyyymmdd.go b/pkg/model/yyyymmdd.go
new file mode 100644
index 0000000..fa9dd06
--- /dev/null
+++ b/pkg/model/yyyymmdd.go
@@ -0,0 +1,35 @@
+// Copyright 2020 The Moov Authors
+// Use of this source code is governed by an Apache License
+// license that can be found in the LICENSE file.
+
+package model
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+)
+
+var (
+	yyyymmddFormat = "2006-01-02"
+)
+
+type YYYYMMDD string
+
+func (d *YYYYMMDD) UnmarshalJSON(data []byte) error {
+	// Ignore null, like in the main JSON package.
+	if string(data) == "null" {
+		return nil
+	}
+
+	s, err := strconv.Unquote(string(data))
+	if err != nil {
+		return fmt.Errorf("YYMMDD: %v", err)
+	}
+	t, err := time.Parse(yyyymmddFormat, s)
+	if err != nil {
+		return fmt.Errorf("YYMMDD: %v", err)
+	}
+	*d = YYYYMMDD(t.Format(yyyymmddFormat))
+	return nil
+}


Test patch:
diff --git a/cmd/server/customers_test.go b/cmd/server/customers_test.go
index 99d133c..5777015 100644
--- a/cmd/server/customers_test.go
+++ b/cmd/server/customers_test.go
@@ -284,7 +284,7 @@ func TestCustomers__createCustomer(t *testing.T) {
 	w := httptest.NewRecorder()
 	phone := `{"number": "555.555.5555", "type": "mobile"}`
 	address := `{"type": "home", "address1": "123 1st St", "city": "Denver", "state": "CO", "postalCode": "12345", "country": "USA"}`
-	body := fmt.Sprintf(`{"firstName": "jane", "lastName": "doe", "email": "jane@example.com", "ssn": "123456789", "type": "individual", "phones": [%s], "addresses": [%s]}`, phone, address)
+	body := fmt.Sprintf(`{"firstName": "jane", "lastName": "doe", "email": "jane@example.com", "birthDate": "1991-04-01", "ssn": "123456789", "type": "individual", "phones": [%s], "addresses": [%s]}`, phone, address)
 	req := httptest.NewRequest("POST", "/customers", strings.NewReader(body))
 	req.Header.Set("x-user-id", "test")
 	req.Header.Set("x-request-id", "test")
diff --git a/pkg/model/yyyymmdd_test.go b/pkg/model/yyyymmdd_test.go
new file mode 100644
index 0000000..1685d81
--- /dev/null
+++ b/pkg/model/yyyymmdd_test.go
@@ -0,0 +1,37 @@
+// Copyright 2020 The Moov Authors
+// Use of this source code is governed by an Apache License
+// license that can be found in the LICENSE file.
+
+package model
+
+import (
+	"encoding/json"
+	"strings"
+	"testing"
+)
+
+type response struct {
+	BirthDate YYYYMMDD `json:"birthDate"`
+}
+
+func TestYYYYMMDD(t *testing.T) {
+	in := strings.NewReader(`{"birthDate": "1989-11-09"}`)
+
+	var resp response
+	if err := json.NewDecoder(in).Decode(&resp); err != nil {
+		t.Fatal(err)
+	}
+
+	if v := string(resp.BirthDate); v != "1989-11-09" {
+		t.Errorf("got %q", v)
+	}
+}
+
+func TestYYYYMMDD_Error(t *testing.T) {
+	in := strings.NewReader(`{"birthDate": "1989-11-INVALID"}`)
+
+	var resp response
+	if err := json.NewDecoder(in).Decode(&resp); err == nil {
+		t.Fatalf("expcted error, got %#v", resp)
+	}
+}