From cb8f922c72797e5d960abab9c0cb2b96e8ddf18f Mon Sep 17 00:00:00 2001 From: Pablo Morelli Date: Wed, 23 Mar 2022 11:24:06 +0100 Subject: [PATCH] updated to generics version --- .github/workflows/ci.yml | 4 +- README.md | 46 ++++------ bool.go | 54 ------------ float.go | 52 ----------- float_test.go | 161 --------------------------------- go.mod | 2 +- int.go | 52 ----------- int_test.go | 161 --------------------------------- maybe.go | 48 ++++++++++ bool_test.go => maybe_test.go | 87 +++++++++++------- string.go | 52 ----------- string_test.go | 161 --------------------------------- time.go | 55 ------------ time_test.go | 162 ---------------------------------- 14 files changed, 124 insertions(+), 973 deletions(-) delete mode 100644 bool.go delete mode 100644 float.go delete mode 100644 float_test.go delete mode 100644 int.go delete mode 100644 int_test.go create mode 100644 maybe.go rename bool_test.go => maybe_test.go (58%) delete mode 100644 string.go delete mode 100644 string_test.go delete mode 100644 time.go delete mode 100644 time_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e03ac15..cd70bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.16 + go-version: 1.18 - name: Build run: go build -v ./... @@ -27,7 +27,7 @@ jobs: - name: Test run: go test -race -covermode atomic -coverprofile=covprofile ./... - + - name: Send coverage env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 7e43521..a30ab91 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,13 @@ Maybe is a library that adds an [Option data type](https://en.wikipedia.org/wiki ### What does it offer: -The types exported by this library are immutable and thread safe. The json serialization and deserialization works in the same way as with the native types. Using this library will free you up from using pointers and possible panics. +The `Maybe[any]` type exported by this library is immutable and thread safe. The json serialization and de-serialization works in the same way as with the native types. Using this library will free you up from using pointers and possible panics. -It also gets rid of the situations where an absence of value means something different from a default (zero) value. For example: a person with salary 100 means he/she has a paid job, 0 means an unpaid internship and null means unemployed. Supporting yourself with Option eliminates the usage of null replacing it with `HasValue`. +It also gets rid of the situations where an absence of value means something different from a default (zero) value. For example: a person with salary 100 means he/she has a paid job, 0 means an unpaid internship and null means unemployed. Supporting yourself with `Maybe[int]` eliminates the usage of null replacing it with `HasValue`: +- `salary.Value != 0` has a paid job. +- `salary.Value == 0 && salary.HasValue` has an unpaid internship. +- `salary.HasValue` does not have a job, this is serialized as `null` but you don't have to care about pointers. ### When should I use it: @@ -19,7 +22,7 @@ It can be used for transport layer (as it has json capabilities) but it could al ### Examples: -**Marshal of String Option without value** +**Marshal of Maybe[string] without value** ```go package main @@ -32,8 +35,8 @@ import ( ) type Person struct { - Name maybe.String `json:"name"` - Age int `json:"age"` + Name Maybe[string] `json:"name"` + Age int `json:"age"` } func main() { @@ -43,7 +46,7 @@ func main() { } ``` -**Marshal of String Option with value** +**Marshal of Maybe[string] with value** ```go package main @@ -56,18 +59,18 @@ import ( ) type Person struct { - Name maybe.String `json:"name"` - Age int `json:"age"` + Name Maybe[string] `json:"name"` + Age int `json:"age"` } func main() { - p := Person{Age: 28, Name: maybe.SetString("Pablo")} + p := Person{Age: 28, Name: maybe.Set("Pablo")} bytes, _ := json.Marshal(p) fmt.Println(string(bytes)) // {"name":"Pablo","age":28} } ``` -**Unmarshal of String Option without value** +**Unmarshal of Maybe[string] without value** ```go package main @@ -80,8 +83,8 @@ import ( ) type Person struct { - Name maybe.String `json:"name"` - Age int `json:"age"` + Name Maybe[string] `json:"name"` + Age int `json:"age"` } func main() { @@ -92,7 +95,7 @@ func main() { ``` -**Unmarshal of String Option with value** +**Unmarshal of Maybe[string] with value** ```go package main @@ -105,8 +108,8 @@ import ( ) type Person struct { - Name maybe.String `json:"name"` - Age int `json:"age"` + Name Maybe[string] `json:"name"` + Age int `json:"age"` } func main() { @@ -119,15 +122,4 @@ func main() { ### Types supported: -- bool -- string -- float -- int -- time - -If this library is not supporting certain type, feel free to do a pull request or add an issue asking for it. - -### Generics - -Go does not support generics as of now, but the draft was recently approved. When they become available on Go 1.18 this library will be updated and only a generic struct will remain. -The library will look like this: [go2playgrounds](https://go2goplay.golang.org/p/YBqR5GX7N6m). +`Maybe` is defined to support `[T any]` so it can support all underlying types. Personally I would not suggest using pointers as the underlying type as it will defeat the whole purpose. diff --git a/bool.go b/bool.go deleted file mode 100644 index 72ee2ee..0000000 --- a/bool.go +++ /dev/null @@ -1,54 +0,0 @@ -package maybe - -import ( - "encoding/json" -) - -// Bool is an option data type, which means it can have a value or not. -type Bool struct { - value bool - hasValue bool -} - -// SetBool returns a Bool option with a value. -func SetBool(value bool) Bool { - return Bool{ - value: value, - hasValue: true, - } -} - -// HasValue allows to check if the Bool has a value. -func (mb Bool) HasValue() bool { - return mb.hasValue -} - -// Value allows to check the Bool value. -func (mb Bool) Value() bool { - return mb.value -} - -// UnmarshalJSON customises the deserialize behaviour for the Bool option -func (mb *Bool) UnmarshalJSON(data []byte) error { - var b *bool - if err := json.Unmarshal(data, &b); err != nil { - return err - } - - if b != nil { - *mb = SetBool(*b) - } - - return nil -} - -// MarshalJSON customises the serialize behaviour for the Bool option -func (mb Bool) MarshalJSON() ([]byte, error) { - var b *bool - - if mb.hasValue { - b = &mb.value - } - - return json.Marshal(b) -} diff --git a/float.go b/float.go deleted file mode 100644 index 23aade0..0000000 --- a/float.go +++ /dev/null @@ -1,52 +0,0 @@ -package maybe - -import "encoding/json" - -// Float is an option data type, which means it can have a value or not. -type Float struct { - value float64 - hasValue bool -} - -// SetFloat returns a Float option with a value. -func SetFloat(value float64) Float { - return Float{ - value: value, - hasValue: true, - } -} - -// HasValue allows to check if the Float has a value. -func (mf Float) HasValue() bool { - return mf.hasValue -} - -// Value allows to check the Float value. -func (mf Float) Value() float64 { - return mf.value -} - -// UnmarshalJSON customises the deserialize behaviour for the Float option -func (mf *Float) UnmarshalJSON(data []byte) error { - var f *float64 - if err := json.Unmarshal(data, &f); err != nil { - return err - } - - if f != nil { - *mf = SetFloat(*f) - } - - return nil -} - -// MarshalJSON customises the serialize behaviour for the Float option -func (mf Float) MarshalJSON() ([]byte, error) { - var f *float64 - - if mf.hasValue { - f = &mf.value - } - - return json.Marshal(f) -} diff --git a/float_test.go b/float_test.go deleted file mode 100644 index 883b38e..0000000 --- a/float_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package maybe - -import ( - "encoding/json" - "reflect" - "testing" -) - -func Test_SetFloat(t *testing.T) { - tests := []struct { - name string - args float64 - want Float - }{ - { - name: "Valid float", - args: 72.4, - want: Float{hasValue: true, value: 72.4}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := SetFloat(tt.args); !reflect.DeepEqual(got, tt.want) { - t.Errorf("SetFloat() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Float_HasValue(t *testing.T) { - tests := []struct { - name string - arg Float - want bool - }{ - { - name: "Has value", - arg: Float{hasValue: true}, - want: true, - }, - { - name: "Hasn't value", - arg: Float{}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.arg.HasValue(); got != tt.want { - t.Errorf("HasValue() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Float_Value(t *testing.T) { - tests := []struct { - name string - arg Float - want float64 - }{ - { - name: "Value is set", - arg: SetFloat(72.4), - want: 72.4, - }, - { - name: "Value is not set", - arg: Float{}, - want: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.arg.Value(); got != tt.want { - t.Errorf("Value() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Float_Marshal(t *testing.T) { - type person struct { - Weight Float `json:"weight"` - } - tests := []struct { - name string - data person - want []byte - wantErr bool - }{ - { - name: "Property get serialised", - data: person{Weight: SetFloat(72.40)}, - want: []byte(`{"weight":72.4}`), - wantErr: false, - }, - { - name: "Property does not get serialised", - data: person{Weight: Float{}}, - want: []byte(`{"weight":null}`), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := json.Marshal(tt.data) - if (err != nil) != tt.wantErr { - t.Errorf("Marshal error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Marshal got = %v, want %v", string(got), string(tt.want)) - } - }) - } -} - -func Test_Float_Unmarshal(t *testing.T) { - type person struct { - Weight Float `json:"weight"` - } - tests := []struct { - name string - data []byte - want person - wantErr bool - }{ - { - name: "Unmarshal with value", - data: []byte(`{"weight":72.4}`), - want: person{Weight: SetFloat(72.40)}, - wantErr: false, - }, - { - name: "Unmarshal without value", - data: []byte(`{"weight":null}`), - want: person{Weight: Float{}}, - wantErr: false, - }, - { - name: "Unmarshal without value (property missing)", - data: []byte(`{}`), - want: person{Weight: Float{}}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got person - err := json.Unmarshal(tt.data, &got) - if (err != nil) != tt.wantErr { - t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Unmarshal() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/go.mod b/go.mod index 022b34e..35e441b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/pmorelli92/maybe -go 1.16 +go 1.18 diff --git a/int.go b/int.go deleted file mode 100644 index 56dad03..0000000 --- a/int.go +++ /dev/null @@ -1,52 +0,0 @@ -package maybe - -import "encoding/json" - -// Int is an option data type, which means it can have a value or not. -type Int struct { - value int - hasValue bool -} - -// SetInt returns a Int option with a value. -func SetInt(value int) Int { - return Int{ - value: value, - hasValue: true, - } -} - -// HasValue allows to check if the Int has a value. -func (mi Int) HasValue() bool { - return mi.hasValue -} - -// Value allows to check the Int value. -func (mi Int) Value() int { - return mi.value -} - -// UnmarshalJSON customises the deserialize behaviour for the Int option -func (mi *Int) UnmarshalJSON(data []byte) error { - var i *int - if err := json.Unmarshal(data, &i); err != nil { - return err - } - - if i != nil { - *mi = SetInt(*i) - } - - return nil -} - -// MarshalJSON customises the serialize behaviour for the Int option -func (mi Int) MarshalJSON() ([]byte, error) { - var i *int - - if mi.hasValue { - i = &mi.value - } - - return json.Marshal(i) -} diff --git a/int_test.go b/int_test.go deleted file mode 100644 index 140127e..0000000 --- a/int_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package maybe - -import ( - "encoding/json" - "reflect" - "testing" -) - -func Test_SetInt(t *testing.T) { - tests := []struct { - name string - args int - want Int - }{ - { - name: "Valid int", - args: 28, - want: Int{hasValue: true, value: 28}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := SetInt(tt.args); !reflect.DeepEqual(got, tt.want) { - t.Errorf("SetInt() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Int_HasValue(t *testing.T) { - tests := []struct { - name string - arg Int - want bool - }{ - { - name: "Has value", - arg: Int{hasValue: true}, - want: true, - }, - { - name: "Hasn't value", - arg: Int{}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.arg.HasValue(); got != tt.want { - t.Errorf("HasValue() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Int_Value(t *testing.T) { - tests := []struct { - name string - arg Int - want int - }{ - { - name: "Value is set", - arg: SetInt(28), - want: 28, - }, - { - name: "Value is not set", - arg: Int{}, - want: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.arg.Value(); got != tt.want { - t.Errorf("Value() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Int_Marshal(t *testing.T) { - type person struct { - Age Int `json:"age"` - } - tests := []struct { - name string - data person - want []byte - wantErr bool - }{ - { - name: "Property get serialised", - data: person{Age: SetInt(28)}, - want: []byte(`{"age":28}`), - wantErr: false, - }, - { - name: "Property does not get serialised", - data: person{Age: Int{}}, - want: []byte(`{"age":null}`), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := json.Marshal(tt.data) - if (err != nil) != tt.wantErr { - t.Errorf("Marshal error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Marshal got = %v, want %v", string(got), string(tt.want)) - } - }) - } -} - -func Test_Int_Unmarshal(t *testing.T) { - type person struct { - Age Int `json:"age"` - } - tests := []struct { - name string - data []byte - want person - wantErr bool - }{ - { - name: "Unmarshal with value", - data: []byte(`{"age":28}`), - want: person{Age: SetInt(28)}, - wantErr: false, - }, - { - name: "Unmarshal without value", - data: []byte(`{"age":null}`), - want: person{Age: Int{}}, - wantErr: false, - }, - { - name: "Unmarshal without value (property missing)", - data: []byte(`{}`), - want: person{Age: Int{}}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got person - err := json.Unmarshal(tt.data, &got) - if (err != nil) != tt.wantErr { - t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Unmarshal() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/maybe.go b/maybe.go new file mode 100644 index 0000000..05b7946 --- /dev/null +++ b/maybe.go @@ -0,0 +1,48 @@ +package maybe + +import ( + "encoding/json" +) + +type Maybe[T any] struct { + value T + hasValue bool +} + +func Set[T any](value T) Maybe[T] { + return Maybe[T]{ + value: value, + hasValue: true, + } +} + +func (m Maybe[T]) HasValue() bool { + return m.hasValue +} + +func (m Maybe[T]) Value() T { + return m.value +} + +func (m *Maybe[T]) UnmarshalJSON(data []byte) error { + var t *T + if err := json.Unmarshal(data, &t); err != nil { + return err + } + + if t != nil { + *m = Set(*t) + } + + return nil +} + +func (m Maybe[T]) MarshalJSON() ([]byte, error) { + var t *T + + if m.hasValue { + t = &m.value + } + + return json.Marshal(t) +} diff --git a/bool_test.go b/maybe_test.go similarity index 58% rename from bool_test.go rename to maybe_test.go index 8efe93a..c3c2ba0 100644 --- a/bool_test.go +++ b/maybe_test.go @@ -4,43 +4,64 @@ import ( "encoding/json" "reflect" "testing" + "time" ) -func Test_SetBool(t *testing.T) { +func Test_Maybe_Set(t *testing.T) { tests := []struct { name string - args bool - want Bool + args any + want Maybe[any] }{ { - name: "Valid bool", + name: "maybe bool", args: true, - want: Bool{hasValue: true, value: true}, + want: Maybe[any]{hasValue: true, value: true}, + }, + { + name: "maybe float", + args: 72.4, + want: Maybe[any]{hasValue: true, value: 72.4}, + }, + { + name: "maybe int", + args: 28, + want: Maybe[any]{hasValue: true, value: 28}, + }, + { + name: "maybe string", + args: "foo", + want: Maybe[any]{hasValue: true, value: "foo"}, + }, + { + name: "maybe time", + args: time.Date(2020, 04, 28, 18, 34, 52, 0, time.UTC), + want: Maybe[any]{hasValue: true, value: time.Date(2020, 04, 28, 18, 34, 52, 0, time.UTC)}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := SetBool(tt.args); !reflect.DeepEqual(got, tt.want) { - t.Errorf("SetBool() = %v, want %v", got, tt.want) + if got := Set(tt.args); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Set() = %v, want %v", got, tt.want) } }) } } -func Test_Bool_HasValue(t *testing.T) { +func Test_Maybe_HasValue(t *testing.T) { tests := []struct { name string - arg Bool - want bool + arg Maybe[any] + want any }{ { - name: "Has value", - arg: Bool{hasValue: true}, + name: "has value", + arg: Maybe[any]{hasValue: true}, want: true, }, { - name: "Hasn't value", - arg: Bool{}, + name: "hasn't value", + arg: Maybe[any]{}, want: false, }, } @@ -53,21 +74,21 @@ func Test_Bool_HasValue(t *testing.T) { } } -func Test_Bool_Value(t *testing.T) { +func Test_Maybe_Value(t *testing.T) { tests := []struct { name string - arg Bool - want bool + arg Maybe[int] + want any }{ { - name: "Value is set", - arg: SetBool(true), - want: true, + name: "value is set", + arg: Set(24), + want: 24, }, { name: "Value is not set", - arg: Bool{}, - want: false, + arg: Maybe[int]{}, + want: 0, }, } for _, tt := range tests { @@ -79,9 +100,9 @@ func Test_Bool_Value(t *testing.T) { } } -func Test_Bool_Marshal(t *testing.T) { +func Test_Maybe_Marshal(t *testing.T) { type person struct { - IsCitizen Bool `json:"is_citizen"` + IsCitizen Maybe[bool] `json:"is_citizen"` } tests := []struct { name string @@ -90,14 +111,14 @@ func Test_Bool_Marshal(t *testing.T) { wantErr bool }{ { - name: "Property get serialised", - data: person{IsCitizen: SetBool(false)}, + name: "property is serialized", + data: person{IsCitizen: Set(false)}, want: []byte(`{"is_citizen":false}`), wantErr: false, }, { - name: "Property does not get serialised", - data: person{IsCitizen: Bool{}}, + name: "Property isn't get serialized", + data: person{}, want: []byte(`{"is_citizen":null}`), wantErr: false, }, @@ -116,9 +137,9 @@ func Test_Bool_Marshal(t *testing.T) { } } -func Test_Bool_Unmarshal(t *testing.T) { +func Test_Maybe_Unmarshal(t *testing.T) { type person struct { - IsCitizen Bool `json:"is_citizen"` + IsCitizen Maybe[bool] `json:"is_citizen"` } tests := []struct { name string @@ -129,19 +150,19 @@ func Test_Bool_Unmarshal(t *testing.T) { { name: "Unmarshal with value", data: []byte(`{"is_citizen":false}`), - want: person{IsCitizen: SetBool(false)}, + want: person{IsCitizen: Set(false)}, wantErr: false, }, { name: "Unmarshal without value", data: []byte(`{"is_citizen":null}`), - want: person{IsCitizen: Bool{}}, + want: person{IsCitizen: Maybe[bool]{hasValue: false}}, wantErr: false, }, { name: "Unmarshal without value (property missing)", data: []byte(`{}`), - want: person{IsCitizen: Bool{}}, + want: person{IsCitizen: Maybe[bool]{hasValue: false}}, wantErr: false, }, } diff --git a/string.go b/string.go deleted file mode 100644 index 8714e46..0000000 --- a/string.go +++ /dev/null @@ -1,52 +0,0 @@ -package maybe - -import "encoding/json" - -// String is an option data type, which means it can have a value or not. -type String struct { - value string - hasValue bool -} - -// SetString returns a String option with a value. -func SetString(value string) String { - return String{ - value: value, - hasValue: true, - } -} - -// HasValue allows to check if the String has a value. -func (ms String) HasValue() bool { - return ms.hasValue -} - -// Value allows to check the String value. -func (ms String) Value() string { - return ms.value -} - -// UnmarshalJSON customises the deserialize behaviour for the String option -func (ms *String) UnmarshalJSON(data []byte) error { - var s *string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - - if s != nil { - *ms = SetString(*s) - } - - return nil -} - -// MarshalJSON customises the serialize behaviour for the String option -func (ms String) MarshalJSON() ([]byte, error) { - var s *string - - if ms.hasValue { - s = &ms.value - } - - return json.Marshal(s) -} diff --git a/string_test.go b/string_test.go deleted file mode 100644 index a5fb27f..0000000 --- a/string_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package maybe - -import ( - "encoding/json" - "reflect" - "testing" -) - -func Test_SetString(t *testing.T) { - tests := []struct { - name string - args string - want String - }{ - { - name: "Valid string", - args: "Pablo", - want: String{hasValue: true, value: "Pablo"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := SetString(tt.args); !reflect.DeepEqual(got, tt.want) { - t.Errorf("SetString() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_String_HasValue(t *testing.T) { - tests := []struct { - name string - arg String - want bool - }{ - { - name: "Has value", - arg: String{hasValue: true}, - want: true, - }, - { - name: "Hasn't value", - arg: String{}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.arg.HasValue(); got != tt.want { - t.Errorf("HasValue() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_String_Value(t *testing.T) { - tests := []struct { - name string - arg String - want string - }{ - { - name: "Value is set", - arg: SetString("Pablo"), - want: "Pablo", - }, - { - name: "Value is not set", - arg: String{}, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.arg.Value(); got != tt.want { - t.Errorf("Value() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_String_Marshal(t *testing.T) { - type person struct { - Name String `json:"name"` - } - tests := []struct { - name string - data person - want []byte - wantErr bool - }{ - { - name: "Property get serialised", - data: person{Name: SetString("Pablo")}, - want: []byte(`{"name":"Pablo"}`), - wantErr: false, - }, - { - name: "Property does not get serialised", - data: person{Name: String{}}, - want: []byte(`{"name":null}`), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := json.Marshal(tt.data) - if (err != nil) != tt.wantErr { - t.Errorf("Marshal error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Marshal got = %v, want %v", string(got), string(tt.want)) - } - }) - } -} - -func Test_String_Unmarshal(t *testing.T) { - type person struct { - Name String `json:"name"` - } - tests := []struct { - name string - data []byte - want person - wantErr bool - }{ - { - name: "Unmarshal with value", - data: []byte(`{"name":"Pablo"}`), - want: person{Name: SetString("Pablo")}, - wantErr: false, - }, - { - name: "Unmarshal without value", - data: []byte(`{"name":null}`), - want: person{Name: String{}}, - wantErr: false, - }, - { - name: "Unmarshal without value (property missing)", - data: []byte(`{}`), - want: person{Name: String{}}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got person - err := json.Unmarshal(tt.data, &got) - if (err != nil) != tt.wantErr { - t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Unmarshal() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/time.go b/time.go deleted file mode 100644 index 7ccd311..0000000 --- a/time.go +++ /dev/null @@ -1,55 +0,0 @@ -package maybe - -import ( - "encoding/json" - "time" -) - -// Time is an option data type, which means it can have a value or not. -type Time struct { - value time.Time - hasValue bool -} - -// SetTime returns a Time option with a value. -func SetTime(value time.Time) Time { - return Time{ - value: value, - hasValue: true, - } -} - -// HasValue allows to check if the Time has a value. -func (mt Time) HasValue() bool { - return mt.hasValue -} - -// Value allows to check the Time value. -func (mt Time) Value() time.Time { - return mt.value -} - -// UnmarshalJSON customises the deserialize behaviour for the Time option -func (mt *Time) UnmarshalJSON(data []byte) error { - var t *time.Time - if err := json.Unmarshal(data, &t); err != nil { - return err - } - - if t != nil { - *mt = SetTime(*t) - } - - return nil -} - -// MarshalJSON customises the serialize behaviour for the Time option -func (mt Time) MarshalJSON() ([]byte, error) { - var t *time.Time - - if mt.hasValue { - t = &mt.value - } - - return json.Marshal(t) -} diff --git a/time_test.go b/time_test.go deleted file mode 100644 index eb7df09..0000000 --- a/time_test.go +++ /dev/null @@ -1,162 +0,0 @@ -package maybe - -import ( - "encoding/json" - "reflect" - "testing" - "time" -) - -func Test_SetTime(t *testing.T) { - tests := []struct { - name string - args time.Time - want Time - }{ - { - name: "Valid time", - args: time.Date(2020, 04, 28, 18, 34, 52, 0, time.UTC), - want: Time{hasValue: true, value: time.Date(2020, 04, 28, 18, 34, 52, 0, time.UTC)}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := SetTime(tt.args); !reflect.DeepEqual(got, tt.want) { - t.Errorf("SetTime() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Time_HasValue(t *testing.T) { - tests := []struct { - name string - arg Time - want bool - }{ - { - name: "Has value", - arg: Time{hasValue: true}, - want: true, - }, - { - name: "Hasn't value", - arg: Time{}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.arg.HasValue(); got != tt.want { - t.Errorf("HasValue() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Time_Value(t *testing.T) { - tests := []struct { - name string - arg Time - want time.Time - }{ - { - name: "Value is set", - arg: SetTime(time.Date(2020, 04, 28, 18, 34, 52, 0, time.UTC)), - want: time.Date(2020, 04, 28, 18, 34, 52, 0, time.UTC), - }, - { - name: "Value is not set", - arg: Time{}, - want: time.Time{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.arg.Value(); got != tt.want { - t.Errorf("Value() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_Time_Marshal(t *testing.T) { - type person struct { - CreatedAt Time `json:"created_at"` - } - tests := []struct { - name string - data person - want []byte - wantErr bool - }{ - { - name: "Property get serialised", - data: person{CreatedAt: SetTime(time.Date(2020, 04, 28, 18, 34, 52, 0, time.UTC))}, - want: []byte(`{"created_at":"2020-04-28T18:34:52Z"}`), - wantErr: false, - }, - { - name: "Property does not get serialised", - data: person{CreatedAt: Time{}}, - want: []byte(`{"created_at":null}`), - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := json.Marshal(tt.data) - if (err != nil) != tt.wantErr { - t.Errorf("Marshal error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Marshal got = %v, want %v", string(got), string(tt.want)) - } - }) - } -} - -func Test_Time_Unmarshal(t *testing.T) { - type person struct { - CreatedAt Time `json:"created_at"` - } - tests := []struct { - name string - data []byte - want person - wantErr bool - }{ - { - name: "Unmarshal with value", - data: []byte(`{"created_at":"2020-04-28T18:34:52Z"}`), - want: person{CreatedAt: SetTime(time.Date(2020, 04, 28, 18, 34, 52, 0, time.UTC))}, - wantErr: false, - }, - { - name: "Unmarshal without value", - data: []byte(`{"created_at":null}`), - want: person{CreatedAt: Time{}}, - wantErr: false, - }, - { - name: "Unmarshal without value (property missing)", - data: []byte(`{}`), - want: person{CreatedAt: Time{}}, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got person - err := json.Unmarshal(tt.data, &got) - if (err != nil) != tt.wantErr { - t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Unmarshal() got = %v, want %v", got, tt.want) - } - }) - } -}