Repository: VictorAvelar/mollie-api-go
Pull request #4 (2019-09-07 11:38:23)

configuration and utilities
- 8e3130d Added scrutinizer check for code style and coverage
- dbaf84f Added Mollie standard types
- 1a220e4 Improved code comments
- a089626 Increased code coverage and testing doc examples



Patch:
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
new file mode 100644
index 0000000..10e6b75
--- /dev/null
+++ b/.scrutinizer.yml
@@ -0,0 +1,27 @@
+checks:
+  go: true
+filter:
+  excluded_paths:
+    - "mollie/*_test.go"
+    - "mollie/testdata/"
+build:
+  environment:
+    variables:
+      GO111MODULES: 'on'
+  nodes:
+    analysis:
+      project_setup:
+        override:
+          - 'true'
+      environment:
+        go:
+          version: go1.12.9
+      tests:
+        override:
+          - go-scrutinizer-run
+          - govet-run
+          - golint-run
+          - command: 'go test ./... -coverprofile=cover.out'
+            coverage:
+              file: 'cover.out'
+              format: 'go-cc'
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index e6a208b..ae850f7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,7 +22,7 @@ notifications:
 go:
   - 1.11.x
   - 1.12.x
-  - master
+  - 1.13.x
 
 # script always runs to completion (set +e). If we have linter issues AND a
 # failing test, we want to see both. Configure golangci-lint with a
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 604d09c..ce9556c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,12 +9,16 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
 ### Added
 - Base HTTP Client for interacting with Mollie REST API
 - Tests for the added code
+- 8e3130d Added scrutinizer check for code style and coverage
+- dbaf84f Added Mollie standard types
 
 ### Deprecated
 - Nothing
 
 ### Changed
 - ClientContext for Ctx in APIClient
