From 778d5ef146736ec543b85823e3864d24d3fa0d2c Mon Sep 17 00:00:00 2001 From: Anatoliy Antonov Date: Wed, 26 Mar 2025 17:14:00 +0300 Subject: [PATCH] feat: it works --- api/rest/gsn.swagger.yml | 5 +- cmd/api/main.go | 45 ++++++++++- go.mod | 6 ++ go.sum | 27 +++++++ internal/{ => pkg}/ds/ds.go | 10 ++- internal/pkg/errcodes/errcodes.go | 25 ++++++ internal/repository/config.go | 23 ++++++ internal/repository/queries/queries.sql | 5 +- internal/repository/repository.go | 17 ++--- internal/repository/satellites.go | 27 +++++++ internal/repository/sqlc/queries.sql.go | 32 +++++--- internal/service/deps.go | 12 +++ internal/service/deps_mock.go | 51 +++++++++++++ internal/service/satellites.go | 11 +++ internal/service/satellites_test.go | 66 ++++++++++++++++ internal/service/service.go | 11 +++ internal/service/service_test.go | 27 +++++++ internal/transport/rest/config.go | 23 ++++++ internal/transport/rest/deps.go | 4 - internal/transport/rest/handler.go | 43 ----------- internal/transport/rest/handler/deps.go | 11 +++ internal/transport/rest/handler/handler.go | 89 ++++++++++++++++++++++ internal/transport/rest/transport.go | 33 ++++++++ pkg/rest/oas_json_gen.go | 75 +++++++++++++----- pkg/rest/oas_schemas_gen.go | 66 +++++++++++++--- 25 files changed, 638 insertions(+), 106 deletions(-) rename internal/{ => pkg}/ds/ds.go (62%) create mode 100644 internal/pkg/errcodes/errcodes.go create mode 100644 internal/repository/config.go create mode 100644 internal/repository/satellites.go create mode 100644 internal/service/deps.go create mode 100644 internal/service/deps_mock.go create mode 100644 internal/service/satellites.go create mode 100644 internal/service/satellites_test.go create mode 100644 internal/service/service.go create mode 100644 internal/service/service_test.go create mode 100644 internal/transport/rest/config.go delete mode 100644 internal/transport/rest/deps.go delete mode 100644 internal/transport/rest/handler.go create mode 100644 internal/transport/rest/handler/deps.go create mode 100644 internal/transport/rest/handler/handler.go create mode 100644 internal/transport/rest/transport.go diff --git a/api/rest/gsn.swagger.yml b/api/rest/gsn.swagger.yml index 7b410c9..e29133d 100644 --- a/api/rest/gsn.swagger.yml +++ b/api/rest/gsn.swagger.yml @@ -225,10 +225,9 @@ components: Error: type: object required: - - code - message properties: - code: - type: integer message: type: string + details: + type: string diff --git a/cmd/api/main.go b/cmd/api/main.go index 66a515c..1653863 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -1,7 +1,48 @@ package main -import "fmt" +import ( + "log" + + "git.intra.yksa.space/gsn/gsn-proxy/internal/repository" + "git.intra.yksa.space/gsn/gsn-proxy/internal/service" + "git.intra.yksa.space/gsn/gsn-proxy/internal/transport/rest" + "git.intra.yksa.space/gsn/gsn-proxy/internal/transport/rest/handler" +) + +const ( + servicePrefix = "GSN" +) func main() { - fmt.Println("gsn") + repoConfig, err := repository.NewConfig(servicePrefix) + if err != nil { + log.Fatal(err) + } + + repo, err := repository.New(repoConfig) + if err != nil { + log.Fatal(err) + } + + svc := service.New(repo) + + handler := handler.New(svc) + + restConfig, err := rest.NewConfig(servicePrefix) + if err != nil { + log.Fatal(err) + } + + transport, err := rest.New(handler, restConfig) + if err != nil { + log.Fatal(err) + } + + for { + transport.Run() + + if r := recover(); r != nil { + log.Println("panic occured: ", r) + } + } } diff --git a/go.mod b/go.mod index de5c027..fad7fae 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,14 @@ go 1.23.0 toolchain go1.23.7 require ( + github.com/caarlos0/env/v11 v11.3.1 github.com/go-faster/errors v0.7.1 github.com/go-faster/jx v1.1.0 + github.com/golang/mock v1.6.0 github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.7.4 github.com/ogen-go/ogen v1.10.1 + github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.35.0 go.opentelemetry.io/otel/metric v1.35.0 go.opentelemetry.io/otel/trace v1.35.0 @@ -17,6 +20,7 @@ require ( ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/fatih/color v1.18.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect @@ -27,6 +31,7 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/segmentio/asm v1.2.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.uber.org/zap v1.27.0 // indirect @@ -37,4 +42,5 @@ require ( golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 645905f..89c5740 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= +github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= 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= @@ -18,6 +20,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -52,6 +56,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= @@ -66,20 +71,42 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/ds/ds.go b/internal/pkg/ds/ds.go similarity index 62% rename from internal/ds/ds.go rename to internal/pkg/ds/ds.go index 4f96351..eb0067f 100644 --- a/internal/ds/ds.go +++ b/internal/pkg/ds/ds.go @@ -1,6 +1,8 @@ package ds -import "github.com/google/uuid" +import ( + "github.com/google/uuid" +) type Status string @@ -15,3 +17,9 @@ type Station struct { Slug string Status Status } + +type Satellite struct { + ID uuid.UUID + DisplayName string + Status Status +} diff --git a/internal/pkg/errcodes/errcodes.go b/internal/pkg/errcodes/errcodes.go new file mode 100644 index 0000000..245f508 --- /dev/null +++ b/internal/pkg/errcodes/errcodes.go @@ -0,0 +1,25 @@ +package errcodes + +import ( + "strings" +) + +type ErrorCode struct { + StatusCode int + Message string + Details string +} + +var errorCodeCounter int32 + +func New(statusCode int, message string, details ...string) *ErrorCode { + return &ErrorCode{ + StatusCode: statusCode, + Message: message, + Details: strings.Join(details, " "), + } +} + +func (e *ErrorCode) Error() string { + return e.Message +} diff --git a/internal/repository/config.go b/internal/repository/config.go new file mode 100644 index 0000000..bb2423c --- /dev/null +++ b/internal/repository/config.go @@ -0,0 +1,23 @@ +package repository + +import ( + "fmt" + + env "github.com/caarlos0/env/v11" +) + +type Config struct { + ConnStr string `env:"CONNSTR" envDefault:"postgres://gsn:gsn@localhost:5432/gsn?sslmode=disable"` +} + +func NewConfig(servicePrefix string) (*Config, error) { + cfg := &Config{} + + if err := env.ParseWithOptions(cfg, env.Options{ + PrefixTagName: fmt.Sprintf("%s_REPOSITORY_", servicePrefix), + }); err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/internal/repository/queries/queries.sql b/internal/repository/queries/queries.sql index d83310d..2b20e62 100644 --- a/internal/repository/queries/queries.sql +++ b/internal/repository/queries/queries.sql @@ -1,4 +1,3 @@ --- name: GetStationByID :one +-- name: GetSatellites :many select * -from stations -where id = @id; \ No newline at end of file +from satellites; \ No newline at end of file diff --git a/internal/repository/repository.go b/internal/repository/repository.go index a49a0b2..a24337d 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -3,24 +3,23 @@ package repository import ( "context" - "git.intra.yksa.space/gsn/gsn-proxy/internal/ds" "git.intra.yksa.space/gsn/gsn-proxy/internal/repository/sqlc" - "github.com/google/uuid" + "github.com/jackc/pgx/v5" ) type Repository struct { queries *sqlc.Queries + cfg *Config } -func (r *Repository) GetStationByID(ctx context.Context, ID uuid.UUID) (ds.Station, error) { - ret, err := r.queries.GetStationByID(ctx, UUIDToPg(ID)) +func New(cfg *Config) (*Repository, error) { + conn, err := pgx.Connect(context.Background(), cfg.ConnStr) if err != nil { - return ds.Station{}, err + return nil, err } - return ds.Station{ - ID: PGToUUID(ret.ID), - Slug: ret.Slug, - Status: ds.Status(ret.Status), + return &Repository{ + queries: sqlc.New(conn), + cfg: cfg, }, nil } diff --git a/internal/repository/satellites.go b/internal/repository/satellites.go new file mode 100644 index 0000000..6fbab41 --- /dev/null +++ b/internal/repository/satellites.go @@ -0,0 +1,27 @@ +package repository + +import ( + "context" + "net/http" + + "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds" + "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/errcodes" +) + +func (r *Repository) GetSatellites(ctx context.Context) ([]ds.Satellite, error) { + satellites, err := r.queries.GetSatellites(ctx) + if err != nil { + return nil, errcodes.New(http.StatusInternalServerError, "failed to get satellites", err.Error()) + } + + ret := make([]ds.Satellite, 0, len(satellites)) + for _, val := range satellites { + ret = append(ret, ds.Satellite{ + ID: PGToUUID(val.ID), + DisplayName: val.DisplayName, + Status: ds.Status(val.Status), + }) + } + + return ret, nil +} diff --git a/internal/repository/sqlc/queries.sql.go b/internal/repository/sqlc/queries.sql.go index b561785..6b08b15 100644 --- a/internal/repository/sqlc/queries.sql.go +++ b/internal/repository/sqlc/queries.sql.go @@ -7,19 +7,29 @@ package sqlc import ( "context" - - "github.com/jackc/pgx/v5/pgtype" ) -const getStationByID = `-- name: GetStationByID :one -select id, slug, status -from stations -where id = $1 +const getSatellites = `-- name: GetSatellites :many +select id, display_name, status +from satellites ` -func (q *Queries) GetStationByID(ctx context.Context, id pgtype.UUID) (Station, error) { - row := q.db.QueryRow(ctx, getStationByID, id) - var i Station - err := row.Scan(&i.ID, &i.Slug, &i.Status) - return i, err +func (q *Queries) GetSatellites(ctx context.Context) ([]Satellite, error) { + rows, err := q.db.Query(ctx, getSatellites) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Satellite + for rows.Next() { + var i Satellite + if err := rows.Scan(&i.ID, &i.DisplayName, &i.Status); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil } diff --git a/internal/service/deps.go b/internal/service/deps.go new file mode 100644 index 0000000..3722d9e --- /dev/null +++ b/internal/service/deps.go @@ -0,0 +1,12 @@ +package service + +import ( + "context" + + "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds" +) + +//go:generate mockgen -source=deps.go -destination=deps_mock.go -package=service +type Repository interface { + GetSatellites(ctx context.Context) ([]ds.Satellite, error) +} diff --git a/internal/service/deps_mock.go b/internal/service/deps_mock.go new file mode 100644 index 0000000..2d09f83 --- /dev/null +++ b/internal/service/deps_mock.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: deps.go + +// Package service is a generated GoMock package. +package service + +import ( + context "context" + reflect "reflect" + + ds "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds" + gomock "github.com/golang/mock/gomock" +) + +// MockRepository is a mock of Repository interface. +type MockRepository struct { + ctrl *gomock.Controller + recorder *MockRepositoryMockRecorder +} + +// MockRepositoryMockRecorder is the mock recorder for MockRepository. +type MockRepositoryMockRecorder struct { + mock *MockRepository +} + +// NewMockRepository creates a new mock instance. +func NewMockRepository(ctrl *gomock.Controller) *MockRepository { + mock := &MockRepository{ctrl: ctrl} + mock.recorder = &MockRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { + return m.recorder +} + +// GetSatellites mocks base method. +func (m *MockRepository) GetSatellites(ctx context.Context) ([]ds.Satellite, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSatellites", ctx) + ret0, _ := ret[0].([]ds.Satellite) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSatellites indicates an expected call of GetSatellites. +func (mr *MockRepositoryMockRecorder) GetSatellites(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSatellites", reflect.TypeOf((*MockRepository)(nil).GetSatellites), ctx) +} diff --git a/internal/service/satellites.go b/internal/service/satellites.go new file mode 100644 index 0000000..df5e629 --- /dev/null +++ b/internal/service/satellites.go @@ -0,0 +1,11 @@ +package service + +import ( + "context" + + "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds" +) + +func (s *Service) GetSatellites(ctx context.Context) ([]ds.Satellite, error) { + return s.repo.GetSatellites(ctx) +} diff --git a/internal/service/satellites_test.go b/internal/service/satellites_test.go new file mode 100644 index 0000000..f9e4341 --- /dev/null +++ b/internal/service/satellites_test.go @@ -0,0 +1,66 @@ +package service + +import ( + "context" + "reflect" + "testing" + + "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds" + "github.com/golang/mock/gomock" + "github.com/google/uuid" + "github.com/stretchr/testify/require" +) + +func Test_GetSatellites(t *testing.T) { + suite := NewSuite(t) + + var ( + commonSatelliteID = uuid.New() + commonDisplayName = "test" + commonStatus = ds.StatusActive + ) + + testCases := []struct { + name string + mock func() + expected []ds.Satellite + wantErr error + }{ + { + name: "success", + mock: func() { + suite.mockRepo.EXPECT().GetSatellites(gomock.Any()).Return([]ds.Satellite{ + { + ID: commonSatelliteID, + DisplayName: commonDisplayName, + Status: commonStatus, + }, + }, nil) + }, + expected: []ds.Satellite{ + { + ID: commonSatelliteID, + DisplayName: commonDisplayName, + Status: commonStatus, + }, + }, + wantErr: nil, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + if tt.mock != nil { + tt.mock() + } + + result, err := suite.svc.GetSatellites(context.Background()) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + require.True(t, reflect.DeepEqual(tt.expected, result)) + } + }) + } +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 0000000..d4e128c --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,11 @@ +package service + +type Service struct { + repo Repository +} + +func New(repo Repository) *Service { + return &Service{ + repo: repo, + } +} diff --git a/internal/service/service_test.go b/internal/service/service_test.go new file mode 100644 index 0000000..ff85a24 --- /dev/null +++ b/internal/service/service_test.go @@ -0,0 +1,27 @@ +package service + +import ( + "testing" + + "github.com/golang/mock/gomock" +) + +type TestSuite struct { + svc *Service + mockRepo *MockRepository +} + +func NewSuite(t *testing.T) *TestSuite { + t.Helper() + + ctrl := gomock.NewController(t) + + mockRepo := NewMockRepository(ctrl) + + svc := New(mockRepo) + + return &TestSuite{ + svc: svc, + mockRepo: mockRepo, + } +} diff --git a/internal/transport/rest/config.go b/internal/transport/rest/config.go new file mode 100644 index 0000000..2cddd06 --- /dev/null +++ b/internal/transport/rest/config.go @@ -0,0 +1,23 @@ +package rest + +import ( + "fmt" + + env "github.com/caarlos0/env/v11" +) + +type Config struct { + Port int `env:"PORT" envDefault:"8080"` +} + +func NewConfig(servicePrefix string) (*Config, error) { + cfg := &Config{} + + if err := env.ParseWithOptions(cfg, env.Options{ + PrefixTagName: fmt.Sprintf("%s_REST_", servicePrefix), + }); err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/internal/transport/rest/deps.go b/internal/transport/rest/deps.go deleted file mode 100644 index ecfb3df..0000000 --- a/internal/transport/rest/deps.go +++ /dev/null @@ -1,4 +0,0 @@ -package rest - -type Service interface { -} diff --git a/internal/transport/rest/handler.go b/internal/transport/rest/handler.go deleted file mode 100644 index fb29d87..0000000 --- a/internal/transport/rest/handler.go +++ /dev/null @@ -1,43 +0,0 @@ -package rest - -import ( - "context" - - api "git.intra.yksa.space/gsn/gsn-proxy/pkg/rest" -) - -var ( - _ api.Handler = (*Handler)(nil) -) - -type Handler struct { - svc Service -} - -func (h *Handler) GetSatellites(ctx context.Context) (*api.GetSatellitesOK, error) { - return nil, nil -} - -func (h *Handler) GetStations(ctx context.Context) (*api.GetStationsOK, error) { - return nil, nil -} - -func (h *Handler) GetSubscriptions(ctx context.Context) (*api.GetSubscriptionsOK, error) { - return nil, nil -} - -func (h *Handler) SubscribeSatellite(ctx context.Context, req *api.SubscribeSatelliteReq) (*api.SubscribeSatelliteOK, error) { - return nil, nil -} - -func (h *Handler) SubscribeStation(ctx context.Context, req *api.SubscribeStationReq) (*api.SubscribeStationOK, error) { - return nil, nil -} - -func (h *Handler) Unsubscribe(ctx context.Context, params api.UnsubscribeParams) error { - return nil -} - -func (h *Handler) NewError(ctx context.Context, err error) *api.ErrorStatusCode { - return nil -} diff --git a/internal/transport/rest/handler/deps.go b/internal/transport/rest/handler/deps.go new file mode 100644 index 0000000..8ed92ff --- /dev/null +++ b/internal/transport/rest/handler/deps.go @@ -0,0 +1,11 @@ +package handler + +import ( + "context" + + "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds" +) + +type Service interface { + GetSatellites(ctx context.Context) ([]ds.Satellite, error) +} diff --git a/internal/transport/rest/handler/handler.go b/internal/transport/rest/handler/handler.go new file mode 100644 index 0000000..cd39b9e --- /dev/null +++ b/internal/transport/rest/handler/handler.go @@ -0,0 +1,89 @@ +package handler + +import ( + "context" + "fmt" + "net/http" + + "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/errcodes" + api "git.intra.yksa.space/gsn/gsn-proxy/pkg/rest" +) + +var ( + _ api.Handler = (*Handler)(nil) +) + +type Handler struct { + svc Service +} + +func New(svc Service) *Handler { + return &Handler{ + svc: svc, + } +} + +func (h *Handler) GetSatellites(ctx context.Context) (*api.GetSatellitesOK, error) { + satellites, err := h.svc.GetSatellites(ctx) + if err != nil { + return nil, err + } + + ret := make([]api.GetSatellitesOKSatellitesItem, 0, len(satellites)) + for _, val := range satellites { + ret = append(ret, api.GetSatellitesOKSatellitesItem{ + ID: val.ID, + DisplayName: val.DisplayName, + Status: api.GetSatellitesOKSatellitesItemStatus(val.Status), + }) + } + + return &api.GetSatellitesOK{ + Satellites: ret, + }, nil +} + +func (h *Handler) GetStations(ctx context.Context) (*api.GetStationsOK, error) { + return nil, nil +} + +func (h *Handler) GetSubscriptions(ctx context.Context) (*api.GetSubscriptionsOK, error) { + return nil, nil +} + +func (h *Handler) SubscribeSatellite(ctx context.Context, req *api.SubscribeSatelliteReq) (*api.SubscribeSatelliteOK, error) { + return nil, nil +} + +func (h *Handler) SubscribeStation(ctx context.Context, req *api.SubscribeStationReq) (*api.SubscribeStationOK, error) { + return nil, nil +} + +func (h *Handler) Unsubscribe(ctx context.Context, params api.UnsubscribeParams) error { + return nil +} + +func (h *Handler) NewError(ctx context.Context, err error) *api.ErrorStatusCode { + if errcode, ok := err.(*errcodes.ErrorCode); ok { + resp := api.Error{ + Message: errcode.Message, + } + + if errcode.Details != "" { + resp.Details = api.NewOptString(errcode.Details) + } + + return &api.ErrorStatusCode{ + StatusCode: errcode.StatusCode, + Response: resp, + } + } + + return &api.ErrorStatusCode{ + StatusCode: http.StatusInternalServerError, + Response: api.Error{ + Message: fmt.Sprintf("undefined internal error"), + Details: api.NewOptString(err.Error()), + }, + } +} diff --git a/internal/transport/rest/transport.go b/internal/transport/rest/transport.go new file mode 100644 index 0000000..157f24b --- /dev/null +++ b/internal/transport/rest/transport.go @@ -0,0 +1,33 @@ +package rest + +import ( + "fmt" + "log" + "net/http" + + handler "git.intra.yksa.space/gsn/gsn-proxy/internal/transport/rest/handler" + api "git.intra.yksa.space/gsn/gsn-proxy/pkg/rest" +) + +type Transport struct { + cfg *Config + srv *api.Server +} + +func New(handler *handler.Handler, cfg *Config) (*Transport, error) { + srv, err := api.NewServer(handler) + if err != nil { + return nil, err + } + + return &Transport{ + srv: srv, + cfg: cfg, + }, nil +} + +func (t *Transport) Run() { + if err := http.ListenAndServe(fmt.Sprintf(":%d", t.cfg.Port), t.srv); err != nil { + log.Panic(err) + } +} diff --git a/pkg/rest/oas_json_gen.go b/pkg/rest/oas_json_gen.go index 813c5f7..16b7789 100644 --- a/pkg/rest/oas_json_gen.go +++ b/pkg/rest/oas_json_gen.go @@ -22,19 +22,21 @@ func (s *Error) Encode(e *jx.Encoder) { // encodeFields encodes fields. func (s *Error) encodeFields(e *jx.Encoder) { - { - e.FieldStart("code") - e.Int(s.Code) - } { e.FieldStart("message") e.Str(s.Message) } + { + if s.Details.Set { + e.FieldStart("details") + s.Details.Encode(e) + } + } } var jsonFieldsNameOfError = [2]string{ - 0: "code", - 1: "message", + 0: "message", + 1: "details", } // Decode decodes Error from json. @@ -46,20 +48,8 @@ func (s *Error) Decode(d *jx.Decoder) error { if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { switch string(k) { - case "code": - requiredBitSet[0] |= 1 << 0 - if err := func() error { - v, err := d.Int() - s.Code = int(v) - if err != nil { - return err - } - return nil - }(); err != nil { - return errors.Wrap(err, "decode field \"code\"") - } case "message": - requiredBitSet[0] |= 1 << 1 + requiredBitSet[0] |= 1 << 0 if err := func() error { v, err := d.Str() s.Message = string(v) @@ -70,6 +60,16 @@ func (s *Error) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"message\"") } + case "details": + if err := func() error { + s.Details.Reset() + if err := s.Details.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"details\"") + } default: return d.Skip() } @@ -80,7 +80,7 @@ func (s *Error) Decode(d *jx.Decoder) error { // Validate required fields. var failures []validate.FieldError for i, mask := range [1]uint8{ - 0b00000011, + 0b00000001, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. @@ -977,6 +977,41 @@ func (s *GetSubscriptionsOKSubscriptionsItemType) UnmarshalJSON(data []byte) err return s.Decode(d) } +// Encode encodes string as json. +func (o OptString) Encode(e *jx.Encoder) { + if !o.Set { + return + } + e.Str(string(o.Value)) +} + +// Decode decodes string from json. +func (o *OptString) Decode(d *jx.Decoder) error { + if o == nil { + return errors.New("invalid: unable to decode OptString to nil") + } + o.Set = true + v, err := d.Str() + if err != nil { + return err + } + o.Value = string(v) + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s OptString) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *OptString) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + // Encode implements json.Marshaler. func (s *SubscribeSatelliteOK) Encode(e *jx.Encoder) { e.ObjStart() diff --git a/pkg/rest/oas_schemas_gen.go b/pkg/rest/oas_schemas_gen.go index 27919b3..251ba55 100644 --- a/pkg/rest/oas_schemas_gen.go +++ b/pkg/rest/oas_schemas_gen.go @@ -16,13 +16,8 @@ func (s *ErrorStatusCode) Error() string { // Ref: #/components/schemas/Error type Error struct { - Code int `json:"code"` - Message string `json:"message"` -} - -// GetCode returns the value of Code. -func (s *Error) GetCode() int { - return s.Code + Message string `json:"message"` + Details OptString `json:"details"` } // GetMessage returns the value of Message. @@ -30,9 +25,9 @@ func (s *Error) GetMessage() string { return s.Message } -// SetCode sets the value of Code. -func (s *Error) SetCode(val int) { - s.Code = val +// GetDetails returns the value of Details. +func (s *Error) GetDetails() OptString { + return s.Details } // SetMessage sets the value of Message. @@ -40,6 +35,11 @@ func (s *Error) SetMessage(val string) { s.Message = val } +// SetDetails sets the value of Details. +func (s *Error) SetDetails(val OptString) { + s.Details = val +} + // ErrorStatusCode wraps Error with StatusCode. type ErrorStatusCode struct { StatusCode int @@ -412,6 +412,52 @@ func (s *GetSubscriptionsOKSubscriptionsItemType) UnmarshalText(data []byte) err } } +// NewOptString returns new OptString with value set to v. +func NewOptString(v string) OptString { + return OptString{ + Value: v, + Set: true, + } +} + +// OptString is optional string. +type OptString struct { + Value string + Set bool +} + +// IsSet returns true if OptString was set. +func (o OptString) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptString) Reset() { + var v string + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptString) SetTo(v string) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptString) Get() (v string, ok bool) { + if !o.Set { + return v, false + } + return o.Value, true +} + +// Or returns value if set, or given parameter if does not. +func (o OptString) Or(d string) string { + if v, ok := o.Get(); ok { + return v + } + return d +} + // NewOptUUID returns new OptUUID with value set to v. func NewOptUUID(v uuid.UUID) OptUUID { return OptUUID{