From 54f6351cafe4269ed2a3c28dcf317fe90912575b Mon Sep 17 00:00:00 2001 From: Rafael Dantas <36964299+dantasrafael@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:51:10 -0300 Subject: [PATCH] chore: general update (#87) --- .../database/migrations/000001_schema.up.sql | 2 +- development-environment/docker-compose.yaml | 13 +- ...localstack-init.sh => start-localstack.sh} | 7 +- development-environment/rest/resp.json | 2 +- development-environment/storage/file.txt | 2 +- .../success/patch-users-without-body.json | 17 + ...rt-form-with-custom-content-type-file.json | 23 + ...t-form-with-default-content-type-file.json | 23 + .../post-multipart-form-with-fields.json | 31 + .../default/success/post-user-token.json | 27 + .../success/post-users-without-body.json | 21 + .../success/put-users-without-body.json | 22 + go.mod | 48 +- go.sum | 144 ++-- pkg/base/test/localstack_container.go | 30 +- pkg/base/test/postgres_container.go | 4 +- pkg/base/test/redis_container.go | 3 +- pkg/base/test/wiremock_container.go | 23 +- pkg/base/types/json_test.go | 4 +- pkg/base/types/null_bool.go | 58 ++ pkg/base/types/null_bool_test.go | 97 +++ pkg/base/types/null_date_time.go | 59 ++ pkg/base/types/null_date_time_test.go | 98 +++ pkg/base/types/null_float_64.go | 58 ++ pkg/base/types/null_float_64_test.go | 97 +++ pkg/base/types/null_int_16.go | 58 ++ pkg/base/types/null_int_16_test.go | 97 +++ pkg/base/types/null_int_32.go | 58 ++ pkg/base/types/null_int_32_test.go | 97 +++ pkg/base/types/null_int_64.go | 58 ++ pkg/base/types/null_int_64_test.go | 97 +++ pkg/base/types/null_iso_date.go | 63 ++ pkg/base/types/null_iso_date_test.go | 101 +++ pkg/base/types/null_iso_time.go | 63 ++ pkg/base/types/null_iso_time_test.go | 101 +++ pkg/base/types/null_string.go | 58 ++ pkg/base/types/null_string_test.go | 85 ++ pkg/base/types/null_types.go | 467 ---------- pkg/base/types/null_types_test.go | 812 ------------------ pkg/base/types/page.go | 19 - pkg/base/types/page_test.go | 64 +- pkg/base/types/sort.go | 20 + pkg/base/types/sort_test.go | 18 + pkg/storage/aws.go | 51 +- pkg/storage/gcp.go | 47 + pkg/storage/storage.go | 40 +- pkg/storage/storage_test.go | 84 ++ pkg/web/restclient/client.go | 331 +------ pkg/web/restclient/client_config.go | 9 + pkg/web/restclient/client_test.go | 551 +++++++++--- pkg/web/restclient/multipart_file.go | 9 + pkg/web/restclient/request.go | 304 +++++++ pkg/web/restclient/request_data.go | 6 + pkg/web/restclient/response_data.go | 144 +++- pkg/web/restclient/response_error_data.go | 6 + pkg/web/restclient/response_success_data.go | 6 + pkg/web/restserver/route.go | 4 +- pkg/web/restserver/server_test.go | 489 ++++++++--- 58 files changed, 3324 insertions(+), 2006 deletions(-) rename development-environment/localstack/{localstack-init.sh => start-localstack.sh} (73%) mode change 100644 => 100755 create mode 100644 development-environment/wiremock/mappings/users-api/mappings/default/success/patch-users-without-body.json create mode 100644 development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-custom-content-type-file.json create mode 100644 development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-default-content-type-file.json create mode 100644 development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-fields.json create mode 100644 development-environment/wiremock/mappings/users-api/mappings/default/success/post-user-token.json create mode 100644 development-environment/wiremock/mappings/users-api/mappings/default/success/post-users-without-body.json create mode 100644 development-environment/wiremock/mappings/users-api/mappings/default/success/put-users-without-body.json create mode 100644 pkg/base/types/null_bool.go create mode 100644 pkg/base/types/null_bool_test.go create mode 100644 pkg/base/types/null_date_time.go create mode 100644 pkg/base/types/null_date_time_test.go create mode 100644 pkg/base/types/null_float_64.go create mode 100644 pkg/base/types/null_float_64_test.go create mode 100644 pkg/base/types/null_int_16.go create mode 100644 pkg/base/types/null_int_16_test.go create mode 100644 pkg/base/types/null_int_32.go create mode 100644 pkg/base/types/null_int_32_test.go create mode 100644 pkg/base/types/null_int_64.go create mode 100644 pkg/base/types/null_int_64_test.go create mode 100644 pkg/base/types/null_iso_date.go create mode 100644 pkg/base/types/null_iso_date_test.go create mode 100644 pkg/base/types/null_iso_time.go create mode 100644 pkg/base/types/null_iso_time_test.go create mode 100644 pkg/base/types/null_string.go create mode 100644 pkg/base/types/null_string_test.go delete mode 100644 pkg/base/types/null_types.go delete mode 100644 pkg/base/types/null_types_test.go create mode 100644 pkg/base/types/sort.go create mode 100644 pkg/base/types/sort_test.go create mode 100644 pkg/storage/storage_test.go create mode 100644 pkg/web/restclient/client_config.go create mode 100644 pkg/web/restclient/multipart_file.go create mode 100644 pkg/web/restclient/request.go create mode 100644 pkg/web/restclient/request_data.go create mode 100644 pkg/web/restclient/response_error_data.go create mode 100644 pkg/web/restclient/response_success_data.go diff --git a/development-environment/database/migrations/000001_schema.up.sql b/development-environment/database/migrations/000001_schema.up.sql index 1f404ed..45ddf8a 100644 --- a/development-environment/database/migrations/000001_schema.up.sql +++ b/development-environment/database/migrations/000001_schema.up.sql @@ -16,4 +16,4 @@ CREATE TABLE IF NOT EXISTS dog id SERIAL PRIMARY KEY, name TEXT NOT NULL, characteristics TEXT[] -); \ No newline at end of file +); diff --git a/development-environment/docker-compose.yaml b/development-environment/docker-compose.yaml index 9a8eb09..9bd6194 100644 --- a/development-environment/docker-compose.yaml +++ b/development-environment/docker-compose.yaml @@ -2,7 +2,7 @@ version: "3" services: wiremock: - image: wiremock/wiremock:2.32.0-alpine + image: wiremock/wiremock:3.3.1-alpine command: "--local-response-templating" ports: - "5050:8080" @@ -12,19 +12,18 @@ services: - dev localstack: - image: localstack/localstack:1.4 + image: localstack/localstack:3.1 ports: - "127.0.0.1:4510-4559:4510-4559" # external service port range - "127.0.0.1:4566:4566" # LocalStack Edge Proxy environment: - - DEBUG=${DEBUG-} - - SERVICES=sns,sqs,s3,dynamodb - - DATA_DIR=${DATA_DIR-} - - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR-} - - HOST_TMP_FOLDER=${TMPDIR:-/tmp/}localstack + - DEBUG=${DEBUG:-0} + - SERVICES=sns,sqs,s3 - DOCKER_HOST=unix:///var/run/docker.sock volumes: + - "${TMPDIR:-/tmp}/localstack:/var/lib/localstack" - "/var/run/docker.sock:/var/run/docker.sock" + - ./localstack/start-localstack.sh:/etc/localstack/init/ready.d/start-localstack.sh networks: - dev diff --git a/development-environment/localstack/localstack-init.sh b/development-environment/localstack/start-localstack.sh old mode 100644 new mode 100755 similarity index 73% rename from development-environment/localstack/localstack-init.sh rename to development-environment/localstack/start-localstack.sh index 2a51e2f..a08f8ba --- a/development-environment/localstack/localstack-init.sh +++ b/development-environment/localstack/start-localstack.sh @@ -4,16 +4,15 @@ awslocal sns create-topic --name COLIBRI_PROJECT_USER_CREATE awslocal sqs create-queue --queue-name COLIBRI_PROJECT_USER_CREATE_APP_CONSUMER awslocal sns subscribe --topic-arn arn:aws:sns:us-east-1:000000000000:COLIBRI_PROJECT_USER_CREATE \ --protocol sqs \ - --notification-endpoint arn:aws:sqs:us-east-1:queue:COLIBRI_PROJECT_USER_CREATE_APP_CONSUMER - + --notification-endpoint arn:aws:sqs:us-east-1:000000000000:COLIBRI_PROJECT_USER_CREATE_APP_CONSUMER awslocal sns create-topic --name COLIBRI_PROJECT_FAIL_USER_CREATE awslocal sqs create-queue --queue-name COLIBRI_PROJECT_FAIL_USER_CREATE_APP_CONSUMER awslocal sqs create-queue --queue-name COLIBRI_PROJECT_FAIL_USER_CREATE_APP_CONSUMER_DLQ awslocal sns subscribe --topic-arn arn:aws:sns:us-east-1:000000000000:COLIBRI_PROJECT_FAIL_USER_CREATE \ --protocol sqs \ - --notification-endpoint arn:aws:sqs:us-east-1:queue:COLIBRI_PROJECT_FAIL_USER_CREATE_APP_CONSUMER + --notification-endpoint arn:aws:sqs:us-east-1:000000000000:COLIBRI_PROJECT_FAIL_USER_CREATE_APP_CONSUMER awslocal s3api create-bucket --bucket my-bucket --acl public-read -echo "localstack emulator started" \ No newline at end of file +echo "localstack emulator started" diff --git a/development-environment/rest/resp.json b/development-environment/rest/resp.json index 98f0fcd..1e532a4 100644 --- a/development-environment/rest/resp.json +++ b/development-environment/rest/resp.json @@ -1,3 +1,3 @@ { "msg": "test file" -} \ No newline at end of file +} diff --git a/development-environment/storage/file.txt b/development-environment/storage/file.txt index 84362ca..524acff 100644 --- a/development-environment/storage/file.txt +++ b/development-environment/storage/file.txt @@ -1 +1 @@ -Test file \ No newline at end of file +Test file diff --git a/development-environment/wiremock/mappings/users-api/mappings/default/success/patch-users-without-body.json b/development-environment/wiremock/mappings/users-api/mappings/default/success/patch-users-without-body.json new file mode 100644 index 0000000..1ab8f93 --- /dev/null +++ b/development-environment/wiremock/mappings/users-api/mappings/default/success/patch-users-without-body.json @@ -0,0 +1,17 @@ +{ + "priority": 1, + "request": { + "method": "PATCH", + "urlPattern": "/users-api/v1/users/100", + "bodyPatterns": [ + { + "equalToJson": { + "name": "User 100 edited" + } + } + ] + }, + "response": { + "status": 204 + } +} diff --git a/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-custom-content-type-file.json b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-custom-content-type-file.json new file mode 100644 index 0000000..b80cf4b --- /dev/null +++ b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-custom-content-type-file.json @@ -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" + } + } +} diff --git a/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-default-content-type-file.json b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-default-content-type-file.json new file mode 100644 index 0000000..250feb5 --- /dev/null +++ b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-default-content-type-file.json @@ -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" + } + } +} diff --git a/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-fields.json b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-fields.json new file mode 100644 index 0000000..25de5b0 --- /dev/null +++ b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-multipart-form-with-fields.json @@ -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" + } + } +} diff --git a/development-environment/wiremock/mappings/users-api/mappings/default/success/post-user-token.json b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-user-token.json new file mode 100644 index 0000000..6bdb657 --- /dev/null +++ b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-user-token.json @@ -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 + } + } +} diff --git a/development-environment/wiremock/mappings/users-api/mappings/default/success/post-users-without-body.json b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-users-without-body.json new file mode 100644 index 0000000..0783909 --- /dev/null +++ b/development-environment/wiremock/mappings/users-api/mappings/default/success/post-users-without-body.json @@ -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" + } + } +} diff --git a/development-environment/wiremock/mappings/users-api/mappings/default/success/put-users-without-body.json b/development-environment/wiremock/mappings/users-api/mappings/default/success/put-users-without-body.json new file mode 100644 index 0000000..30164df --- /dev/null +++ b/development-environment/wiremock/mappings/users-api/mappings/default/success/put-users-without-body.json @@ -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" + } + } +} diff --git a/go.mod b/go.mod index 2f0064a..3b1d539 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( cloud.google.com/go/storage v1.33.0 firebase.google.com/go v3.13.0+incompatible github.com/aws/aws-sdk-go v1.45.28 + github.com/docker/docker v24.0.5+incompatible github.com/docker/go-connections v0.4.0 github.com/go-playground/form/v4 v4.2.1 github.com/go-playground/validator/v10 v10.15.5 @@ -39,31 +40,28 @@ require ( require ( cloud.google.com/go v0.110.8 // indirect - cloud.google.com/go/compute v1.23.1 // indirect + cloud.google.com/go/compute v1.23.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/firestore v1.13.0 // indirect - cloud.google.com/go/iam v1.1.3 // indirect - cloud.google.com/go/longrunning v0.5.2 // indirect + cloud.google.com/go/iam v1.1.2 // indirect + cloud.google.com/go/longrunning v0.5.1 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect - github.com/andybalholm/brotli v1.0.6 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v3 v3.1.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/containerd/containerd v1.7.11 // indirect - github.com/containerd/log v0.1.0 // indirect + github.com/containerd/containerd v1.7.3 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -75,16 +73,16 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.17.1 // indirect + github.com/klauspost/compress v1.17.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -98,14 +96,14 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc4 // indirect - github.com/opencontainers/runc v1.1.12 // indirect + github.com/opencontainers/runc v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect github.com/swaggo/files/v2 v2.0.0 // indirect github.com/swaggo/swag v1.16.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect @@ -114,22 +112,22 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.14.0 // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.14.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect + google.golang.org/grpc v1.58.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.90.1 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect diff --git a/go.sum b/go.sum index ddd1a79..c84dc9d 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= -cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= -cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/firestore v1.13.0 h1:/3S4RssUV4GO/kvgJZB+tayjhOfyAHs+KcpJgRVu/Qk= cloud.google.com/go/firestore v1.13.0/go.mod h1:QojqqOh8IntInDUSTAh0c8ZsPYAr68Ma8c5DWOy8xb8= -cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= -cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= -cloud.google.com/go/kms v1.15.3 h1:RYsbxTRmk91ydKCzekI2YjryO4c5Y2M80Zwcs9/D/cI= -cloud.google.com/go/kms v1.15.3/go.mod h1:AJdXqHxS2GlPyduM99s9iGqi2nwbviBbhV/hdmt4iOQ= -cloud.google.com/go/longrunning v0.5.2 h1:u+oFqfEwwU7F9dIELigxbe0XVnBAo9wqMuQLA50CZ5k= -cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= +cloud.google.com/go/iam v1.1.2 h1:gacbrBdWcoVmGLozRuStX45YKvJtzIjJdAolzUs1sm4= +cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/kms v1.15.2 h1:lh6qra6oC4AyWe5fUUUBe/S27k12OHAleOOOw6KakdE= +cloud.google.com/go/kms v1.15.2/go.mod h1:3hopT4+7ooWRCjc2DxgnpESFxhIraaI2IpAVUEhbT/w= +cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI= +cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= cloud.google.com/go/pubsub v1.33.0 h1:6SPCPvWav64tj0sVX/+npCBKhUi/UjJehy9op/V3p2g= cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M= @@ -21,8 +21,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4= firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -32,13 +32,12 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= +github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= +github.com/Microsoft/hcsshim v0.10.0-rc.8/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/aws/aws-sdk-go v1.45.28 h1:p2ATcaK6ffSw4yZ2UAGzgRyRXwKyOJY6ZCiKqj5miJE= github.com/aws/aws-sdk-go v1.45.28/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= @@ -55,18 +54,22 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw= -github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.7.3 h1:cKwYKkP1eTj54bP3wCdXXBymmKRQMrWjkLSWZZJDa8o= +github.com/containerd/containerd v1.7.3/go.mod h1:32FOM4/O0RkNg7AjQj3hDzN9cUGtu+HMvaKUNiqCZB8= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -76,22 +79,24 @@ github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= +github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -125,6 +130,8 @@ github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QX github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw= github.com/gofiber/fiber/v2 v2.52.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlgHI= github.com/gofiber/fiber/v2 v2.52.1/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= @@ -135,13 +142,14 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -153,7 +161,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -162,9 +169,10 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= @@ -177,8 +185,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nw github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -199,8 +207,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= -github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -235,12 +243,14 @@ github.com/mercari/go-circuitbreaker v0.0.2 h1:o4hEUhXQ5n1CqVYpLLk6dyBUF4GDfgCf+ github.com/mercari/go-circuitbreaker v0.0.2/go.mod h1:0jxDKIpe1ktz1HaqQW8bJ9NwT/rxOn5A/92CZVgbJRs= github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/newrelic/go-agent/v3 v3.0.0/go.mod h1:H28zDNUC0U/b7kLoY4EFOhuth10Xu/9dchozUiOseQQ= github.com/newrelic/go-agent/v3 v3.3.0/go.mod h1:H28zDNUC0U/b7kLoY4EFOhuth10Xu/9dchozUiOseQQ= github.com/newrelic/go-agent/v3 v3.26.0 h1:xJkqiQgLtC3ys5zoBxD91ITm7sVHZNEF+7/mqmFjnl0= @@ -267,8 +277,10 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= -github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -288,16 +300,19 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -314,9 +329,11 @@ github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/testcontainers/testcontainers-go v0.22.0 h1:hOK4NzNu82VZcKEB1aP9LO1xYssVFMvlfeuDW9JMmV0= github.com/testcontainers/testcontainers-go v0.22.0/go.mod h1:k0YiPa26xJCRUbUkYqy5rY6NGvSbVCeUBXCvucscBR4= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= @@ -325,6 +342,8 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= @@ -366,8 +385,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= @@ -388,12 +407,14 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -401,8 +422,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= @@ -420,16 +441,23 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -439,8 +467,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -452,13 +480,12 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -479,30 +506,30 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.147.0 h1:Can3FaQo9LlVqxJCodNmeZW/ib3/qKAY3rFeXiHo5gc= google.golang.org/api v0.147.0/go.mod h1:pQ/9j83DcmPd/5C9e2nFOdjjNkDZ1G+zkbK2uvdkJMs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= -google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= -google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.58.2 h1:SXUpjxeVF3FKrTYQI4f4KvbGD5u2xccdYdurwowix5I= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -514,8 +541,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/base/test/localstack_container.go b/pkg/base/test/localstack_container.go index a0e6bbe..acb79a4 100644 --- a/pkg/base/test/localstack_container.go +++ b/pkg/base/test/localstack_container.go @@ -8,6 +8,8 @@ import ( "github.com/colibri-project-io/colibri-sdk-go/pkg/base/config" "github.com/colibri-project-io/colibri-sdk-go/pkg/base/logging" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" "github.com/docker/go-connections/nat" "github.com/google/uuid" "github.com/testcontainers/testcontainers-go" @@ -15,7 +17,7 @@ import ( ) const ( - localstackDockerImage = "localstack/localstack:1.4" + localstackDockerImage = "localstack/localstack:3.1" localstackSvcPort = "4566" ) @@ -43,17 +45,21 @@ func newLocalstackContainer(ctx context.Context, configPath string) *LocalstackC ExposedPorts: []string{localstackSvcPort}, Name: fmt.Sprintf("colibri-project-test-localstack-%s", uuid.New().String()), Env: map[string]string{ - "DEBUG": "${DEBUG-}", - "SERVICES": "sns,sqs,s3,dynamodb", - "DATA_DIR": "${DATA_DIR-}", - "LAMBDA_EXECUTOR": "${LAMBDA_EXECUTOR-}", - "HOST_TMP_FOLDER": "${TMPDIR:-/tmp/}localstack", - "DOCKER_HOST": "unix:///var/run/docker.sock", + "DEBUG": "1", + "SERVICES": "sns,sqs,s3,dynamodb", + }, + HostConfigModifier: func(hostConfig *container.HostConfig) { + hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{ + Type: mount.TypeBind, + Source: configPath, + Target: "/etc/localstack/init/ready.d/", + }) + hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{ + Type: mount.TypeBind, + Source: "/var/run/docker.sock", + Target: "/var/run/docker.sock", + }) }, - Mounts: testcontainers.Mounts( - testcontainers.BindMount(configPath, "/docker-entrypoint-initaws.d/"), - testcontainers.BindMount("/var/run/docker.sock", "/var/run/docker.sock"), - ), WaitingFor: wait.ForAll( wait.ForListeningPort(localstackSvcPort), wait.ForLog("localstack emulator started"), @@ -72,10 +78,12 @@ func (c *LocalstackContainer) start() { if err != nil { logging.Fatal(err.Error()) } + localstackPort, err := c.lsContainer.MappedPort(c.ctx, localstackSvcPort) if err != nil { logging.Fatal(err.Error()) } + log.Printf("Test localstack started at port: %s", localstackPort) c.setEnv(localstackPort) } diff --git a/pkg/base/test/postgres_container.go b/pkg/base/test/postgres_container.go index e058509..5caa825 100644 --- a/pkg/base/test/postgres_container.go +++ b/pkg/base/test/postgres_container.go @@ -19,7 +19,7 @@ import ( ) const ( - postgresDockerImage = "postgres:14-alpine" + postgresDockerImage = "postgres:16-alpine" testDbHost = "localhost" testDbName = "test_db" testDbUser = "test_user" @@ -88,7 +88,7 @@ func (c *PostgresContainer) start() { logging.Fatal(err.Error()) } - log.Printf("Test database started at port: %s", testDbPort) + log.Printf("Test postgres started at port: %s", testDbPort) c.setDatabaseEnv(testDbPort) databaseURL := fmt.Sprintf(config.SQL_DB_CONNECTION_URI_DEFAULT, os.Getenv(config.ENV_SQL_DB_HOST), diff --git a/pkg/base/test/redis_container.go b/pkg/base/test/redis_container.go index 6ca6566..d248836 100644 --- a/pkg/base/test/redis_container.go +++ b/pkg/base/test/redis_container.go @@ -16,7 +16,7 @@ import ( ) const ( - redisDockerImage = "redis:7.0-alpine" + redisDockerImage = "redis:alpine" testRedisSvcPort = "6379" ) @@ -52,6 +52,7 @@ func newRedisContainer() *RedisContainer { func (c *RedisContainer) start() { var err error ctx := context.Background() + c.redisContainer, err = testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: *c.redisContainerRequest, Started: true, diff --git a/pkg/base/test/wiremock_container.go b/pkg/base/test/wiremock_container.go index 9bacf77..63ca1b3 100644 --- a/pkg/base/test/wiremock_container.go +++ b/pkg/base/test/wiremock_container.go @@ -5,13 +5,15 @@ import ( "fmt" "github.com/colibri-project-io/colibri-sdk-go/pkg/base/logging" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" "github.com/google/uuid" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) const ( - wiremockDockerImage = "wiremock/wiremock:2.32.0-alpine" + wiremockDockerImage = "wiremock/wiremock:3.3.1-alpine" wiremockSvcPort = "8080" ) @@ -34,12 +36,17 @@ func UseWiremockContainer(configPath string) *WiremockContainer { func newWiremockContainer(configPath string) *WiremockContainer { req := testcontainers.ContainerRequest{ - Image: wiremockDockerImage, - Name: fmt.Sprintf("colibri-project-test-wiremock-%s", uuid.New().String()), - ExposedPorts: []string{wiremockSvcPort}, - Env: map[string]string{}, - Mounts: testcontainers.ContainerMounts{ - testcontainers.ContainerMount{Source: testcontainers.GenericBindMountSource{HostPath: configPath}, Target: "/home/wiremock"}, + Image: wiremockDockerImage, + ImagePlatform: "linux/amd64", + Name: fmt.Sprintf("colibri-project-test-wiremock-%s", uuid.New().String()), + ExposedPorts: []string{wiremockSvcPort}, + Env: map[string]string{}, + HostConfigModifier: func(hostConfig *container.HostConfig) { + hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{ + Type: mount.TypeBind, + Source: configPath, + Target: "/home/wiremock", + }) }, Cmd: []string{"--local-response-templating"}, WaitingFor: wait.ForListeningPort(wiremockSvcPort), @@ -61,7 +68,7 @@ func (c *WiremockContainer) start() { runningPort, _ := c.wContainer.MappedPort(ctx, wiremockSvcPort) c.instancePort = runningPort.Int() - logging.Info("Wiremock container exposed port: %s", runningPort.Port()) + logging.Info("Test wiremock started at port: %s", runningPort.Port()) } func (c *WiremockContainer) Port() int { diff --git a/pkg/base/types/json_test.go b/pkg/base/types/json_test.go index eb899b2..fde4588 100644 --- a/pkg/base/types/json_test.go +++ b/pkg/base/types/json_test.go @@ -1,12 +1,12 @@ package types import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestJsonB(t *testing.T) { - t.Run("should error with nil value", func(t *testing.T) { var result JsonB err := result.Scan(nil) diff --git a/pkg/base/types/null_bool.go b/pkg/base/types/null_bool.go new file mode 100644 index 0000000..027dc88 --- /dev/null +++ b/pkg/base/types/null_bool.go @@ -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 +} diff --git a/pkg/base/types/null_bool_test.go b/pkg/base/types/null_bool_test.go new file mode 100644 index 0000000..0b5da83 --- /dev/null +++ b/pkg/base/types/null_bool_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_date_time.go b/pkg/base/types/null_date_time.go new file mode 100644 index 0000000..34f3c82 --- /dev/null +++ b/pkg/base/types/null_date_time.go @@ -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 +} diff --git a/pkg/base/types/null_date_time_test.go b/pkg/base/types/null_date_time_test.go new file mode 100644 index 0000000..828b430 --- /dev/null +++ b/pkg/base/types/null_date_time_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_float_64.go b/pkg/base/types/null_float_64.go new file mode 100644 index 0000000..f70d05e --- /dev/null +++ b/pkg/base/types/null_float_64.go @@ -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 +} diff --git a/pkg/base/types/null_float_64_test.go b/pkg/base/types/null_float_64_test.go new file mode 100644 index 0000000..acb583f --- /dev/null +++ b/pkg/base/types/null_float_64_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_int_16.go b/pkg/base/types/null_int_16.go new file mode 100644 index 0000000..71d9511 --- /dev/null +++ b/pkg/base/types/null_int_16.go @@ -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 +} diff --git a/pkg/base/types/null_int_16_test.go b/pkg/base/types/null_int_16_test.go new file mode 100644 index 0000000..31371b6 --- /dev/null +++ b/pkg/base/types/null_int_16_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_int_32.go b/pkg/base/types/null_int_32.go new file mode 100644 index 0000000..7eb7fc2 --- /dev/null +++ b/pkg/base/types/null_int_32.go @@ -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 +} diff --git a/pkg/base/types/null_int_32_test.go b/pkg/base/types/null_int_32_test.go new file mode 100644 index 0000000..c5ee99a --- /dev/null +++ b/pkg/base/types/null_int_32_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_int_64.go b/pkg/base/types/null_int_64.go new file mode 100644 index 0000000..62036ae --- /dev/null +++ b/pkg/base/types/null_int_64.go @@ -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 +} diff --git a/pkg/base/types/null_int_64_test.go b/pkg/base/types/null_int_64_test.go new file mode 100644 index 0000000..e73d9db --- /dev/null +++ b/pkg/base/types/null_int_64_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_iso_date.go b/pkg/base/types/null_iso_date.go new file mode 100644 index 0000000..d2e4290 --- /dev/null +++ b/pkg/base/types/null_iso_date.go @@ -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 +} diff --git a/pkg/base/types/null_iso_date_test.go b/pkg/base/types/null_iso_date_test.go new file mode 100644 index 0000000..ed7d129 --- /dev/null +++ b/pkg/base/types/null_iso_date_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_iso_time.go b/pkg/base/types/null_iso_time.go new file mode 100644 index 0000000..fc4506d --- /dev/null +++ b/pkg/base/types/null_iso_time.go @@ -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 +} diff --git a/pkg/base/types/null_iso_time_test.go b/pkg/base/types/null_iso_time_test.go new file mode 100644 index 0000000..f5c5c59 --- /dev/null +++ b/pkg/base/types/null_iso_time_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_string.go b/pkg/base/types/null_string.go new file mode 100644 index 0000000..d8a90c9 --- /dev/null +++ b/pkg/base/types/null_string.go @@ -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 +} diff --git a/pkg/base/types/null_string_test.go b/pkg/base/types/null_string_test.go new file mode 100644 index 0000000..a9a6456 --- /dev/null +++ b/pkg/base/types/null_string_test.go @@ -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) + }) +} diff --git a/pkg/base/types/null_types.go b/pkg/base/types/null_types.go deleted file mode 100644 index c11a988..0000000 --- a/pkg/base/types/null_types.go +++ /dev/null @@ -1,467 +0,0 @@ -package types - -import ( - "database/sql" - "database/sql/driver" - "encoding/json" - "reflect" - "time" -) - -// NullBoll for empty boolean field -type NullBool sql.NullBool - -// NullFloat64 for empty float field -type NullFloat64 sql.NullFloat64 - -// NullInt16 for empty int16 field -type NullInt16 sql.NullInt16 - -// NullInt32 for empty int32 field -type NullInt32 sql.NullInt32 - -// NullInt64 for empty int64 field -type NullInt64 sql.NullInt64 - -// NullIsoDate for empty date field -type NullIsoDate sql.NullTime - -// NullIsoTime for empty time field -type NullIsoTime sql.NullTime - -// NullString for empty string field -type NullString sql.NullString - -// NullDateTime for empty date/time field -type NullDateTime sql.NullTime - -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 -} - -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 -} - -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 -} - -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 -} - -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 -} - -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 -} - -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 -} - -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 -} - -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 -} diff --git a/pkg/base/types/null_types_test.go b/pkg/base/types/null_types_test.go deleted file mode 100644 index 4b4cb90..0000000 --- a/pkg/base/types/null_types_test.go +++ /dev/null @@ -1,812 +0,0 @@ -package types - -import ( - "testing" - "time" - - "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) - }) -} - -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) - }) -} - -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) - }) -} - -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) - }) -} - -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) - }) -} - -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) - }) -} - -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) - }) -} - -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) - }) -} - -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) - }) -} diff --git a/pkg/base/types/page.go b/pkg/base/types/page.go index af9623e..09e5905 100644 --- a/pkg/base/types/page.go +++ b/pkg/base/types/page.go @@ -11,25 +11,6 @@ type Page[T any] struct { TotalElements uint64 `json:"totalElements"` } -// 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} -} - // PageRequest is the contract of request page type PageRequest struct { Page uint16 diff --git a/pkg/base/types/page_test.go b/pkg/base/types/page_test.go index 7312dfa..1379750 100644 --- a/pkg/base/types/page_test.go +++ b/pkg/base/types/page_test.go @@ -6,16 +6,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNewSort(t *testing.T) { - - t.Run("NewSort", func(t *testing.T) { - s := NewSort(ASC, "name") - assert.NotNil(t, s) - assert.Equal(t, ASC, s.Direction) - assert.Equal(t, "name", s.Field) - }) -} - func TestNewPageRequest(t *testing.T) { const ( page uint16 = 1 @@ -23,40 +13,42 @@ func TestNewPageRequest(t *testing.T) { ) t.Run("Should return a new PageRequest with nil sort list", func(t *testing.T) { - s := NewPageRequest(page, size, nil) - assert.NotNil(t, s) - assert.Equal(t, s.Page, page) - assert.Equal(t, s.Size, size) - assert.Nil(t, s.Order) - assert.Equal(t, "", s.GetOrder()) + expected := &PageRequest{Page: page, Size: size} + + result := NewPageRequest(expected.Page, expected.Size, nil) + + assert.NotNil(t, result) + assert.EqualValues(t, expected, result) + assert.Equal(t, "", result.GetOrder()) }) t.Run("Should return a new PageRequest with empty sort list", func(t *testing.T) { - s := NewPageRequest(page, size, []Sort{}) - assert.NotNil(t, s) - assert.Equal(t, s.Page, page) - assert.Equal(t, s.Size, size) - assert.NotNil(t, s.Order) - assert.Equal(t, "", s.GetOrder()) + expected := &PageRequest{Page: page, Size: size, Order: []Sort{}} + + result := NewPageRequest(expected.Page, expected.Size, expected.Order) + + assert.NotNil(t, result) + assert.EqualValues(t, expected, result) + assert.Equal(t, "", result.GetOrder()) }) t.Run("Should return a new PageRequest with one populated sort list", func(t *testing.T) { - sort := []Sort{NewSort(ASC, "field1")} - s := NewPageRequest(page, size, sort) - assert.NotNil(t, s) - assert.Equal(t, s.Page, page) - assert.Equal(t, s.Size, size) - assert.NotNil(t, s.Order) - assert.Equal(t, "field1 ASC", s.GetOrder()) + expected := &PageRequest{Page: page, Size: size, Order: []Sort{NewSort(ASC, "field1")}} + + result := NewPageRequest(expected.Page, expected.Size, expected.Order) + + assert.NotNil(t, result) + assert.EqualValues(t, expected, result) + assert.EqualValues(t, "field1 ASC", result.GetOrder()) }) t.Run("Should return a new PageRequest with many populated sort list", func(t *testing.T) { - sort := []Sort{NewSort(ASC, "field1"), NewSort(DESC, "field2")} - s := NewPageRequest(page, size, sort) - assert.NotNil(t, s) - assert.Equal(t, s.Page, page) - assert.Equal(t, s.Size, size) - assert.NotNil(t, s.Order) - assert.Equal(t, "field1 ASC, field2 DESC", s.GetOrder()) + expected := &PageRequest{Page: page, Size: size, Order: []Sort{NewSort(ASC, "field1"), NewSort(DESC, "field2")}} + + result := NewPageRequest(expected.Page, expected.Size, expected.Order) + + assert.NotNil(t, result) + assert.EqualValues(t, expected, result) + assert.Equal(t, "field1 ASC, field2 DESC", result.GetOrder()) }) } diff --git a/pkg/base/types/sort.go b/pkg/base/types/sort.go new file mode 100644 index 0000000..2aed604 --- /dev/null +++ b/pkg/base/types/sort.go @@ -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} +} diff --git a/pkg/base/types/sort_test.go b/pkg/base/types/sort_test.go new file mode 100644 index 0000000..a52d349 --- /dev/null +++ b/pkg/base/types/sort_test.go @@ -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) + }) +} diff --git a/pkg/storage/aws.go b/pkg/storage/aws.go index 7de73aa..fb02dc9 100644 --- a/pkg/storage/aws.go +++ b/pkg/storage/aws.go @@ -3,6 +3,7 @@ package storage import ( "context" "mime/multipart" + "os" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" @@ -12,10 +13,15 @@ import ( ) type awsStorage struct { - s3Service *s3.S3 - uploader *s3manager.Uploader + s3Service *s3.S3 + uploader *s3manager.Uploader + downloader *s3manager.Downloader } +// newAwsStorage creates a new awsStorage instance and initializes the S3 service, uploader, and downloader. +// +// No parameters. +// Returns a pointer to the awsStorage instance. func newAwsStorage() *awsStorage { var s awsStorage s.s3Service = s3.New(cloud.GetAwsSession()) @@ -24,10 +30,40 @@ func newAwsStorage() *awsStorage { } s.uploader = s3manager.NewUploader(cloud.GetAwsSession()) + s.downloader = s3manager.NewDownloader(cloud.GetAwsSession()) return &s } -func (s awsStorage) uploadFile(ctx context.Context, bucket, key string, file *multipart.File) (string, error) { +// downloadFile downloads a file from the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket from which the file is downloaded. +// key: the key or identifier of the file to be downloaded. +// Returns a file pointer and an error. +func (s *awsStorage) downloadFile(ctx context.Context, bucket, key string) (*os.File, error) { + file, err := os.CreateTemp("", "tempFile") + if err != nil { + return nil, err + } + + if _, err := s.downloader.DownloadWithContext(ctx, file, &s3.GetObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(key), + }); err != nil { + return nil, err + } + + return file, nil +} + +// uploadFile uploads a file to the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket to upload the file to. +// key: the key or identifier of the file to be uploaded. +// file: the file to be uploaded. +// Returns the location of the uploaded file and an error, if any. +func (s *awsStorage) uploadFile(ctx context.Context, bucket, key string, file *multipart.File) (string, error) { result, err := s.uploader.UploadWithContext(ctx, &s3manager.UploadInput{ Bucket: aws.String(bucket), Key: aws.String(key), @@ -40,10 +76,17 @@ func (s awsStorage) uploadFile(ctx context.Context, bucket, key string, file *mu return result.Location, nil } -func (s awsStorage) deleteFile(ctx context.Context, bucket, key string) error { +// deleteFile deletes a file from the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket from which the file is deleted. +// key: the key or identifier of the file to be deleted. +// Returns an error. +func (s *awsStorage) deleteFile(ctx context.Context, bucket, key string) error { _, err := s.s3Service.DeleteObjectWithContext(ctx, &s3.DeleteObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), }) + return err } diff --git a/pkg/storage/gcp.go b/pkg/storage/gcp.go index 271fe0b..7a61f1b 100644 --- a/pkg/storage/gcp.go +++ b/pkg/storage/gcp.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "mime/multipart" + "os" gcp_storage "cloud.google.com/go/storage" "github.com/colibri-project-io/colibri-sdk-go/pkg/base/logging" @@ -14,6 +15,10 @@ type gcpStorage struct { client *gcp_storage.Client } +// newGcpStorage creates a new instance of gcpStorage by initializing the client. +// +// No parameters. +// Returns a pointer to gcpStorage. func newGcpStorage() *gcpStorage { client, err := gcp_storage.NewClient(context.Background()) if err != nil { @@ -24,6 +29,42 @@ func newGcpStorage() *gcpStorage { } +// downloadFile downloads a file from the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket from which the file is downloaded. +// key: the key or identifier of the file to be downloaded. +// Returns a file pointer and an error. +func (s *gcpStorage) downloadFile(ctx context.Context, bucket, key string) (*os.File, error) { + file, err := os.CreateTemp("", "tempFile") + if err != nil { + return nil, err + } + + if _, err := s.client.Bucket(bucket).Object(key).NewReader(ctx); err != nil { + return nil, err + } + + fileReader, err := s.client.Bucket(bucket).Object(key).NewReader(ctx) + if err != nil { + return nil, err + } + defer fileReader.Close() + + if _, err := io.Copy(file, fileReader); err != nil { + return nil, err + } + + return file, nil +} + +// uploadFile uploads a file to the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket to upload the file to. +// key: the key or identifier of the file to be uploaded. +// file: the file to be uploaded. +// Returns the location of the uploaded file and an error, if any. func (s *gcpStorage) uploadFile(ctx context.Context, bucket, key string, file *multipart.File) (string, error) { writer := s.client.Bucket(bucket).Object(key).NewWriter(ctx) writer.CacheControl = "no-cache, max-age=1" @@ -37,6 +78,12 @@ func (s *gcpStorage) uploadFile(ctx context.Context, bucket, key string, file *m return fmt.Sprintf("https://storage.googleapis.com/%s/%s", bucket, key), nil } +// deleteFile deletes a file from the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket from which the file is deleted. +// key: the key or identifier of the file to be deleted. +// Returns an error. func (s *gcpStorage) deleteFile(ctx context.Context, bucket, key string) error { return s.client.Bucket(bucket).Object(key).Delete(ctx) } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 9a8f901..67946fa 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -3,6 +3,7 @@ package storage import ( "context" "mime/multipart" + "os" "github.com/colibri-project-io/colibri-sdk-go/pkg/base/config" "github.com/colibri-project-io/colibri-sdk-go/pkg/base/logging" @@ -15,13 +16,17 @@ const ( ) type storage interface { + downloadFile(ctx context.Context, bucket, key string) (*os.File, error) uploadFile(ctx context.Context, bucket, key string, file *multipart.File) (string, error) deleteFile(ctx context.Context, bucket, key string) error } var instance storage -// Initialize loads the storage settings according to the configured environment. +// Initialize initializes the storage provider based on the configured cloud. +// +// No parameters. +// No return values. func Initialize() { switch config.CLOUD { case config.CLOUD_AWS: @@ -33,6 +38,33 @@ func Initialize() { logging.Info("Storage provider connected") } +// DownloadFile downloads a file from the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket from which the file is downloaded. +// key: the key or identifier of the file to be downloaded. +// Returns a file pointer and an error. +func DownloadFile(ctx context.Context, bucket, key string) (*os.File, error) { + txn := monitoring.GetTransactionInContext(ctx) + if txn != nil { + segment := monitoring.StartTransactionSegment(ctx, storage_transaction, map[string]string{ + "method": "Download", + "bucket": bucket, + "key": key, + }) + defer monitoring.EndTransactionSegment(segment) + } + + return instance.downloadFile(ctx, bucket, key) +} + +// UploadFile uploads a file to the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket to upload the file to. +// key: the key or identifier of the file to be uploaded. +// file: the file to be uploaded. +// Returns the location of the uploaded file and an error, if any. func UploadFile(ctx context.Context, bucket, key string, file *multipart.File) (string, error) { txn := monitoring.GetTransactionInContext(ctx) if txn != nil { @@ -47,6 +79,12 @@ func UploadFile(ctx context.Context, bucket, key string, file *multipart.File) ( return instance.uploadFile(ctx, bucket, key, file) } +// DeleteFile deletes a file from the storage provider. +// +// ctx: the context for the operation. +// bucket: the storage bucket from which the file is deleted. +// key: the key or identifier of the file to be deleted. +// Returns an error. func DeleteFile(ctx context.Context, bucket, key string) error { txn := monitoring.GetTransactionInContext(ctx) if txn != nil { diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go new file mode 100644 index 0000000..2b391ad --- /dev/null +++ b/pkg/storage/storage_test.go @@ -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 +} diff --git a/pkg/web/restclient/client.go b/pkg/web/restclient/client.go index 6fc7198..9cb4440 100644 --- a/pkg/web/restclient/client.go +++ b/pkg/web/restclient/client.go @@ -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) - } -} diff --git a/pkg/web/restclient/client_config.go b/pkg/web/restclient/client_config.go new file mode 100644 index 0000000..ed3d2a2 --- /dev/null +++ b/pkg/web/restclient/client_config.go @@ -0,0 +1,9 @@ +package restclient + +type RestClientConfig struct { + Name string + BaseURL string + Timeout uint + Retries uint8 + RetrySleepInSeconds uint +} diff --git a/pkg/web/restclient/client_test.go b/pkg/web/restclient/client_test.go index 6f192be..b38b3d4 100644 --- a/pkg/web/restclient/client_test.go +++ b/pkg/web/restclient/client_test.go @@ -1,16 +1,21 @@ package restclient import ( + "bytes" "context" "fmt" + "io" "net/http" "net/url" "testing" + "time" + + "k8s.io/apimachinery/pkg/util/net" "github.com/colibri-project-io/colibri-sdk-go/pkg/base/monitoring" "github.com/colibri-project-io/colibri-sdk-go/pkg/base/test" + "github.com/colibri-project-io/colibri-sdk-go/pkg/database/cacheDB" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/util/net" ) type userResponseTestStruct struct { @@ -29,167 +34,515 @@ type userRequestTestStruct struct { Email string `json:"email,omitempty"` } -func InitWiremock() *RestClient { - wiremockContainer := test.UseWiremockContainer(test.MountAbsolutPath(test.WIREMOCK_ENVIRONMENT_PATH)) +type tokenResponseTestStruct struct { + Type string `json:"type"` + Token string `json:"token"` + ExpiresIn uint64 `json:"expiresIn"` +} + +var ( + ctx = context.Background() + wiremock *test.WiremockContainer + restClient *RestClient +) - test.InitializeBaseTest() - return NewRestClient("test-client", fmt.Sprintf("http://localhost:%d/users-api/v1", wiremockContainer.Port()), 1) +func TestMain(m *testing.M) { + monitoring.Initialize() + test.InitializeCacheDBTest() + wiremock = test.UseWiremockContainer(test.MountAbsolutPath(test.WIREMOCK_ENVIRONMENT_PATH)) + restClient = NewRestClient(&RestClientConfig{ + Name: "test-rest-client", + BaseURL: fmt.Sprintf("http://localhost:%d/users-api/v1", wiremock.Port()), + Timeout: 1, + }) + + m.Run() } func TestGet(t *testing.T) { - restClient := InitWiremock() - - t.Run("User ok", func(t *testing.T) { + t.Run("Should return 500 status code (Internal Server Error) and nil body when timeout occurs", func(t *testing.T) { t.Parallel() - resp := Get[userResponseTestStruct](context.Background(), restClient, "/1", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body(), "user is nil") + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodGet, + Path: "/2", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusInternalServerError, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.Errorf(t, response.Error(), "expected timeout error: %v\n", response.Error()) + assert.Truef(t, net.IsTimeout(response.Error()), "expected timeout error: %v\n", response.Error()) }) - t.Run("User with timeout", func(t *testing.T) { + t.Run("Should return 200 status code (OK) and not nil body with list", func(t *testing.T) { t.Parallel() - resp := Get[userResponseTestStruct](context.Background(), restClient, "/2", nil) - assert.Errorf(t, resp.Error(), "expected timeout error: %v\n", resp.Error()) - assert.Truef(t, net.IsTimeout(resp.Error()), "expected timeout error: %v\n", resp.Error()) - assert.Nilf(t, resp.Body(), "user is not null\n") + expected := []userResponseTestStruct{ + {ID: 1, Name: "Jaya Ganaka 1", Email: "ganaka_1_jaya@ryan.biz"}, + {ID: 2, Name: "Jaya Ganaka 2", Email: "ganaka_2_jaya@ryan.biz"}, + {ID: 3, Name: "Jaya Ganaka 3", Email: "ganaka_3_jaya@ryan.biz"}, + {ID: 4, Name: "Jaya Ganaka 4", Email: "ganaka_4_jaya@ryan.biz"}, + {ID: 5, Name: "Jaya Ganaka 5", Email: "ganaka_5_jaya@ryan.biz"}, + } + + response := Request[[]userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodGet, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.Equal(t, 5, len(*response.SuccessBody())) + assert.EqualValues(t, expected, *response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) - t.Run("List users ok", func(t *testing.T) { + t.Run("Should return 200 status code (OK) and not nil body with object", func(t *testing.T) { t.Parallel() - resp := Get[[]userResponseTestStruct](context.Background(), restClient, "", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.True(t, len(*resp.Body()) == 5) + expected := &userResponseTestStruct{ID: 1, Name: "Jaya Ganaka MD", Email: "ganaka_md_jaya@ryan.biz"} + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodGet, + Path: "/1", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.EqualValues(t, expected, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) } func TestPost(t *testing.T) { - restClient := InitWiremock() - - t.Run("OK", func(t *testing.T) { + t.Run("Should return 201 status code (Created) and not nil body with object", func(t *testing.T) { t.Parallel() newUser := userRequestTestStruct{Name: "User 10", Email: "user_10@email.com"} - resp := Post[userResponseTestStruct](context.Background(), restClient, "/users", &newUser, nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, uint(10), resp.Body().ID) + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/users", + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusCreated, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, uint(10), response.SuccessBody().ID) + assert.Equal(t, newUser.Name, response.SuccessBody().Name) + assert.Equal(t, newUser.Email, response.SuccessBody().Email) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) + }) + + t.Run("Should return 201 status code (Created) and nil body", func(t *testing.T) { + t.Parallel() + newUser := userRequestTestStruct{Name: "User 100", Email: "user_100@email.com"} + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/users", + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusCreated, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) } -func TestPut(t *testing.T) { - restClient := InitWiremock() +func TestPostWithMultipart(t *testing.T) { + t.Run("Should return 201 status code (Created) and nil body to valid MultipartFields containing a file with custom content type", func(t *testing.T) { + t.Parallel() + var UploadFile io.Reader = bytes.NewBufferString("test") + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/upload", + MultipartFields: map[string]interface{}{ + "myfile": MultipartFile{ + FileName: "test.txt", + File: UploadFile, + ContentType: "text/plain", + }, + }, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusCreated, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) + }) + + t.Run("Should return 201 status code (Created) and nil body to valid MultipartFields containing a file with default content type", func(t *testing.T) { + t.Parallel() + var UploadFile io.Reader = bytes.NewBufferString("test") + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/upload", + MultipartFields: map[string]interface{}{ + "file": MultipartFile{ + FileName: "test.txt", + File: UploadFile, + }, + }, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusCreated, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) + }) + + t.Run("Should return 201 status code (Created) and nil body to valid MultipartFields containing text fields", func(t *testing.T) { + t.Parallel() + newUser := userRequestTestStruct{Name: "User 100", Email: "user_100@email.com"} + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + MultipartFields: map[string]interface{}{ + "name": "User 100", + "email": "user_100@email.com", + }, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusCreated, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, uint(10), response.SuccessBody().ID) + assert.Equal(t, newUser.Name, response.SuccessBody().Name) + assert.Equal(t, newUser.Email, response.SuccessBody().Email) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) + }) - t.Run("OK", func(t *testing.T) { + t.Run("Should return 500 status code (Internal Server Error) when send invalid MultipartFields type", func(t *testing.T) { + t.Parallel() + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/upload", + MultipartFields: map[string]interface{}{ + "file": -1, + }, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusInternalServerError, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.ErrorContains(t, response.Error(), "error while sending the multipart/form-data: data type not allowed") + }) + +} + +func TestPut(t *testing.T) { + t.Run("Should return 200 status code (OK) and not nil body with object", func(t *testing.T) { t.Parallel() newUser := userRequestTestStruct{ID: 10, Name: "User 10 edited", Email: "user_10@email.com"} - resp := Put[userResponseTestStruct](context.Background(), restClient, fmt.Sprintf("/users/%d", newUser.ID), &newUser, nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, uint(10), resp.Body().ID) - assert.Equal(t, "User 10 edited", resp.Body().Name) + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPut, + Path: fmt.Sprintf("/users/%d", newUser.ID), + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NoError(t, response.Error()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, newUser.ID, response.SuccessBody().ID) + assert.Equal(t, newUser.Name, response.SuccessBody().Name) + assert.Equal(t, newUser.Email, response.SuccessBody().Email) + }) + + t.Run("Should return 204 status code (No Content) and nil body", func(t *testing.T) { + t.Parallel() + newUser := userRequestTestStruct{ID: 100, Name: "User 100 edited", Email: "user_100@email.com"} + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPut, + Path: fmt.Sprintf("/users/%d", newUser.ID), + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusNoContent, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) } func TestPatch(t *testing.T) { - restClient := InitWiremock() - - t.Run("OK", func(t *testing.T) { + t.Run("Should return 200 status code (OK) and not nil body with object", func(t *testing.T) { t.Parallel() newUser := userRequestTestStruct{Name: "User 10 edited"} - resp := Patch[userResponseTestStruct](context.Background(), restClient, "/users/10", &newUser, nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, uint(10), resp.Body().ID) - assert.Equal(t, "User 10 edited", resp.Body().Name) + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPatch, + Path: "/users/10", + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, uint(10), response.SuccessBody().ID) + assert.Equal(t, newUser.Name, response.SuccessBody().Name) + assert.NotNil(t, response.SuccessBody().Email) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) + }) + + t.Run("Should return 204 status code (No Content) and nil body", func(t *testing.T) { + t.Parallel() + newUser := userRequestTestStruct{Name: "User 100 edited"} + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPatch, + Path: "/users/100", + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusNoContent, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) } func TestDelete(t *testing.T) { - restClient := InitWiremock() - - t.Run("OK", func(t *testing.T) { + t.Run("Should return 200 status code (OK) and not nil body with object", func(t *testing.T) { t.Parallel() - resp := Delete[userResponseTestStruct](context.Background(), restClient, "/users/11", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, uint(11), resp.Body().ID) - assert.Equal(t, "User 11 deleted", resp.Body().Name) + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodDelete, + Path: "/users/11", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, uint(11), response.SuccessBody().ID) + assert.Equal(t, "User 11 deleted", response.SuccessBody().Name) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) - t.Run("OK no content", func(t *testing.T) { + t.Run("Should return 204 status code (No Content) and nil body", func(t *testing.T) { t.Parallel() - resp := Delete[userResponseTestStruct](context.Background(), restClient, "/users/12", nil) - assert.NoError(t, resp.Error()) - assert.Nil(t, resp.Body()) + + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodDelete, + Path: "/users/12", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusNoContent, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) } func TestPostNotEmptyResponseBodyError(t *testing.T) { - restClient := InitWiremock() - - t.Run("Should return an empty body", func(t *testing.T) { + t.Run("Should return 500 status code (Internal Server Error) and nil body", func(t *testing.T) { t.Parallel() newUser := userRequestTestStruct{Name: "Empty Body Response", Email: "post_user_empty_body@error.com"} - resp, respErr := PostWithErrorData[any, userResponseErrorTestStruct](context.Background(), restClient, "/users", &newUser, nil) - - assert.NotNil(t, resp) - assert.Nil(t, resp.Body()) - assert.ErrorContains(t, resp.Error(), "500") - assert.Nil(t, respErr) + response := Request[any, userResponseErrorTestStruct]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/users", + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusInternalServerError, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.Errorf(t, response.Error(), errResponseWithEmptyBody, http.StatusInternalServerError) }) - t.Run("Should return a body on statusCode 500", func(t *testing.T) { + t.Run("Should return 500 status code (Internal Server Error) and not nil body with object", func(t *testing.T) { t.Parallel() newUser := userRequestTestStruct{Name: "Body Response", Email: "post_user_with_body@error.com"} - resp, respErr := PostWithErrorData[userResponseTestStruct, userResponseErrorTestStruct](context.Background(), restClient, "/users", &newUser, nil) - - assert.NotNil(t, resp) - assert.Nil(t, resp.Body()) - assert.ErrorContains(t, resp.Error(), "500") - assert.Equal(t, respErr.Message, "Error message post user") + response := Request[userResponseTestStruct, userResponseErrorTestStruct]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/users", + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusInternalServerError, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.EqualValues(t, "Error message post user", response.ErrorBody().Message) + assert.EqualError(t, response.Error(), "error body decoded with 500 status code") }) - t.Run("Should return an decode error on statusCode 500", func(t *testing.T) { + t.Run("Should return 500 status code (Internal Server Error) when decode response error occurs", func(t *testing.T) { t.Parallel() newUser := userRequestTestStruct{Name: "Body Response", Email: "post_user_decode_error@error.com"} - resp, respErr := PostWithErrorData[userResponseTestStruct, userResponseErrorTestStruct](context.Background(), restClient, "/users", &newUser, nil) - - assert.NotNil(t, resp) - assert.Nil(t, resp.Body()) - assert.ErrorContains(t, resp.Error(), "500") - assert.ErrorContains(t, resp.Error(), "could not decode response") - assert.Nil(t, respErr) + response := Request[userResponseTestStruct, userResponseErrorTestStruct]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/users", + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusInternalServerError, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.ErrorContains(t, response.Error(), "500") + assert.ErrorContains(t, response.Error(), "could not decode response") }) } func TestPostWithBodyString(t *testing.T) { - restClient := InitWiremock() - - t.Run("post x-www-form-urlencoded", func(t *testing.T) { + t.Run("Should return 200 status code (OK) and not nil body with object when post with x-www-form-urlencoded", func(t *testing.T) { data := url.Values{} data.Set("user", "darth_vader") data.Set("pass", "force") - resp := PostBodyString[userResponseTestStruct](context.Background(), restClient, "/login", data.Encode(), map[string]string{"Content-type": "application/x-www-form-urlencoded"}) + response := Request[userResponseTestStruct, any]{ + Ctx: ctx, + Client: restClient, + HttpMethod: http.MethodPost, + Path: "/login", + Headers: map[string]string{"Content-type": "application/x-www-form-urlencoded"}, + Body: data.Encode(), + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, uint(100), response.SuccessBody().ID) + assert.Equal(t, "user_100@email.com", response.SuccessBody().Email) + assert.Equal(t, "User 100", response.SuccessBody().Name) + assert.Nil(t, response.ErrorBody()) + assert.Nil(t, response.Error()) + }) +} - assert.NotNil(t, resp) - assert.NotNil(t, resp.Body()) - assert.Equal(t, uint(100), resp.Body().ID) - assert.Equal(t, "user_100@email.com", resp.Body().Email) - assert.Equal(t, "User 100", resp.Body().Name) +func TestPostWithRetry(t *testing.T) { + retryClient := NewRestClient(&RestClientConfig{ + Name: "test-post-with-retry-in-rest-client", + BaseURL: fmt.Sprintf("http://localhost:%d/users-api/v1", wiremock.Port()), + Timeout: 100, + Retries: 3, + RetrySleepInSeconds: 1, + }) + + t.Run("Should return 200 status code (OK) and not nil body with object when post with x-www-form-urlencoded", func(t *testing.T) { + newUser := userRequestTestStruct{Name: "Empty Body Response", Email: "post_user_empty_body@error.com"} + + response := Request[any, any]{ + Ctx: ctx, + Client: retryClient, + HttpMethod: http.MethodPost, + Path: "/users", + Body: &newUser, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusInternalServerError, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) }) } -func TestCreateSegmentTestEnv(t *testing.T) { - monitoring.Initialize() - seg := createSegment(context.Background(), http.MethodPost, "/api/users") - assert.Nil(t, seg) +func TestPostWithCache(t *testing.T) { + cacheDB.Initialize() + retryClient := NewRestClient(&RestClientConfig{ + Name: "test-post-with-cache-in-rest-client", + BaseURL: fmt.Sprintf("http://localhost:%d/token", wiremock.Port()), + Timeout: 100, + Retries: 3, + RetrySleepInSeconds: 1, + }) + userToken := &tokenResponseTestStruct{Type: "Bearer", Token: "1A2B3C4D5E", ExpiresIn: 123456789} + userBody := &userRequestTestStruct{ID: 10, Name: "User 10", Email: "user_10@email.com"} + tokenCache := cacheDB.NewCache[tokenResponseTestStruct]("user-token-cache", time.Hour) + + t.Run("Should set and return object in cache database", func(t *testing.T) { + // Check if cache is empty + emptyCache, err := tokenCache.One(ctx) + assert.NoError(t, err) + assert.Nil(t, emptyCache) + + // First call, get in api and set in cache + request := Request[tokenResponseTestStruct, any]{ + Ctx: ctx, + Client: retryClient, + HttpMethod: http.MethodPost, + Cache: tokenCache, + Body: userBody, + } + + firstResponse := request.Call() + + assert.NotNil(t, firstResponse) + assert.EqualValues(t, http.StatusOK, firstResponse.StatusCode()) + assert.EqualValues(t, userToken, firstResponse.SuccessBody()) + assert.Nil(t, firstResponse.ErrorBody()) + assert.Nil(t, firstResponse.Error()) + + // Check if token saved in cache + cachedToken, err := tokenCache.One(ctx) + + assert.Nil(t, err) + assert.NotNil(t, cachedToken) + assert.EqualValues(t, userToken, cachedToken) + + // Second call, get in cache and return not modified status code + cachedResponse := request.Call() + + assert.NotNil(t, cachedResponse) + assert.EqualValues(t, http.StatusNotModified, cachedResponse.StatusCode()) + assert.EqualValues(t, userToken, cachedResponse.SuccessBody()) + assert.Nil(t, cachedResponse.ErrorBody()) + assert.Nil(t, cachedResponse.Error()) + }) } diff --git a/pkg/web/restclient/multipart_file.go b/pkg/web/restclient/multipart_file.go new file mode 100644 index 0000000..eac21a4 --- /dev/null +++ b/pkg/web/restclient/multipart_file.go @@ -0,0 +1,9 @@ +package restclient + +import "io" + +type MultipartFile struct { + FileName string + File io.Reader + ContentType string +} diff --git a/pkg/web/restclient/request.go b/pkg/web/restclient/request.go new file mode 100644 index 0000000..7b4821e --- /dev/null +++ b/pkg/web/restclient/request.go @@ -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 +} diff --git a/pkg/web/restclient/request_data.go b/pkg/web/restclient/request_data.go new file mode 100644 index 0000000..3b404ce --- /dev/null +++ b/pkg/web/restclient/request_data.go @@ -0,0 +1,6 @@ +package restclient + +// RequestData interface to encapsulate request data +type RequestData interface { + any +} diff --git a/pkg/web/restclient/response_data.go b/pkg/web/restclient/response_data.go index c4a6dac..0aa5f11 100644 --- a/pkg/web/restclient/response_data.go +++ b/pkg/web/restclient/response_data.go @@ -2,37 +2,143 @@ package restclient import "net/http" -// ResponseData interface to encapsulate response data with Status, Body, Header and Error -type ResponseData[Body any] interface { - Status() int - Body() *Body - Header() http.Header +// ResponseData interface to encapsulate response data with Status code, Headers, Success body, Error body and Error +type ResponseData[T any, E any] interface { + StatusCode() int + Headers() http.Header + SuccessBody() *T + ErrorBody() *E Error() error + + IsInformationalResponse() bool + IsSuccessfulResponse() bool + IsRedirectionMessage() bool + IsClientErrorResponse() bool + IsServerErrorResponse() bool + HasError() bool + HasSuccess() bool +} + +// newResponseData creates a new ResponseData instance with the provided data. +// +// Parameters: +// - statusCode: the status code of the response +// - headers: the headers of the response +// - successBody: the success body of the response +// - errorBody: the error body of the response +// - err: the error associated with the response +// +// Returns a ResponseData instance. +func newResponseData[T any, E any](statusCode int, headers http.Header, successBody *T, errorBody *E, err error) ResponseData[T, E] { + return &responseData[T, E]{ + statusCode: statusCode, + headers: headers, + successBody: successBody, + errorBody: errorBody, + err: err, + } } -func newResponseData[Body any](status int, body *Body, header http.Header, err error) ResponseData[Body] { - return &responseData[Body]{status: status, body: body, header: header, err: err} +// responseData implements the ResponseData interface. +type responseData[T any, E any] struct { + statusCode int + headers http.Header + successBody *T + errorBody *E + err error } -type responseData[B any] struct { - status int - body *B - header http.Header - err error +// StatusCode returns the status code of the response. +// +// No parameters. +// Returns an integer. +func (r *responseData[T, E]) StatusCode() int { + return r.statusCode } -func (r *responseData[B]) Status() int { - return r.status +// Headers returns the headers of the response. +// +// No parameters. +// Returns http.Header. +func (r *responseData[T, E]) Headers() http.Header { + return r.headers } -func (r *responseData[B]) Body() *B { - return r.body +// SuccessBody returns the success body of the response. +// +// No parameters. +// Returns a pointer to the success body type. +func (r *responseData[T, E]) SuccessBody() *T { + return r.successBody } -func (r *responseData[B]) Header() http.Header { - return r.header +// ErrorBody returns the error body of the response. +// +// No parameters. +// Returns a pointer to the error body. +func (r *responseData[T, E]) ErrorBody() *E { + return r.errorBody } -func (r *responseData[B]) Error() error { +// Error returns the error associated with the response. +// +// No parameters. +// Returns an error. +func (r *responseData[T, E]) Error() error { return r.err } + +// IsInformationalResponse checks if the response status code is in the informational range (100-199). +// +// No parameters. +// Returns a boolean. +func (r *responseData[T, E]) IsInformationalResponse() bool { + return r.statusCode >= 100 && r.statusCode <= 199 +} + +// IsSuccessfulResponse checks if the response status code is within the successful range (200-299). +// +// No parameters. +// Returns a boolean. +func (r *responseData[T, E]) IsSuccessfulResponse() bool { + return r.statusCode >= 200 && r.statusCode <= 299 +} + +// IsRedirectionMessage checks if the response status code is in the redirection range (300-399). +// +// No parameters. +// Returns a boolean. +func (r *responseData[T, E]) IsRedirectionMessage() bool { + return r.statusCode >= 300 && r.statusCode <= 399 +} + +// IsClientErrorResponse checks if the response status code is within the client error range (400-499). +// +// No parameters. +// Returns a boolean. +func (r *responseData[T, E]) IsClientErrorResponse() bool { + return r.statusCode >= 400 && r.statusCode <= 499 +} + +// IsServerErrorResponse checks if the response status code is within the server error range (500-599). +// +// No parameters. +// Returns a boolean. +func (r *responseData[T, E]) IsServerErrorResponse() bool { + return r.statusCode >= 500 && r.statusCode <= 599 +} + +// HasError checks if the response has an error based on the presence of err or errorBody. +// No parameters. +// Returns a boolean. +func (r *responseData[T, E]) HasError() bool { + return r.err != nil || r.errorBody != nil +} + +// HasSuccess checks if the response has no errors and no error body. +// +// No parameters. +// Returns a boolean. +func (r *responseData[T, E]) HasSuccess() bool { + return r.err == nil && r.errorBody == nil +} diff --git a/pkg/web/restclient/response_error_data.go b/pkg/web/restclient/response_error_data.go new file mode 100644 index 0000000..295f739 --- /dev/null +++ b/pkg/web/restclient/response_error_data.go @@ -0,0 +1,6 @@ +package restclient + +// ResponseErrorData interface to encapsulate response error data +type ResponseErrorData interface { + any +} diff --git a/pkg/web/restclient/response_success_data.go b/pkg/web/restclient/response_success_data.go new file mode 100644 index 0000000..7772117 --- /dev/null +++ b/pkg/web/restclient/response_success_data.go @@ -0,0 +1,6 @@ +package restclient + +// ResponseSuccessData interface to encapsulate response success data +type ResponseSuccessData interface { + any +} diff --git a/pkg/web/restserver/route.go b/pkg/web/restserver/route.go index 065b20c..d7f77e4 100644 --- a/pkg/web/restserver/route.go +++ b/pkg/web/restserver/route.go @@ -33,7 +33,7 @@ type healtCheck struct { } func addHealthCheckRoute() { - const route = "/management/health" + const route = "/health" srvRoutes = append(srvRoutes, Route{ URI: route, Method: http.MethodGet, @@ -47,7 +47,7 @@ func addHealthCheckRoute() { func addDocumentationRoute() { if slices.Contains([]string{config.ENVIRONMENT_SANDBOX, config.ENVIRONMENT_DEVELOPMENT}, config.ENVIRONMENT) { - const route = "/v2/api-docs" + const route = "/api-docs" srvRoutes = append(srvRoutes, Route{ URI: route, Method: http.MethodGet, diff --git a/pkg/web/restserver/server_test.go b/pkg/web/restserver/server_test.go index ee55fe1..2f2be1d 100644 --- a/pkg/web/restserver/server_test.go +++ b/pkg/web/restserver/server_test.go @@ -57,6 +57,7 @@ func beforeEnterApply(ctx WebContext) *MiddlewareError { } func TestStartRestServer(t *testing.T) { + ctx := context.Background() test.InitializeBaseTest() type Resp struct { @@ -269,204 +270,418 @@ func TestStartRestServer(t *testing.T) { Use(&customMiddlewareTest{}) go ListenAndServe() time.Sleep(1 * time.Second) - client := restclient.NewRestClient("test-server", fmt.Sprintf("http://localhost:%d", config.PORT), 1) + + baseURL := fmt.Sprintf("http://localhost:%d", config.PORT) + client := restclient.NewRestClient(&restclient.RestClientConfig{ + Name: "test-server", + BaseURL: baseURL, + Timeout: 100, + }) t.Run("Should return status 200 (OK) in health-check", func(t *testing.T) { - resp := restclient.Get[healtCheck](context.Background(), client, "/management/health", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.Equal(t, "OK", resp.Body().Status) + response := restclient.Request[healtCheck, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/health", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.Equal(t, "OK", response.SuccessBody().Status) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return error 404 (not found) when endpoint not exists", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/not-exists-endpoint", nil) - assert.NotNil(t, resp) - assert.Error(t, resp.Error(), "404 statusCode") - assert.Nil(t, resp.Body()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/not-exists-endpoint", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusNotFound, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.Error(t, response.Error(), "404 staus code") }) t.Run("Should return 200 (OK) in public api", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/public/test-public-endpoint", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, "test-public-endpoint", resp.Body().Msg) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/public/test-public-endpoint", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, "test-public-endpoint", response.SuccessBody().Msg) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return 200 (OK) in private api", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/private/test-private-endpoint/abc", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, "abc", resp.Body().Msg) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/private/test-private-endpoint/abc", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, "abc", response.SuccessBody().Msg) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return error 401 (Unauthorized) when not inform the userId in authenticated api", func(t *testing.T) { - r := restclient.Get[Resp](context.Background(), client, "/api/test-authenticated-endpoint/abc", map[string]string{tenantIDHeader: TenantId}) - assert.NotNil(t, r) - assert.Error(t, r.Error(), "401 statusCode") - assert.Nil(t, r.Body()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/api/test-authenticated-endpoint/abc", + Headers: map[string]string{tenantIDHeader: TenantId}, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusUnauthorized, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.NotNil(t, response.ErrorBody()) + assert.Error(t, errors.New("401 statusCode"), response.Error()) }) t.Run("Should return error 401 (Unauthorized) when not inform the tenantId in authenticated api", func(t *testing.T) { - r := restclient.Get[Resp](context.Background(), client, "/api/test-authenticated-endpoint/abc", map[string]string{userIDHeader: UserId}) - assert.NotNil(t, r) - assert.Error(t, r.Error(), "401 statusCode") - assert.Nil(t, r.Body()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/api/test-authenticated-endpoint/abc", + Headers: map[string]string{userIDHeader: UserId}, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusUnauthorized, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.NotNil(t, response.ErrorBody()) + assert.Error(t, errors.New("401 statusCode"), response.Error()) }) t.Run("Should return error 401 (Unauthorized) when not inform the credentials in authenticated api", func(t *testing.T) { - r := restclient.Get[Resp](context.Background(), client, "/api/test-authenticated-endpoint/abc", nil) - assert.NotNil(t, r) - assert.Error(t, r.Error(), "401 statusCode") - assert.Nil(t, r.Body()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/api/test-authenticated-endpoint/abc", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusUnauthorized, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.NotNil(t, response.ErrorBody()) + assert.Error(t, errors.New("401 statusCode"), response.Error()) }) t.Run("Should return 200 (OK) in authenticated api", func(t *testing.T) { - r := restclient.Get[Resp](context.Background(), client, "/api/test-authenticated-endpoint/abc", map[string]string{tenantIDHeader: TenantId, userIDHeader: UserId}) - assert.NotNil(t, r) - assert.NoError(t, r.Error()) - assert.NotNil(t, r.Body()) - assert.Equal(t, "abc", r.Body().Msg) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/api/test-authenticated-endpoint/abc", + Headers: map[string]string{tenantIDHeader: TenantId, userIDHeader: UserId}, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, "abc", response.SuccessBody().Msg) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return 200 (OK) in no prefix api", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/test-no-prefix-endpoint", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, "test-no-prefix-endpoint", resp.Body().Msg) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-no-prefix-endpoint", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, "test-no-prefix-endpoint", response.SuccessBody().Msg) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return error response body", func(t *testing.T) { - resp := restclient.Get[Error](context.Background(), client, "/test-error-body", nil) - assert.NotNil(t, resp) - assert.Error(t, resp.Error(), "500 statusCode") - assert.Nil(t, resp.Body()) + response := restclient.Request[Error, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-error-body", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusInternalServerError, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.Error(t, errors.New("500 statusCode"), response.Error()) }) t.Run("Should return empty response body", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/test-empty-body", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.Nil(t, resp.Body()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-empty-body", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusNoContent, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return request header", func(t *testing.T) { - expected := []string{"123"} - resp := restclient.Get[[]string](context.Background(), client, "/test-request-header", map[string]string{"X-Id": "123"}) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.Equal(t, &expected, resp.Body()) + expected := &[]string{"123"} + + response := restclient.Request[[]string, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-request-header", + Headers: map[string]string{"X-Id": "123"}, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return request headers", func(t *testing.T) { expected := []string{"456"} - resp := restclient.Get[map[string][]string](context.Background(), client, "/test-request-headers", map[string]string{"X-Id": "456"}) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, expected, (*resp.Body())["X-Id"]) + + response := restclient.Request[map[string][]string, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-request-headers", + Headers: map[string]string{"X-Id": "456"}, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, (*response.SuccessBody())["X-Id"]) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return query param", func(t *testing.T) { expected := "10" - resp := restclient.Get[string](context.Background(), client, "/test-query-param?size=10", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, expected, *resp.Body()) + + response := restclient.Request[string, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-query-param?size=10", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, *response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return query array param", func(t *testing.T) { expected := []string{"10", "20", "30"} - resp := restclient.Get[[]string](context.Background(), client, "/test-query-array-param?page=1&idList=10,20,30", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, expected, *resp.Body()) + + response := restclient.Request[[]string, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-query-array-param?page=1&idList=10,20,30", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, *response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return decoded query param", func(t *testing.T) { expected := &Query{Msg: "decoded", Size: 10} - resp := restclient.Get[Query](context.Background(), client, "/test-decode-query-params?size=10&msg=decoded", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, expected, resp.Body()) + + response := restclient.Request[Query, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-decode-query-params?size=10&msg=decoded", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return bad request when an error occurred in decoded body", func(t *testing.T) { - resp := restclient.Post[Resp](context.Background(), client, "/test-decode-body", &Resp{}, nil) - assert.NotNil(t, resp) - assert.Error(t, resp.Error()) - assert.Nil(t, resp.Body()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodPost, + Path: "/test-decode-body", + Body: &Resp{}, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusInternalServerError, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.ErrorContains(t, response.Error(), "500 status code.") }) t.Run("Should return decoded body", func(t *testing.T) { expected := &Resp{Msg: "decoded"} - resp := restclient.Post[Resp](context.Background(), client, "/test-decode-body", expected, nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, expected, resp.Body()) + + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodPost, + Path: "/test-decode-body", + Body: expected, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return context", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/test-context", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.NotEmpty(t, resp.Body().Msg) + expected := &Resp{Msg: "context.Background"} + + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-context", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return server file", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/test-serve-file", nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, "test file", resp.Body().Msg) - }) - - t.Run("Should return error 200 using raw body", func(t *testing.T) { - body := "text message" - resp := restclient.Post[Resp](context.Background(), client, "/public/test-public-endpoint", &body, nil) - assert.NotNil(t, resp) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, "\"text message\"", resp.Body().Msg) + expected := &Resp{Msg: "test file"} + + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/test-serve-file", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("should validate custom middleware with error", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/public/middleware/conflict", nil) - assert.NotNil(t, resp) - assert.Equal(t, "409 statusCode", resp.Error().Error()) - assert.Equal(t, http.StatusConflict, resp.Status()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/public/middleware/conflict", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusConflict, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.NotNil(t, response.ErrorBody()) + assert.ErrorContains(t, response.Error(), "409 status code") }) t.Run("should validate custom middleware with success", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/public/middleware/success", nil) - assert.NotNil(t, resp) - assert.Equal(t, http.StatusOK, resp.Status()) - assert.Equal(t, "success", resp.Body().Msg) + expected := &Resp{Msg: "success"} + + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/public/middleware/success", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("should validate hook before enter route with error", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/public/hook/conflict", nil) - assert.NotNil(t, resp) - assert.Equal(t, "409 statusCode", resp.Error().Error()) - assert.Equal(t, http.StatusConflict, resp.Status()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/public/hook/conflict", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusConflict, response.StatusCode()) + assert.Nil(t, response.SuccessBody()) + assert.NotNil(t, response.ErrorBody()) + assert.ErrorContains(t, response.Error(), "409 status code") }) t.Run("should validate hook before enter route with success", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/public/hook/success", nil) - assert.NotNil(t, resp) - assert.Equal(t, http.StatusOK, resp.Status()) - assert.Equal(t, "success", resp.Body().Msg) + expected := &Resp{Msg: "success"} + + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/public/hook/success", + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.EqualValues(t, expected, response.SuccessBody()) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) } func TestStartRestServerCustomAuthMiddleware(t *testing.T) { + ctx := context.Background() test.InitializeBaseTest() type Resp struct { @@ -499,19 +714,39 @@ func TestStartRestServerCustomAuthMiddleware(t *testing.T) { CustomAuthMiddleware(&customAuthenticationContextMiddleware{}) go ListenAndServe() time.Sleep(1 * time.Second) - client := restclient.NewRestClient("test-server", fmt.Sprintf("http://localhost:%d", config.PORT), 1) + client := restclient.NewRestClient(&restclient.RestClientConfig{ + Name: "test-server", + BaseURL: fmt.Sprintf("http://localhost:%d", config.PORT), + Timeout: 1, + }) t.Run("Should return status 200", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/api/users", map[string]string{"Authorization": "abcd1234"}) - assert.NoError(t, resp.Error()) - assert.NotNil(t, resp.Body()) - assert.Equal(t, "test-custom-authentication-middleware", resp.Body().Msg) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/api/users", + Headers: map[string]string{"Authorization": "abcd1234"}, + }.Call() + + assert.NotNil(t, response) + assert.EqualValues(t, http.StatusOK, response.StatusCode()) + assert.NotNil(t, response.SuccessBody()) + assert.Equal(t, "test-custom-authentication-middleware", response.SuccessBody().Msg) + assert.Nil(t, response.ErrorBody()) + assert.NoError(t, response.Error()) }) t.Run("Should return error 401", func(t *testing.T) { - resp := restclient.Get[Resp](context.Background(), client, "/api/users", nil) - assert.NotNil(t, resp) - assert.Error(t, resp.Error(), "401 statusCode") - assert.Nil(t, resp.Body()) + response := restclient.Request[Resp, any]{ + Ctx: ctx, + Client: client, + HttpMethod: http.MethodGet, + Path: "/api/users", + }.Call() + + assert.NotNil(t, response) + assert.Error(t, response.Error(), "401 statusCode") + assert.Nil(t, response.SuccessBody()) }) }