+- 1a220e4 Improved code comments
+- a089626 Increased code coverage and testing doc examples
 
 ### Fixed
 - Missing types for MOLLIE env variables
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..469796c
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,240 @@
+# mollie
+--
+    import "github.com/VictorAvelar/mollie-api-go/mollie"
+
+Package mollie is a wrapper around Mollie's REST API.
+
+See: https://www.mollie.com/en/developers
+
+The Mollie API implements a Representational state transfer (REST) architecture.
+
+Sounds technical, but it’s really quite easy. It mainly breaks down to
+HTTP-methods GET, PATCH, POST and DELETE matching the operations to read,
+update, create and delete.
+
+REST also implies a nice and clean structure for URLs or endpoints. This means
+you can reach any part of the Mollie API on https://api.mollie.com/v2/ adding
+the name of the resource you want to interact with.
+
+## Usage
+
+```go
+const (
+	BaseURL     string = "https://api.mollie.com"
+	APIVersion  string = "v2"
+	AuthHeader  string = "authorization"
+	TokenType   string = "Bearer"
+	APITokenEnv string = "MOLLIE_API_TOKEN"
+	OrgTokenEnv string = "MOLLIE_ORG_TOKEN"
+)
+```
+Mollie constants holding values to initialize the client and create requests.
+
+#### type APIClient
+
+```go
+type APIClient struct {
+	Ctx               context.Context
+	HTTPClient        httpClient
+	BaseURL           *url.URL
+	APIKey            string
+	OrganizationToken string
+}
+```
+
+APIClient for Mollie API plus information related to the different
+authentication methods provided by the API.
+
+#### func  NewClient
+
+```go
+func NewClient(ctx context.Context, baseClient httpClient, uri string) (mollie *APIClient, err error)
+```
+NewClient returns a fully qualified Mollie HTTP API client with context. It
+receives a context, a httpClient and a uri to initialize the client but also
+accepts nil in some cases.
+
+If nil is passed to context, context.Background will be initialized. if nil is
+passed as httpClient then the http.DefaultClient will be initialized. The uri
+will be parsed with url.Parse function.
+
+By default NewClient will lookup the environment for values to assign to the API
+token (`MOLLIE_API_TOKEN`) and the Organization token (`MOLLIE_ORG_TOKEN`).
+
+You can also set the token values programmatically by using the APIClient
+WithAPIKey and WithOrganizationKey functions.
+
+#### func (*APIClient) NewAPIRequest
+
+```go
+func (c *APIClient) NewAPIRequest(m string, uri string, body io.Reader, auth bool) (req *http.Request, err error)
+```
+NewAPIRequest is a wrapper around the http.NewRequest function. It takes the
+same parameters plus a flag to indicate if the request needs to have the
+authorization headers.
+
+For setting up the headers it takes a hierarchical approach, this meaning that
+if set the APIClient.OrganizationToken will be used, if this value is empty then
+it will attempt to use the APIClient.APIKey, and if this value is also empty it
+will return an error.
+
+#### func (*APIClient) WithAPIKey
+
+```go
+func (c *APIClient) WithAPIKey(k string) error
+```
+WithAPIKey offers a convenient setter with some base validation to attach an API
+key to an APIClient.
+
+Ideally your API key will be provided from and environment variable or a secret
+management engine.
+
+#### func (*APIClient) WithOrganizationToken
+
+```go
+func (c *APIClient) WithOrganizationToken(t string) error
+```
+WithOrganizationToken offers a convenient token with some base validation to
+attach a Mollie Organization Token to an APIClient.
+
+Ideally your API key will be provided from and environment variable or a secret
+management engine.
+
+#### type Address
+
+```go
+type Address struct {
+	StreetAndNumber  string `json:"streetAndNumber"`
+	PostalCode       string `json:"postalCode"`
+	City             string `json:"city"`
+	Country          string `json:"country"`
+	Region           string `json:"region,omitempty"`
+	StreetAdditional string `json:"streetAdditional,omitempty"`
+}
+```
+
+Address contains information to provide a means of physically locating a place
+in the urban geographical space.
+
+For Mollie the country must follow an ISO 3166-1 alpha-2 format, postal codes
+must belong to the specified city and country. When providing an address object
+as parameter to a request, the following conditions must be met:
+
+    - If any of the fields is provided, all fields have to be provided
+      with exception of the region field.
+    - If only the region field is given, one should provide all the other
+      fields as per the previous condition.
+    - For certain PayPal payments the region field is required.
+
+#### type Amount
+
+```go
+type Amount struct {
+	Currency string `json:"currency"`
+	Value    string `json:"value"`
+}
+```
+
+Amount contains a currency code compliant to the ISO 4217 specification and a
+value with the exact amount in the given currency.
+
+#### type Date
+
+```go
+type Date struct {
+	time.Time
+}
+```
+
+Date is represented on `YYYY-MM-DD` format for Mollie requests, in order to
+comply with this the time.Time struct is embedded on Date and custom marshal and
+unmarshal will be provided.
+
+#### func (*Date) UnmarshalJSON
+
+```go
+func (d *Date) UnmarshalJSON(b []byte) error
+```
+UnmarshalJSON overrides the default unmarshal action for the Date struct, as we
+need links to be pointers to the time.Time struct.
+
+#### type HAL
+
+```go
+type HAL map[string]Link
+```
+
+HAL is a map of Links that contains relevant resources according to the
+response.
+
+#### type HalURL
+
+```go
+type HalURL struct {
+	*url.URL
+}
+```
+
+HalURL has url.URL embedded to modify the default json marshal behaviour.
+
+#### func (*HalURL) UnmarshalJSON
+
+```go
+func (hl *HalURL) UnmarshalJSON(b []byte) error
+```
+UnmarshalJSON overrides the default unmarshal action for the HalURL struct, as
+we need links to be pointers to the url.URL struct.
+
+#### type Link
+
+```go
+type Link struct {
+	Href        HalURL `json:"href"`
+	ContentType string `json:"type"`
+}
+```
+
+Link for Mollie are commonly represented as objects with an href and type field.
+
+#### type Locale
+
+```go
+type Locale string
+```
+
+Locale is a string representing the country and language in ISO 15897 format.
+
+#### type PhoneNumber
+
+```go
+type PhoneNumber string
+```
+
+PhoneNumber is a string in the E.164 format. For example, `+31208202070`.
+
+#### type QRCode
+
+```go
+type QRCode struct {
+	Height int `json:"height"`
+	Width  int `json:"width"`
+	Source int `json:"src"`
+}
+```
+
+QRCode represents an image of a QR code.
+
+#### type ResponseErr
+
+```go
+type ResponseErr struct {
+	Status int    `json:"status"`
+	Title  string `json:"title"`
+	Detail string `json:"detail"`
+	Field  string `json:"field,omitempty"`
+	Links  HAL    `json:"_links"`
+}
+```
+
+ResponseErr describes the content of the response body when the http call to
+Mollie API is not successful.
diff --git a/mollie/doc.go b/mollie/doc.go
new file mode 100644
index 0000000..677e070
--- /dev/null
+++ b/mollie/doc.go
@@ -0,0 +1,13 @@
+// Package mollie is a wrapper around Mollie's REST API.
+//
+// See: https://www.mollie.com/en/developers
+//
+// The Mollie API implements a Representational state transfer (REST) architecture.
+//
+// Sounds technical, but it’s really quite easy. It mainly breaks down to HTTP-methods
+// GET, PATCH, POST and DELETE matching the operations to read, update, create and delete.
+//
+// REST also implies a nice and clean structure for URLs or endpoints.
+// This means you can reach any part of the Mollie API on https://api.mollie.com/v2/ adding
+// the name of the resource you want to interact with.
+package mollie
diff --git a/mollie/mollie.go b/mollie/mollie.go
index a148971..11263a7 100644
--- a/mollie/mollie.go
+++ b/mollie/mollie.go
@@ -40,6 +40,7 @@ type APIClient struct {
 
 //WithAPIKey offers a convenient setter with some base validation to attach
 //an API key to an APIClient.
+//
 //Ideally your API key will be provided from and environment variable or
 //a secret management engine.
 func (c *APIClient) WithAPIKey(k string) error {
@@ -54,6 +55,7 @@ func (c *APIClient) WithAPIKey(k string) error {
 
 //WithOrganizationToken offers a convenient token with some base validation to
 //attach a Mollie Organization Token to an APIClient.
+//
 //Ideally your API key will be provided from and environment variable or
 //a secret management engine.
 func (c *APIClient) WithOrganizationToken(t string) error {
@@ -67,9 +69,10 @@ func (c *APIClient) WithOrganizationToken(t string) error {
 //NewAPIRequest is a wrapper around the http.NewRequest function.
 //It takes the same parameters plus a flag to indicate if the request needs
 //to have the authorization headers.
+//
 //For setting up the headers it takes a hierarchical approach, this meaning that
 //if set the APIClient.OrganizationToken will be used, if this value is empty then
-//it will attemp to use the APIClient.APIKey, and if this value is also empty it
+//it will attempt to use the APIClient.APIKey, and if this value is also empty it
 //will return an error.
 func (c *APIClient) NewAPIRequest(m string, uri string, body io.Reader, auth bool) (req *http.Request, err error) {
 	uri = strings.Trim(uri, "/")
@@ -106,11 +109,14 @@ func (c *APIClient) NewAPIRequest(m string, uri string, body io.Reader, auth boo
 //NewClient returns a fully qualified Mollie HTTP API client with context.
 //It receives a context, a httpClient and a uri to initialize the client
 //but also accepts nil in some cases.
+//
 //If nil is passed to context, context.Background will be initialized.
 //if nil is passed as httpClient then the http.DefaultClient will be initialized.
-//The uri will be parsed with url.Parse() function.
+//The uri will be parsed with url.Parse function.
+//
 //By default NewClient will lookup the environment for values to assign to the
-//API token (MOLLIE_API_TOKEN) and the Organization token (MOLLIE_ORG_TOKEN).
+//API token (`MOLLIE_API_TOKEN`) and the Organization token (`MOLLIE_ORG_TOKEN`).
+//
 //You can also set the token values programmatically by using the APIClient
 //WithAPIKey and WithOrganizationKey functions.
 func NewClient(ctx context.Context, baseClient httpClient, uri string) (mollie *APIClient, err error) {
diff --git a/mollie/types.go b/mollie/types.go
new file mode 100644
index 0000000..67476fc
--- /dev/null
+++ b/mollie/types.go
@@ -0,0 +1,114 @@
+package mollie
+
+import (
+	"net/url"
+	"strings"
+	"time"
+)
+
+//Locale is a string representing the country and language
+//in ISO 15897 format.
+type Locale string
+
+//PhoneNumber is a string in the E.164 format.
+//For example, `+31208202070`.
+type PhoneNumber string
+
+//Amount contains a currency code compliant to the ISO 4217
+//specification and a value with the exact amount in the
+//given currency.
+type Amount struct {
+	Currency string `json:"currency"`
+	Value    string `json:"value"`
+}
+
+//Address contains information to provide a means of physically
+//locating a place in the urban geographical space.
+//
+//For Mollie the country must follow an ISO 3166-1 alpha-2 format,
+//postal codes must belong to the specified city and country.
+//When providing an address object as parameter to a request,
+//the following conditions must be met:
+//
+//	- If any of the fields is provided, all fields have to be provided
+//	  with exception of the region field.
+//	- If only the region field is given, one should provide all the other
+//	  fields as per the previous condition.
+//	- For certain PayPal payments the region field is required.
+type Address struct {
+	StreetAndNumber  string `json:"streetAndNumber"`
+	PostalCode       string `json:"postalCode"`
+	City             string `json:"city"`
+	Country          string `json:"country"`
+	Region           string `json:"region,omitempty"`
+	StreetAdditional string `json:"streetAdditional,omitempty"`
+}
+
+//Date is represented on `YYYY-MM-DD` format for Mollie requests,
+//in order to comply with this the time.Time struct is embedded
+//on Date and custom marshal and unmarshal will be provided.
+type Date struct {
+	time.Time
+}
+
+//UnmarshalJSON overrides the default unmarshal action
+//for the Date struct, as we need links to be pointers
+//to the time.Time struct.
+func (d *Date) UnmarshalJSON(b []byte) error {
+	s := string(b)
+	s = strings.Trim(s, "\"")
+	t, err := time.Parse("2006-01-02", s)
+	if err != nil {
+		return err
+	}
+	d.Time = t
+	return nil
+}
+
+//QRCode represents an image of a QR code.
+type QRCode struct {
+	Height int `json:"height"`
+	Width  int `json:"width"`
+	Source int `json:"src"`
+}
+
+//Link for Mollie are commonly represented as objects
+//with an href and type field.
+type Link struct {
+	Href        HalURL `json:"href"`
+	ContentType string `json:"type"`
+}
+
+//HalURL has url.URL embedded to modify the default json
+//marshal behaviour.
+type HalURL struct {
+	*url.URL
+}
+
+//UnmarshalJSON overrides the default unmarshal action
+//for the HalURL struct, as we need links to be pointers
+//to the url.URL struct.
+func (hl *HalURL) UnmarshalJSON(b []byte) error {
+	s := string(b)
+	s = strings.Trim(s, "\"")
+	uri, err := url.Parse(s)
+	if err != nil {
+		return err
+	}
+	hl.URL = uri
+	return nil
+}
+
+//HAL is a map of Links that contains relevant resources
+//according to the response.
+type HAL map[string]Link
+
+//ResponseErr describes the content of the response body
+//when the http call to Mollie API is not successful.
+type ResponseErr struct {
+	Status int    `json:"status"`
+	Title  string `json:"title"`
+	Detail string `json:"detail"`
+	Field  string `json:"field,omitempty"`
+	Links  HAL    `json:"_links"`
+}


Test patch:
diff --git a/mollie/mollie_test.go b/mollie/mollie_test.go
index 7e34e2a..7639fa1 100644
--- a/mollie/mollie_test.go
+++ b/mollie/mollie_test.go
@@ -3,6 +3,8 @@ package mollie
 import (
 	"context"
 	"errors"
+	"fmt"
+	"log"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -210,7 +212,7 @@ func TestWithOrganizationToken(t *testing.T) {
 	}
 }
 
-func TestNewAPIRequest_WellformedRequest(t *testing.T) {
+func TestNewAPIRequest_WellFormedRequest(t *testing.T) {
 	setup()
 	defer teardown()
 
@@ -237,7 +239,7 @@ func TestNewAPIRequest_WithAPIKeyAuthHeader(t *testing.T) {
 
 	var testKey = "test_demoAPIKey1234"
 
-	testClient.WithAPIKey(testKey)
+	_ = testClient.WithAPIKey(testKey)
 
 	req, err := testClient.NewAPIRequest("", "/testing", nil, true)
 	if err != nil {
@@ -258,9 +260,9 @@ func TestNewAPIRequest_WithOrgTokenAuthHeader(t *testing.T) {
 	var apiKey = "test_demoAPIKey1234"
 	var orgToken = "token_demoOrgToken"
 
-	// Accoring to the token hierarchy Org Token should be used.
-	testClient.WithOrganizationToken(orgToken)
-	testClient.WithAPIKey(apiKey)
+	// According to the token hierarchy Org Token should be used.
+	_ = testClient.WithOrganizationToken(orgToken)
+	_ = testClient.WithAPIKey(apiKey)
 
 	req, err := testClient.NewAPIRequest("", "/testing", nil, true)
 	if err != nil {
@@ -284,3 +286,25 @@ func TestNewAPIRequest_AuthError(t *testing.T) {
 		t.Fatalf("mismatching error message: got %v, want %v", err, errEmptyAPIKey)
 	}
 }
+
+func TestNewAPIRequest_RequestError(t *testing.T) {
+	setup()
+	defer teardown()
+	m := "\\\\\\"
+	want := fmt.Errorf("net/http: invalid method %q", m)
+	_, err := testClient.NewAPIRequest(m, "https://google.com", nil, false)
+
+	if err.Error() != want.Error() {
+		t.Errorf("wrong expected error, got: %v, want: %v", err, want)
+	}
+}
+
+// ----- mollie examples -----
+
+func ExampleNewClient() {
+	var ctx context.Context
+	c, err := NewClient(ctx, nil, BaseURL)
+	log.Printf("%+v", c)
+	fmt.Println(err == nil)
+	// Output: true
+}
diff --git a/mollie/types_test.go b/mollie/types_test.go
new file mode 100644
index 0000000..8e2e65d
--- /dev/null
+++ b/mollie/types_test.go
@@ -0,0 +1,65 @@
+package mollie
+
+import (
+	"testing"
+)
+
+func TestHalURL_UnmarshalJSON(t *testing.T) {
+	type args struct {
+		b []byte
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			"Unmarshal google.com",
+			args{b: []byte("https://google.com")},
+			false,
+		},
+		{
+			"Unmarshal parse error",
+			args{b: []byte(" http://google.com")},
+			true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			hl := &HalURL{}
+			if err := hl.UnmarshalJSON(tt.args.b); (err != nil) != tt.wantErr {
+				t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestDate_UnmarshalJSON(t *testing.T) {
+	type args struct {
+		b []byte
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			"unmarshal fails with invalid date format",
+			args{b: []byte("30-12-1991")},
+			true,
+		},
+		{
+			"unmarshal is successful",
+			args{b: []byte("1991-12-30")},
+			false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			d := &Date{}
+			if err := d.UnmarshalJSON(tt.args.b); (err != nil) != tt.wantErr {
+				t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}