-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1 parent
87cf522
commit 54f6351
Showing
58 changed files
with
3,324 additions
and
2,006 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,4 +16,4 @@ CREATE TABLE IF NOT EXISTS dog | |
id SERIAL PRIMARY KEY, | ||
name TEXT NOT NULL, | ||
characteristics TEXT[] | ||
); | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
{ | ||
"msg": "test file" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
Test file | ||
Test file |
17 changes: 17 additions & 0 deletions
17
...onment/wiremock/mappings/users-api/mappings/default/success/patch-users-without-body.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"priority": 1, | ||
"request": { | ||
"method": "PATCH", | ||
"urlPattern": "/users-api/v1/users/100", | ||
"bodyPatterns": [ | ||
{ | ||
"equalToJson": { | ||
"name": "User 100 edited" | ||
} | ||
} | ||
] | ||
}, | ||
"response": { | ||
"status": 204 | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
...users-api/mappings/default/success/post-multipart-form-with-custom-content-type-file.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"priority": 1, | ||
"request": { | ||
"method": "POST", | ||
"urlPattern": "/users-api/v1/upload", | ||
"headers": { | ||
"Content-Type": { | ||
"matches": "multipart/form-data;\\s*boundary=[^\\s]+" | ||
} | ||
}, | ||
"bodyPatterns": [ | ||
{ | ||
"matches": ".+Content-Disposition: form-data; name=\"myfile\"; filename=\"test.txt\".*Content-Type: text/plain.+" | ||
} | ||
] | ||
}, | ||
"response": { | ||
"status": 201, | ||
"headers": { | ||
"Content-Type": "application/json; charset=utf-8" | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
...sers-api/mappings/default/success/post-multipart-form-with-default-content-type-file.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"priority": 1, | ||
"request": { | ||
"method": "POST", | ||
"urlPattern": "/users-api/v1/upload", | ||
"headers": { | ||
"Content-Type": { | ||
"matches": "multipart/form-data;\\s*boundary=[^\\s]+" | ||
} | ||
}, | ||
"bodyPatterns": [ | ||
{ | ||
"matches": ".+Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\".*Content-Type: application/octet-stream.+" | ||
} | ||
] | ||
}, | ||
"response": { | ||
"status": 201, | ||
"headers": { | ||
"Content-Type": "application/json; charset=utf-8" | ||
} | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
...wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-fields.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"priority": 1, | ||
"request": { | ||
"method": "POST", | ||
"urlPattern": "/users-api/v1", | ||
"headers": { | ||
"Content-Type": { | ||
"matches": "multipart/form-data;\\s*boundary=[^\\s]+" | ||
} | ||
}, | ||
"bodyPatterns": [ | ||
{ | ||
"matches": ".+Content-Disposition: form-data; name=\"name\".*User 100.+" | ||
}, | ||
{ | ||
"matches": ".+Content-Disposition: form-data; name=\"email\".*user_100@email.com.+" | ||
} | ||
] | ||
}, | ||
"response": { | ||
"status": 201, | ||
"headers": { | ||
"Content-Type": "application/json; charset=utf-8" | ||
}, | ||
"jsonBody": { | ||
"id": 10, | ||
"name": "User 100", | ||
"email": "user_100@email.com" | ||
} | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
...ent-environment/wiremock/mappings/users-api/mappings/default/success/post-user-token.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"priority": 1, | ||
"request": { | ||
"method": "POST", | ||
"urlPattern": "/token", | ||
"bodyPatterns": [ | ||
{ | ||
"equalToJson": { | ||
"id": 10, | ||
"name": "User 10", | ||
"email": "user_10@email.com" | ||
} | ||
} | ||
] | ||
}, | ||
"response": { | ||
"status": 200, | ||
"headers": { | ||
"Content-Type": "application/json; charset=utf-8" | ||
}, | ||
"jsonBody": { | ||
"type": "Bearer", | ||
"token": "1A2B3C4D5E", | ||
"expiresIn": 123456789 | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
...ronment/wiremock/mappings/users-api/mappings/default/success/post-users-without-body.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"priority": 1, | ||
"request": { | ||
"method": "POST", | ||
"urlPattern": "/users-api/v1/users", | ||
"bodyPatterns": [ | ||
{ | ||
"equalToJson": { | ||
"name": "User 100", | ||
"email": "user_100@email.com" | ||
} | ||
} | ||
] | ||
}, | ||
"response": { | ||
"status": 201, | ||
"headers": { | ||
"Content-Length": "0" | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...ironment/wiremock/mappings/users-api/mappings/default/success/put-users-without-body.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"priority": 1, | ||
"request": { | ||
"method": "PUT", | ||
"urlPattern": "/users-api/v1/users/100", | ||
"bodyPatterns": [ | ||
{ | ||
"equalToJson": { | ||
"id": 100, | ||
"name": "User 100 edited", | ||
"email": "user_100@email.com" | ||
} | ||
} | ||
] | ||
}, | ||
"response": { | ||
"status": 204, | ||
"headers": { | ||
"Content-Length": "0" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
) | ||
|
||
// NullBoll for empty boolean field | ||
type NullBool sql.NullBool | ||
|
||
func (t *NullBool) Scan(value interface{}) error { | ||
var i sql.NullBool | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullBool{i.Bool, false} | ||
} else { | ||
*t = NullBool{i.Bool, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (n NullBool) Value() (driver.Value, error) { | ||
if !n.Valid { | ||
return nil, nil | ||
} | ||
|
||
return n.Bool, nil | ||
} | ||
|
||
func (t NullBool) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.Bool) | ||
} | ||
|
||
func (t *NullBool) UnmarshalJSON(data []byte) error { | ||
var ptr *bool | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr != nil { | ||
t.Valid = true | ||
t.Bool = *ptr | ||
} else { | ||
t.Valid = false | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullBool(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullBool | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, false, result.Bool) | ||
}) | ||
|
||
t.Run("Should error when scan with a invalid value", func(t *testing.T) { | ||
value := "invalid" | ||
|
||
var result NullBool | ||
err := result.Scan(value) | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, false, result.Bool) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
value := true | ||
|
||
var result NullBool | ||
err := result.Scan(value) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, value, result.Bool) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullBool{true, true} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.Bool, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullBool{false, false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullBool{false, false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullBool{true, true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "true", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullBool | ||
err := result.UnmarshalJSON([]byte("true")) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, true, result.Bool) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullBool | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, false, result.Bool) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
"time" | ||
) | ||
|
||
// NullDateTime for empty date/time field | ||
type NullDateTime sql.NullTime | ||
|
||
func (t *NullDateTime) Scan(value interface{}) error { | ||
var i sql.NullTime | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullDateTime{i.Time, false} | ||
} else { | ||
*t = NullDateTime{i.Time, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (n NullDateTime) Value() (driver.Value, error) { | ||
if !n.Valid { | ||
return nil, nil | ||
} | ||
|
||
return n.Time, nil | ||
} | ||
|
||
func (t NullDateTime) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.Time) | ||
} | ||
|
||
func (t *NullDateTime) UnmarshalJSON(data []byte) error { | ||
var ptr *time.Time | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr != nil { | ||
t.Valid = true | ||
t.Time = *ptr | ||
} else { | ||
t.Valid = false | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullDateTime(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullDateTime | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should error when scan with a invalid value", func(t *testing.T) { | ||
value := "invalid" | ||
|
||
var result NullDateTime | ||
err := result.Scan(value) | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
value := time.Now() | ||
|
||
var result NullDateTime | ||
err := result.Scan(value) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, value, result.Time) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullDateTime{time.Now(), true} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.Time, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullDateTime{time.Time(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)), false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullDateTime{time.Time(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)), false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullDateTime{time.Time(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)), true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "\"2022-01-01T00:00:00Z\"", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullDateTime | ||
err := result.UnmarshalJSON([]byte("\"2022-01-01T00:00:00Z\"")) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullDateTime | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
) | ||
|
||
// NullFloat64 for empty float field | ||
type NullFloat64 sql.NullFloat64 | ||
|
||
func (t *NullFloat64) Scan(value interface{}) error { | ||
var i sql.NullFloat64 | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullFloat64{i.Float64, false} | ||
} else { | ||
*t = NullFloat64{i.Float64, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (n NullFloat64) Value() (driver.Value, error) { | ||
if !n.Valid { | ||
return nil, nil | ||
} | ||
|
||
return n.Float64, nil | ||
} | ||
|
||
func (t NullFloat64) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.Float64) | ||
} | ||
|
||
func (t *NullFloat64) UnmarshalJSON(data []byte) error { | ||
var ptr *float64 | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr != nil { | ||
t.Valid = true | ||
t.Float64 = *ptr | ||
} else { | ||
t.Valid = false | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullFloat64(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullFloat64 | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, 0.00, result.Float64) | ||
}) | ||
|
||
t.Run("Should error when scan with a invalid value", func(t *testing.T) { | ||
value := "invalid" | ||
|
||
var result NullFloat64 | ||
err := result.Scan(value) | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, 0.00, result.Float64) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
value := 123.45 | ||
|
||
var result NullFloat64 | ||
err := result.Scan(value) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, value, result.Float64) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullFloat64{123.45, true} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.Float64, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullFloat64{0.00, false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullFloat64{0.00, false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullFloat64{123.45, true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "123.45", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullFloat64 | ||
err := result.UnmarshalJSON([]byte("123.45")) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, 123.45, result.Float64) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullFloat64 | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, 0.00, result.Float64) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
) | ||
|
||
// NullInt16 for empty int16 field | ||
type NullInt16 sql.NullInt16 | ||
|
||
func (t *NullInt16) Scan(value interface{}) error { | ||
var i sql.NullInt16 | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullInt16{i.Int16, false} | ||
} else { | ||
*t = NullInt16{i.Int16, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (n NullInt16) Value() (driver.Value, error) { | ||
if !n.Valid { | ||
return nil, nil | ||
} | ||
|
||
return n.Int16, nil | ||
} | ||
|
||
func (t NullInt16) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.Int16) | ||
} | ||
|
||
func (t *NullInt16) UnmarshalJSON(data []byte) error { | ||
var ptr *int16 | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr != nil { | ||
t.Valid = true | ||
t.Int16 = *ptr | ||
} else { | ||
t.Valid = false | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullInt16(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullInt16 | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int16(0), result.Int16) | ||
}) | ||
|
||
t.Run("Should error when scan with a invalid value", func(t *testing.T) { | ||
value := "invalid" | ||
|
||
var result NullInt16 | ||
err := result.Scan(value) | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int16(0), result.Int16) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
value := int16(123) | ||
|
||
var result NullInt16 | ||
err := result.Scan(value) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, value, result.Int16) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullInt16{12, true} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.Int16, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullInt16{0, false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullInt16{0, false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullInt16{123, true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "123", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullInt16 | ||
err := result.UnmarshalJSON([]byte("123")) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, int16(123), result.Int16) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullInt16 | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int16(0), result.Int16) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
) | ||
|
||
// NullInt32 for empty int32 field | ||
type NullInt32 sql.NullInt32 | ||
|
||
func (t *NullInt32) Scan(value interface{}) error { | ||
var i sql.NullInt32 | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullInt32{i.Int32, false} | ||
} else { | ||
*t = NullInt32{i.Int32, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (n NullInt32) Value() (driver.Value, error) { | ||
if !n.Valid { | ||
return nil, nil | ||
} | ||
|
||
return n.Int32, nil | ||
} | ||
|
||
func (t NullInt32) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.Int32) | ||
} | ||
|
||
func (t *NullInt32) UnmarshalJSON(data []byte) error { | ||
var ptr *int32 | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr != nil { | ||
t.Valid = true | ||
t.Int32 = *ptr | ||
} else { | ||
t.Valid = false | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullInt32(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullInt32 | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int32(0), result.Int32) | ||
}) | ||
|
||
t.Run("Should error when scan with a invalid value", func(t *testing.T) { | ||
value := "invalid" | ||
|
||
var result NullInt32 | ||
err := result.Scan(value) | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int32(0), result.Int32) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
value := int32(123) | ||
|
||
var result NullInt32 | ||
err := result.Scan(value) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, value, result.Int32) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullInt32{12, true} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.Int32, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullInt32{0, false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullInt32{0, false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullInt32{123, true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "123", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullInt32 | ||
err := result.UnmarshalJSON([]byte("123")) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, int32(123), result.Int32) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullInt32 | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int32(0), result.Int32) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
) | ||
|
||
// NullInt64 for empty int64 field | ||
type NullInt64 sql.NullInt64 | ||
|
||
func (t *NullInt64) Scan(value interface{}) error { | ||
var i sql.NullInt64 | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullInt64{i.Int64, false} | ||
} else { | ||
*t = NullInt64{i.Int64, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (n NullInt64) Value() (driver.Value, error) { | ||
if !n.Valid { | ||
return nil, nil | ||
} | ||
|
||
return n.Int64, nil | ||
} | ||
|
||
func (t NullInt64) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.Int64) | ||
} | ||
|
||
func (t *NullInt64) UnmarshalJSON(data []byte) error { | ||
var ptr *int64 | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr != nil { | ||
t.Valid = true | ||
t.Int64 = *ptr | ||
} else { | ||
t.Valid = false | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullInt64(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullInt64 | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int64(0), result.Int64) | ||
}) | ||
|
||
t.Run("Should error when scan with a invalid value", func(t *testing.T) { | ||
value := "invalid" | ||
|
||
var result NullInt64 | ||
err := result.Scan(value) | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int64(0), result.Int64) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
value := int64(123) | ||
|
||
var result NullInt64 | ||
err := result.Scan(value) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, value, result.Int64) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullInt64{123, true} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.Int64, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullInt64{0, false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullInt64{0, false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullInt64{123, true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "123", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullInt64 | ||
err := result.UnmarshalJSON([]byte("123")) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, int64(123), result.Int64) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullInt64 | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, int64(0), result.Int64) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
"time" | ||
) | ||
|
||
// NullIsoDate for empty date field | ||
type NullIsoDate sql.NullTime | ||
|
||
func (t *NullIsoDate) Scan(value interface{}) error { | ||
var i sql.NullTime | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullIsoDate{i.Time, false} | ||
} else { | ||
*t = NullIsoDate{i.Time, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (t NullIsoDate) Value() (driver.Value, error) { | ||
if !t.Valid { | ||
return nil, nil | ||
} | ||
|
||
return t.Time, nil | ||
} | ||
|
||
func (t NullIsoDate) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.Time.Format(time.DateOnly)) | ||
} | ||
|
||
func (t *NullIsoDate) UnmarshalJSON(data []byte) error { | ||
var ptr *string | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr == nil { | ||
t.Valid = false | ||
return nil | ||
} | ||
|
||
parsedDate, err := time.Parse(time.DateOnly, *ptr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
t.Time, t.Valid = parsedDate, true | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullIsoDate(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullIsoDate | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should error when scan with a invalid value", func(t *testing.T) { | ||
var result NullIsoDate | ||
err := result.Scan("invalid") | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
expected := time.Now() | ||
|
||
var result NullIsoDate | ||
err := result.Scan(expected) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, expected, result.Time) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullIsoDate{time.Now(), true} | ||
|
||
result, err := expected.Value() | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.Time, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullIsoDate{time.Time(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)), false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullIsoDate{time.Time(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)), false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
|
||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullIsoDate{time.Time(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)), true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "\"2022-01-01\"", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullIsoDate | ||
err := result.UnmarshalJSON([]byte("\"2022-01-01\"")) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullIsoDate | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
"time" | ||
) | ||
|
||
// NullIsoTime for empty time field | ||
type NullIsoTime sql.NullTime | ||
|
||
func (t *NullIsoTime) Scan(value interface{}) error { | ||
var i sql.NullTime | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullIsoTime{i.Time, false} | ||
} else { | ||
*t = NullIsoTime{i.Time, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (t NullIsoTime) Value() (driver.Value, error) { | ||
if !t.Valid { | ||
return nil, nil | ||
} | ||
|
||
return t.Time, nil | ||
} | ||
|
||
func (t NullIsoTime) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.Time.Format(time.TimeOnly)) | ||
} | ||
|
||
func (t *NullIsoTime) UnmarshalJSON(data []byte) error { | ||
var ptr *string | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr == nil { | ||
t.Valid = false | ||
return nil | ||
} | ||
|
||
parsedTime, err := time.Parse(time.TimeOnly, *ptr) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
t.Time, t.Valid = parsedTime, true | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullIsoTime(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullIsoTime | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should error when scan with a invalid value", func(t *testing.T) { | ||
var result NullIsoTime | ||
err := result.Scan("invalid") | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
expected := time.Now() | ||
|
||
var result NullIsoTime | ||
err := result.Scan(expected) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, expected, result.Time) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullIsoTime{time.Now(), true} | ||
|
||
result, err := expected.Value() | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.Time, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullIsoTime{time.Time(time.Date(1, time.January, 1, 10, 20, 30, 0, time.UTC)), false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullIsoTime{time.Time(time.Date(1, time.January, 1, 10, 20, 30, 0, time.UTC)), false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
|
||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullIsoTime{time.Time(time.Date(0, time.January, 1, 10, 20, 30, 0, time.UTC)), true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "\"10:20:30\"", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullIsoTime | ||
err := result.UnmarshalJSON([]byte("\"10:20:30\"")) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(0, time.January, 1, 10, 20, 30, 0, time.UTC)), result.Time) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullIsoTime | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
|
||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, time.Time(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)), result.Time) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package types | ||
|
||
import ( | ||
"database/sql" | ||
"database/sql/driver" | ||
"encoding/json" | ||
"reflect" | ||
) | ||
|
||
// NullString for empty string field | ||
type NullString sql.NullString | ||
|
||
func (t *NullString) Scan(value interface{}) error { | ||
var i sql.NullString | ||
if err := i.Scan(value); err != nil { | ||
return err | ||
} | ||
|
||
if reflect.TypeOf(value) == nil { | ||
*t = NullString{i.String, false} | ||
} else { | ||
*t = NullString{i.String, true} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (ns NullString) Value() (driver.Value, error) { | ||
if !ns.Valid { | ||
return nil, nil | ||
} | ||
|
||
return ns.String, nil | ||
} | ||
|
||
func (t NullString) MarshalJSON() ([]byte, error) { | ||
if !t.Valid { | ||
return json.Marshal(nil) | ||
} | ||
|
||
return json.Marshal(t.String) | ||
} | ||
|
||
func (t *NullString) UnmarshalJSON(data []byte) error { | ||
var ptr *string | ||
if err := json.Unmarshal(data, &ptr); err != nil { | ||
return err | ||
} | ||
|
||
if ptr != nil { | ||
t.Valid = true | ||
t.String = *ptr | ||
} else { | ||
t.Valid = false | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNullString(t *testing.T) { | ||
t.Run("Should error when scan with a nil value", func(t *testing.T) { | ||
var result NullString | ||
err := result.Scan(nil) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, "", result.String) | ||
}) | ||
|
||
t.Run("Should scan with a valid value", func(t *testing.T) { | ||
value := "string test" | ||
|
||
var result NullString | ||
err := result.Scan(value) | ||
|
||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, value, result.String) | ||
}) | ||
|
||
t.Run("Should get value with a valid value", func(t *testing.T) { | ||
expected := NullString{"string test", true} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, expected.String, result) | ||
}) | ||
|
||
t.Run("Should return nil when get value with a invalid value", func(t *testing.T) { | ||
expected := NullString{"", false} | ||
|
||
result, err := expected.Value() | ||
assert.Nil(t, err) | ||
assert.Nil(t, result) | ||
}) | ||
|
||
t.Run("Should return null when get json value with a invalid value", func(t *testing.T) { | ||
expected := NullString{"", false} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.Equal(t, "null", result) | ||
}) | ||
|
||
t.Run("Should get json value with a valid value", func(t *testing.T) { | ||
expected := NullString{"string test", true} | ||
|
||
json, err := expected.MarshalJSON() | ||
result := string(json) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, "\"string test\"", result) | ||
}) | ||
|
||
t.Run("Should get value with a valid json", func(t *testing.T) { | ||
var result NullString | ||
err := result.UnmarshalJSON([]byte("\"string test\"")) | ||
assert.Nil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, true, result.Valid) | ||
assert.Equal(t, "string test", result.String) | ||
}) | ||
|
||
t.Run("Should return error when get value with a invalid json", func(t *testing.T) { | ||
var result NullString | ||
err := result.UnmarshalJSON([]byte("invalid")) | ||
assert.NotNil(t, err) | ||
assert.NotNil(t, result) | ||
assert.Equal(t, false, result.Valid) | ||
assert.Equal(t, "", result.String) | ||
}) | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package types | ||
|
||
// SortDirection is the field sort direction | ||
type SortDirection string | ||
|
||
const ( | ||
ASC SortDirection = "ASC" | ||
DESC SortDirection = "DESC" | ||
) | ||
|
||
// Sort is the contract to sort an field | ||
type Sort struct { | ||
Direction SortDirection | ||
Field string | ||
} | ||
|
||
// NewSort returns a new Sort | ||
func NewSort(direction SortDirection, field string) Sort { | ||
return Sort{direction, field} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package types | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestNewSort(t *testing.T) { | ||
t.Run("Should return new sort", func(t *testing.T) { | ||
expected := Sort{Direction: ASC, Field: "name"} | ||
|
||
result := NewSort(expected.Direction, expected.Field) | ||
|
||
assert.NotNil(t, result) | ||
assert.EqualValues(t, expected, result) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package storage | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"io" | ||
"mime/multipart" | ||
"os" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/colibri-project-io/colibri-sdk-go/pkg/base/test" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
const ( | ||
BUCKET = "my-bucket" | ||
ID = "a0264d4f-cb3b-41e7-9632-2f8f86f6b28d" | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
test.InitializeTestLocalstack() | ||
|
||
Initialize() | ||
|
||
m.Run() | ||
} | ||
|
||
func TestStorage(t *testing.T) { | ||
ctx := context.Background() | ||
file, err := generateFile() | ||
|
||
t.Run("Should upload with success", func(t *testing.T) { | ||
expected := fmt.Sprintf("/%s/%s", BUCKET, ID) | ||
|
||
assert.NoError(t, err) | ||
|
||
result, err := UploadFile(ctx, BUCKET, ID, file) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, result) | ||
assert.True(t, strings.Contains(result, expected)) | ||
}) | ||
|
||
t.Run("Should download with success", func(t *testing.T) { | ||
_, err := UploadFile(ctx, BUCKET, ID, file) | ||
assert.NoError(t, err) | ||
result, err := DownloadFile(ctx, BUCKET, ID) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, result) | ||
}) | ||
|
||
t.Run("Should delete with success", func(t *testing.T) { | ||
err := DeleteFile(ctx, BUCKET, ID) | ||
assert.NoError(t, err) | ||
}) | ||
} | ||
|
||
func generateFile() (*multipart.File, error) { | ||
filePath := "./../../development-environment/storage/file.txt" | ||
fieldName := "file" | ||
body := new(bytes.Buffer) | ||
|
||
multipartWriter := multipart.NewWriter(body) | ||
defer multipartWriter.Close() | ||
|
||
file, err := os.Open(filePath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
fileWriter, err := multipartWriter.CreateFormFile(fieldName, filePath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if _, err := io.Copy(fileWriter, file); err != nil { | ||
return nil, err | ||
} | ||
|
||
filePtr := new(multipart.File) | ||
*filePtr = file | ||
return filePtr, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,328 +1,61 @@ | ||
package restclient | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
"github.com/colibri-project-io/colibri-sdk-go/pkg/base/logging" | ||
"github.com/colibri-project-io/colibri-sdk-go/pkg/base/monitoring" | ||
"github.com/mercari/go-circuitbreaker" | ||
"github.com/newrelic/go-agent/v3/newrelic" | ||
) | ||
|
||
const ( | ||
timeoutDefault uint = 1 | ||
restClientTransaction string = "REST-CLIENT" | ||
timeoutDefault uint = 1 | ||
restClientTransaction string = "REST-CLIENT" | ||
errServiceNotAvailable string = "service not available" | ||
errResponseWithEmptyBody string = "response returned with empty body and %d status code" | ||
) | ||
|
||
var ErrServiceNotAvailable = errors.New("service not available") | ||
|
||
type ResponseBody interface { | ||
any | ||
} | ||
|
||
type RequestData interface { | ||
any | ||
} | ||
|
||
type ResponseErrorData interface { | ||
any | ||
} | ||
|
||
// RestClient client http to encapsulate rest calls | ||
// RestClient struct with http client and circuit breaker | ||
// name: Represents the name of the REST client. | ||
// baseURL: Holds the base URL of the REST client. | ||
// retries: Specifies the number of retries for the REST client. | ||
// retrySleep: Indicates the time to sleep between retries. | ||
// client: Is a pointer to an http.Client for making HTTP requests. | ||
// cb: Points to a circuitbreaker.CircuitBreaker for managing circuit breaking behavior in the REST client. | ||
type RestClient struct { | ||
name string | ||
baseURL string | ||
client *http.Client | ||
cb *circuitbreaker.CircuitBreaker | ||
name string | ||
baseURL string | ||
retries uint8 | ||
retrySleep uint | ||
client *http.Client | ||
cb *circuitbreaker.CircuitBreaker | ||
} | ||
|
||
// NewRestClient create new client http with timeout configuration and New Relic transport configuration | ||
func NewRestClient(name string, baseURL string, timeout uint) *RestClient { | ||
if timeout == 0 { | ||
timeout = timeoutDefault | ||
// NewRestClient creates a new REST client based on the provided configuration. | ||
// | ||
// config: A pointer to RestClientConfig containing the configuration details for the REST client. | ||
// Returns a pointer to RestClient. | ||
func NewRestClient(config *RestClientConfig) *RestClient { | ||
if config == nil { | ||
return nil | ||
} | ||
|
||
if config.Timeout == 0 { | ||
config.Timeout = timeoutDefault | ||
} | ||
client := &http.Client{Timeout: time.Duration(timeout) * time.Second} | ||
client := &http.Client{Timeout: time.Duration(config.Timeout) * time.Second} | ||
client.Transport = newrelic.NewRoundTripper(client.Transport) | ||
return &RestClient{ | ||
name: name, | ||
baseURL: baseURL, | ||
name: config.Name, | ||
baseURL: config.BaseURL, | ||
client: client, | ||
cb: circuitbreaker.New( | ||
circuitbreaker.WithOpenTimeout(time.Second*10), | ||
circuitbreaker.WithTripFunc(circuitbreaker.NewTripFuncConsecutiveFailures(5)), | ||
circuitbreaker.WithOnStateChangeHookFn(func(oldState, newState circuitbreaker.State) { | ||
logging.Info("[%s] state changed: old [%s] -> new [%s]", name, string(oldState), string(newState)) | ||
logging.Info("[%s] state changed: old [%s] -> new [%s]", config.Name, string(oldState), string(newState)) | ||
}), | ||
), | ||
} | ||
} | ||
|
||
// Get call get request | ||
func Get[T ResponseBody](ctx context.Context, client *RestClient, path string, headers map[string]string) ResponseData[T] { | ||
resp, _ := callRequestWithoutBody[T, any](ctx, http.MethodGet, client, path, headers) | ||
return resp | ||
} | ||
|
||
// GetWithErrorData call get request and when error occurs decode err into ResponseErrorData | ||
func GetWithErrorData[T ResponseBody, E ResponseErrorData](ctx context.Context, client *RestClient, path string, headers map[string]string) (ResponseData[T], *E) { | ||
return callRequestWithoutBody[T, E](ctx, http.MethodGet, client, path, headers) | ||
} | ||
|
||
// Post call post request | ||
func Post[T ResponseBody, R RequestData](ctx context.Context, client *RestClient, path string, entityBody *R, headers map[string]string) ResponseData[T] { | ||
resp, _ := callRequest[T, any](ctx, http.MethodPost, client, path, entityBody, headers) | ||
return resp | ||
} | ||
|
||
// PostWithErrorData call post request and when error occurs decode err into ResponseErrorData | ||
func PostWithErrorData[T ResponseBody, E ResponseErrorData, R RequestData](ctx context.Context, client *RestClient, path string, entityBody *R, headers map[string]string) (ResponseData[T], *E) { | ||
return callRequest[T, E](ctx, http.MethodPost, client, path, entityBody, headers) | ||
} | ||
|
||
// PostBodyString call post request with string as body | ||
// to set Content-type pass is header map | ||
// | ||
// Ex.: to make a form url encoded request | ||
// `PostBodyString[MyReturnData](ctx, client, "/my-path", "body string for urlencoded...", map[string]string{"Content-Type": "application/x-www-form-urlencoded"}) | ||
func PostBodyString[T ResponseBody](ctx context.Context, client *RestClient, path string, bodyString string, headers map[string]string) ResponseData[T] { | ||
return callRequestBodyString[T](ctx, http.MethodPost, client, path, bodyString, headers) | ||
} | ||
|
||
// Put call put request | ||
func Put[T ResponseBody, R RequestData](ctx context.Context, client *RestClient, path string, entityBody *R, headers map[string]string) ResponseData[T] { | ||
resp, _ := callRequest[T, any](ctx, http.MethodPut, client, path, entityBody, headers) | ||
return resp | ||
} | ||
|
||
// PutWithErrorData call put request and when error occurs decode err into ResponseErrorData | ||
func PutWithErrorData[T ResponseBody, E ResponseErrorData, R RequestData](ctx context.Context, client *RestClient, path string, entityBody *R, headers map[string]string) (ResponseData[T], *E) { | ||
return callRequest[T, E](ctx, http.MethodPut, client, path, entityBody, headers) | ||
} | ||
|
||
// Patch call patch request | ||
func Patch[T ResponseBody, R RequestData](ctx context.Context, client *RestClient, path string, entityBody *R, headers map[string]string) ResponseData[T] { | ||
resp, _ := callRequest[T, any](ctx, http.MethodPatch, client, path, entityBody, headers) | ||
return resp | ||
} | ||
|
||
// PatchWithErrorData call patch request and when error occurs decode err into ResponseErrorData | ||
func PatchWithErrorData[T ResponseBody, E ResponseErrorData, R RequestData](ctx context.Context, client *RestClient, path string, entityBody *R, headers map[string]string) (ResponseData[T], *E) { | ||
return callRequest[T, E](ctx, http.MethodPatch, client, path, entityBody, headers) | ||
} | ||
|
||
// Delete call delete request | ||
func Delete[T ResponseBody](ctx context.Context, client *RestClient, path string, headers map[string]string) ResponseData[T] { | ||
resp, _ := callRequestWithoutBody[T, any](ctx, http.MethodDelete, client, path, headers) | ||
return resp | ||
} | ||
|
||
// DeleteWithErrorData call delete request and when error occurs decode err into ResponseErrorData | ||
func DeleteWithErrorData[T ResponseBody, E ResponseErrorData](ctx context.Context, client *RestClient, path string, headers map[string]string) (ResponseData[T], *E) { | ||
return callRequestWithoutBody[T, E](ctx, http.MethodDelete, client, path, headers) | ||
} | ||
|
||
func callRequestWithoutBody[T ResponseBody, E ResponseErrorData](ctx context.Context, method string, client *RestClient, path string, headers map[string]string) (_ ResponseData[T], _ *E) { | ||
if !client.cb.Ready() { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, ErrServiceNotAvailable), nil | ||
} | ||
|
||
var err error | ||
defer func() { | ||
err = client.cb.Done(ctx, err) | ||
}() | ||
|
||
if monitoring.GetTransactionInContext(ctx) != nil { | ||
segment := createSegment(ctx, method, path) | ||
defer monitoring.EndTransactionSegment(segment) | ||
} | ||
|
||
url := fmt.Sprintf("%s%s", client.baseURL, path) | ||
req, err := http.NewRequest(method, url, nil) | ||
if err != nil { | ||
fErr := fmt.Errorf("could not create new http request %s: %w", url, err) | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, fErr), nil | ||
} | ||
|
||
addHeaders(req, headers) | ||
resp, err := client.client.Do(req) | ||
if err != nil { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, err), nil | ||
} | ||
|
||
statusOK := resp.StatusCode >= 200 && resp.StatusCode < 300 | ||
if !statusOK { | ||
respBErr, err := processErrorResponse[E](resp) | ||
return newResponseData[T](resp.StatusCode, nil, resp.Header, err), respBErr | ||
} | ||
|
||
r, err := decodeResponse[T](resp) | ||
return newResponseData(resp.StatusCode, r, resp.Header, err), nil | ||
} | ||
|
||
func callRequest[T ResponseBody, E ResponseErrorData, R RequestData](ctx context.Context, method string, client *RestClient, path string, entityBody *R, headers map[string]string) (_ ResponseData[T], _ *E) { | ||
if !client.cb.Ready() { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, ErrServiceNotAvailable), nil | ||
} | ||
|
||
var err error | ||
defer func() { | ||
err = client.cb.Done(ctx, err) | ||
}() | ||
|
||
if monitoring.GetTransactionInContext(ctx) != nil { | ||
segment := createSegment(ctx, method, path) | ||
defer monitoring.EndTransactionSegment(segment) | ||
} | ||
|
||
url := fmt.Sprintf("%s%s", client.baseURL, path) | ||
bytesBody, err := makeBytesBody(entityBody) | ||
if err != nil { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, err), nil | ||
} | ||
req, err := http.NewRequest(method, url, bytesBody) | ||
|
||
addHeaders(req, headers) | ||
resp, err := client.client.Do(req) | ||
if err != nil { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, err), nil | ||
} | ||
|
||
statusOK := resp.StatusCode >= 200 && resp.StatusCode < 300 | ||
if !statusOK { | ||
errBody, err := processErrorResponse[E](resp) | ||
return newResponseData[T](resp.StatusCode, nil, resp.Header, err), errBody | ||
} | ||
|
||
r, err := decodeResponse[T](resp) | ||
return newResponseData(resp.StatusCode, r, resp.Header, err), nil | ||
} | ||
|
||
func callRequestBodyString[T ResponseBody](ctx context.Context, method string, client *RestClient, path string, bodyString string, headers map[string]string) ResponseData[T] { | ||
if !client.cb.Ready() { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, ErrServiceNotAvailable) | ||
} | ||
|
||
var err error | ||
defer func() { | ||
err = client.cb.Done(ctx, err) | ||
}() | ||
|
||
if monitoring.GetTransactionInContext(ctx) != nil { | ||
segment := createSegment(ctx, method, path) | ||
defer monitoring.EndTransactionSegment(segment) | ||
} | ||
|
||
dataIOReader := strings.NewReader(bodyString) | ||
|
||
url := fmt.Sprintf("%s%s", client.baseURL, path) | ||
|
||
req, err := http.NewRequest(method, url, dataIOReader) | ||
if err != nil { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, fmt.Errorf("could not create new request: %w", err)) | ||
} | ||
|
||
addHeaders(req, headers) | ||
|
||
resp, err := client.client.Do(req) | ||
if err != nil { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, err) | ||
} | ||
defer func() { | ||
if err = resp.Body.Close(); err != nil { | ||
logging.Error("could not close response body: %v", err) | ||
} | ||
}() | ||
bodyText, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return newResponseData[T](http.StatusInternalServerError, nil, nil, err) | ||
} | ||
|
||
var r T | ||
if err := json.Unmarshal(bodyText, &r); err != nil { | ||
return newResponseData[T](resp.StatusCode, nil, resp.Header, err) | ||
} | ||
return newResponseData(resp.StatusCode, &r, resp.Header, err) | ||
} | ||
|
||
func createSegment(ctx context.Context, method string, path string) interface{} { | ||
segment := monitoring.StartTransactionSegment(ctx, restClientTransaction, map[string]string{ | ||
"method": method, | ||
"path": path, | ||
}) | ||
return segment | ||
} | ||
|
||
func processErrorResponse[E ResponseErrorData](resp *http.Response) (*E, error) { | ||
respErr, derr := decodeResponseErrorData[E](resp) | ||
if respErr != nil { | ||
return respErr, fmt.Errorf("%d statusCode", resp.StatusCode) | ||
} else if derr != nil { | ||
return nil, fmt.Errorf("%d statusCode. Body decoder Error: %w", resp.StatusCode, derr) | ||
} | ||
return nil, fmt.Errorf("%d statusCode", resp.StatusCode) | ||
} | ||
|
||
func makeBytesBody(entityBody any) (*bytes.Buffer, error) { | ||
if entityBody == nil { | ||
return nil, nil | ||
} | ||
requestBody, err := json.Marshal(entityBody) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not marshal entity body: %w", err) | ||
} | ||
return bytes.NewBuffer(requestBody), nil | ||
} | ||
|
||
func decodeResponse[T ResponseBody](resp *http.Response) (*T, error) { | ||
defer func() { | ||
closeBody(resp) | ||
}() | ||
if resp.StatusCode == http.StatusNoContent { | ||
return nil, nil | ||
} | ||
var responseModel T | ||
err := json.NewDecoder(resp.Body).Decode(&responseModel) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not decode response: %w", err) | ||
} | ||
return &responseModel, nil | ||
} | ||
|
||
func decodeResponseErrorData[E ResponseErrorData](resp *http.Response) (*E, error) { | ||
defer func() { | ||
closeBody(resp) | ||
}() | ||
var responseModel E | ||
err := json.NewDecoder(resp.Body).Decode(&responseModel) | ||
switch { | ||
case err == io.EOF: | ||
return nil, nil | ||
case err != nil: | ||
return nil, fmt.Errorf("could not decode response: %w", err) | ||
} | ||
return &responseModel, nil | ||
} | ||
|
||
func addHeaders(req *http.Request, headers map[string]string) { | ||
for k, v := range headers { | ||
req.Header.Set(k, v) | ||
} | ||
} | ||
|
||
func closeBody(resp *http.Response) { | ||
if resp == nil { | ||
return | ||
} | ||
|
||
if err := resp.Body.Close(); err != nil { | ||
logging.Error("error when close response body: %v\n", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package restclient | ||
|
||
type RestClientConfig struct { | ||
Name string | ||
BaseURL string | ||
Timeout uint | ||
Retries uint8 | ||
RetrySleepInSeconds uint | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package restclient | ||
|
||
import "io" | ||
|
||
type MultipartFile struct { | ||
FileName string | ||
File io.Reader | ||
ContentType string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,304 @@ | ||
package restclient | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"mime/multipart" | ||
"net/http" | ||
"net/textproto" | ||
"reflect" | ||
"strings" | ||
"time" | ||
|
||
"github.com/colibri-project-io/colibri-sdk-go/pkg/base/logging" | ||
"github.com/colibri-project-io/colibri-sdk-go/pkg/database/cacheDB" | ||
) | ||
|
||
const ( | ||
request_ctx_is_empty string = "context is empty" | ||
request_client_is_empty string = "client is empty" | ||
request_method_is_empty string = "http method is empty" | ||
) | ||
|
||
// Request struct for http requests | ||
type Request[T ResponseSuccessData, E ResponseErrorData] struct { | ||
Ctx context.Context | ||
Client *RestClient | ||
HttpMethod string // use http.methodXXX | ||
|
||
Cache *cacheDB.Cache[T] | ||
Path string | ||
Headers map[string]string | ||
Body any | ||
MultipartFields map[string]any | ||
writer *multipart.Writer | ||
} | ||
|
||
// Call executes the HTTP request and handles retries and caching. | ||
// | ||
// It validates the request, checks cache, executes the request with retries, handles success, and logs errors. | ||
// Returns the response data. | ||
func (req Request[T, E]) Call() (response ResponseData[T, E]) { | ||
if err := req.validate(); err != nil { | ||
return newResponseData[T, E](http.StatusInternalServerError, nil, nil, nil, err) | ||
} | ||
|
||
if req.hasCache() { | ||
data, _ := req.Cache.One(req.Ctx) | ||
if data != nil { | ||
return newResponseData[T, E](http.StatusNotModified, nil, data, nil, nil) | ||
} | ||
} | ||
|
||
for execution := uint8(0); execution <= req.Client.retries; execution++ { | ||
response = req.execute() | ||
if response.HasSuccess() { | ||
if req.hasCache() { | ||
req.Cache.Set(req.Ctx, response.SuccessBody()) | ||
} | ||
break | ||
} | ||
|
||
time.Sleep(req.getSleepDuration()) | ||
if req.Client.retries != 0 { | ||
logging.Warn("[%dx] call to the url '%s'. status code = %d | general error: %v | response error: %v", execution+1, req.getUrl(), response.StatusCode(), response.Error(), response.ErrorBody()) | ||
} | ||
} | ||
|
||
return | ||
} | ||
|
||
// validate checks if the Request struct fields are valid and returns an error if any are missing. | ||
// | ||
// No parameters. | ||
// Returns an error. | ||
func (rc *Request[T, E]) validate() error { | ||
if rc.Ctx == nil { | ||
return errors.New(request_ctx_is_empty) | ||
} | ||
|
||
if rc.Client == nil { | ||
return errors.New(request_client_is_empty) | ||
} | ||
|
||
if rc.HttpMethod == "" { | ||
return errors.New(request_method_is_empty) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// hasCache checks if the Request struct has a cache attached. | ||
// | ||
// No parameters. | ||
// Returns a boolean. | ||
func (rc *Request[T, E]) hasCache() bool { | ||
return rc.Cache != nil | ||
} | ||
|
||
// getUrl returns the full URL by combining the base URL with the path. | ||
// | ||
// No parameters. | ||
// Returns a string. | ||
func (rc *Request[T, E]) getUrl() string { | ||
return fmt.Sprintf("%s%s", rc.Client.baseURL, rc.Path) | ||
} | ||
|
||
// getSleepDuration returns the sleep duration as a time.Duration. | ||
// | ||
// No parameters. | ||
// Returns a time.Duration. | ||
func (rc *Request[T, E]) getSleepDuration() time.Duration { | ||
return time.Duration(rc.Client.retrySleep) * time.Second | ||
} | ||
|
||
// getBytesBody returns the body as an io.Reader and an error. | ||
// | ||
// No parameters. | ||
// Returns an io.Reader and an error. | ||
func (rc *Request[T, E]) getBytesBody() (io.Reader, error) { | ||
if rc.Body == nil && len(rc.MultipartFields) == 0 { | ||
return nil, nil | ||
} | ||
|
||
if len(rc.MultipartFields) > 0 { | ||
return rc.processMultipartFields() | ||
} | ||
|
||
if reflect.ValueOf(rc.Body).Kind() == reflect.String { | ||
return strings.NewReader(fmt.Sprintf("%v", rc.Body)), nil | ||
} | ||
|
||
requestBody, err := json.Marshal(rc.Body) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not marshal body: %w", err) | ||
} | ||
|
||
return bytes.NewBuffer(requestBody), nil | ||
} | ||
|
||
// processMultipartFields processes the multipart fields and returns the io.Reader and an error. | ||
// | ||
// No parameters. | ||
// Returns an io.Reader and an error. | ||
func (rc *Request[T, E]) processMultipartFields() (io.Reader, error) { | ||
body := &bytes.Buffer{} | ||
rc.writer = multipart.NewWriter(body) | ||
for fieldName, contentField := range rc.MultipartFields { | ||
if err := rc.processField(fieldName, contentField); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
if err := rc.writer.Close(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return body, nil | ||
} | ||
|
||
// processField processes the field based on its type and performs the necessary actions accordingly. | ||
// | ||
// fieldName: the name of the field being processed (string). | ||
// contentField: the content of the field being processed (interface{}). | ||
// Returns an error if any issues occur during processing. | ||
func (rc *Request[T, E]) processField(fieldName string, contentField interface{}) error { | ||
if file, ok := contentField.(MultipartFile); ok { | ||
part, err := rc.createFilePart(fieldName, file) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if _, err := io.Copy(part, file.File); err != nil { | ||
return err | ||
} | ||
} else if str, ok := contentField.(string); ok { | ||
if err := rc.writer.WriteField(fieldName, str); err != nil { | ||
return err | ||
} | ||
} else { | ||
return errors.New("error while sending the multipart/form-data: data type not allowed") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// createFilePart creates a file part for the request based on the field name and the file. | ||
// | ||
// fieldName: the name of the field for the file part (string). | ||
// file: the file to be processed (MultipartFile). | ||
// Returns an io.Writer and an error. | ||
func (rc *Request[T, E]) createFilePart(fieldName string, file MultipartFile) (io.Writer, error) { | ||
if file.ContentType != "" { | ||
return rc.createCustomContentTypeFormFile(fieldName, file) | ||
} | ||
|
||
return rc.writer.CreateFormFile(fieldName, file.FileName) | ||
} | ||
|
||
// createCustomContentTypeFormFile creates a custom content type form file for the request. | ||
// | ||
// fieldName: the name of the field for the file part (string). | ||
// file: the file to be processed (MultipartFile). | ||
// Returns an io.Writer and an error. | ||
func (rc *Request[T, E]) createCustomContentTypeFormFile(fieldName string, file MultipartFile) (io.Writer, error) { | ||
h := make(textproto.MIMEHeader) | ||
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, fieldName, file.FileName)) | ||
h.Set("Content-Type", file.ContentType) | ||
|
||
return rc.writer.CreatePart(h) | ||
} | ||
|
||
// addHeadersInRequest sets the headers in the HTTP request based on the headers map in the Request struct. | ||
// | ||
// req: the HTTP request to add headers to (*http.Request). | ||
// Return type: void | ||
func (rc *Request[T, E]) addHeadersInRequest(req *http.Request) { | ||
for key, value := range rc.Headers { | ||
req.Header.Set(key, value) | ||
} | ||
} | ||
|
||
// execute executes the HTTP request and processes the response data. | ||
// | ||
// No parameters. | ||
// Returns a ResponseData containing the response data and any errors. | ||
func (req *Request[T, E]) execute() (response ResponseData[T, E]) { | ||
if !req.Client.cb.Ready() { | ||
return newResponseData[T, E](http.StatusInternalServerError, nil, nil, nil, errors.New(errServiceNotAvailable)) | ||
} | ||
|
||
var err error | ||
defer func() { | ||
err = req.Client.cb.Done(req.Ctx, err) | ||
}() | ||
|
||
bytesBody, err := req.getBytesBody() | ||
if err != nil { | ||
return newResponseData[T, E](http.StatusInternalServerError, nil, nil, nil, err) | ||
} | ||
|
||
request, err := http.NewRequestWithContext(req.Ctx, string(req.HttpMethod), req.getUrl(), bytesBody) | ||
req.addHeadersInRequest(request) | ||
if len(req.MultipartFields) > 0 { | ||
request.Header.Add("Content-Type", req.writer.FormDataContentType()) | ||
} | ||
|
||
resp, err := req.Client.client.Do(request) | ||
if err != nil { | ||
return newResponseData[T, E](http.StatusInternalServerError, nil, nil, nil, err) | ||
} | ||
|
||
defer resp.Body.Close() | ||
if !statusCodeIsOK(resp) { | ||
errBody, err := processErrorResponse[E](resp) | ||
return newResponseData[T, E](resp.StatusCode, resp.Header, nil, errBody, err) | ||
} | ||
|
||
decodedResponse, err := decodeResponse[T](resp) | ||
return newResponseData[T, E](resp.StatusCode, resp.Header, decodedResponse, nil, err) | ||
} | ||
|
||
func statusCodeIsOK(resp *http.Response) bool { | ||
return resp.StatusCode >= 200 && resp.StatusCode < 300 | ||
} | ||
|
||
// processErrorResponse decodes the response error data and handles different error scenarios. | ||
// | ||
// Takes an http.Response as input. | ||
// Returns a pointer to the response error data and an error. | ||
func processErrorResponse[E ResponseErrorData](resp *http.Response) (*E, error) { | ||
respErr, derr := decodeResponse[E](resp) | ||
if respErr != nil { | ||
return respErr, fmt.Errorf("error body decoded with %d status code", resp.StatusCode) | ||
} else if derr != nil { | ||
return nil, fmt.Errorf("%d status code. body decoder error: %w", resp.StatusCode, derr) | ||
} | ||
|
||
return nil, fmt.Errorf(errResponseWithEmptyBody, resp.StatusCode) | ||
} | ||
|
||
// decodeResponse decodes the response body from an http response into a generic type T. | ||
// | ||
// resp: the http response object to decode | ||
// Returns a pointer to the decoded response model of type T and an error | ||
func decodeResponse[T any](resp *http.Response) (*T, error) { | ||
if resp.ContentLength == 0 { | ||
return nil, nil | ||
} | ||
|
||
var responseModel T | ||
err := json.NewDecoder(resp.Body).Decode(&responseModel) | ||
switch { | ||
case err == io.EOF: | ||
return nil, nil | ||
case err != nil: | ||
return nil, fmt.Errorf("could not decode response: %w", err) | ||
} | ||
|
||
return &responseModel, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package restclient | ||
|
||
// RequestData interface to encapsulate request data | ||
type RequestData interface { | ||
any | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package restclient | ||
|
||
// ResponseErrorData interface to encapsulate response error data | ||
type ResponseErrorData interface { | ||
any | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package restclient | ||
|
||
// ResponseSuccessData interface to encapsulate response success data | ||
type ResponseSuccessData interface { | ||
any | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.