From 6302dd62d6b918889b63639ad3789caef7dbd7bf Mon Sep 17 00:00:00 2001 From: Anatoly Antonov Date: Wed, 26 Mar 2025 15:07:12 +0300 Subject: [PATCH] feat: init --- Makefile | 5 + api/rest/gsn.swagger.yml | 234 +++ cmd/api/main.go | 7 + configs/sqlc.yaml | 10 + go.mod | 40 + go.sum | 90 ++ internal/ds/ds.go | 17 + internal/repository/helpers.go | 22 + .../repository/migrations/0001_init.up.sql | 29 + internal/repository/queries/queries.sql | 4 + internal/repository/repository.go | 26 + internal/repository/sqlc/db.go | 32 + internal/repository/sqlc/models.go | 81 + internal/repository/sqlc/queries.sql.go | 25 + internal/transport/rest/deps.go | 4 + internal/transport/rest/handler.go | 43 + pkg/rest/oas_cfg_gen.go | 283 ++++ pkg/rest/oas_client_gen.go | 573 +++++++ pkg/rest/oas_handlers_gen.go | 868 +++++++++++ pkg/rest/oas_json_gen.go | 1362 +++++++++++++++++ pkg/rest/oas_labeler_gen.go | 42 + pkg/rest/oas_middleware_gen.go | 10 + pkg/rest/oas_operations_gen.go | 15 + pkg/rest/oas_parameters_gen.go | 78 + pkg/rest/oas_request_decoders_gen.go | 142 ++ pkg/rest/oas_request_encoders_gen.go | 40 + pkg/rest/oas_response_decoders_gen.go | 508 ++++++ pkg/rest/oas_response_encoders_gen.go | 118 ++ pkg/rest/oas_router_gen.go | 396 +++++ pkg/rest/oas_schemas_gen.go | 519 +++++++ pkg/rest/oas_server_gen.go | 70 + pkg/rest/oas_unimplemented_gen.go | 76 + pkg/rest/oas_validators_gen.go | 258 ++++ 33 files changed, 6027 insertions(+) create mode 100644 Makefile create mode 100644 api/rest/gsn.swagger.yml create mode 100644 cmd/api/main.go create mode 100644 configs/sqlc.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/ds/ds.go create mode 100644 internal/repository/helpers.go create mode 100644 internal/repository/migrations/0001_init.up.sql create mode 100644 internal/repository/queries/queries.sql create mode 100644 internal/repository/repository.go create mode 100644 internal/repository/sqlc/db.go create mode 100644 internal/repository/sqlc/models.go create mode 100644 internal/repository/sqlc/queries.sql.go create mode 100644 internal/transport/rest/deps.go create mode 100644 internal/transport/rest/handler.go create mode 100644 pkg/rest/oas_cfg_gen.go create mode 100644 pkg/rest/oas_client_gen.go create mode 100644 pkg/rest/oas_handlers_gen.go create mode 100644 pkg/rest/oas_json_gen.go create mode 100644 pkg/rest/oas_labeler_gen.go create mode 100644 pkg/rest/oas_middleware_gen.go create mode 100644 pkg/rest/oas_operations_gen.go create mode 100644 pkg/rest/oas_parameters_gen.go create mode 100644 pkg/rest/oas_request_decoders_gen.go create mode 100644 pkg/rest/oas_request_encoders_gen.go create mode 100644 pkg/rest/oas_response_decoders_gen.go create mode 100644 pkg/rest/oas_response_encoders_gen.go create mode 100644 pkg/rest/oas_router_gen.go create mode 100644 pkg/rest/oas_schemas_gen.go create mode 100644 pkg/rest/oas_server_gen.go create mode 100644 pkg/rest/oas_unimplemented_gen.go create mode 100644 pkg/rest/oas_validators_gen.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..be7bb3f --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +generate-ogen: + go run github.com/ogen-go/ogen/cmd/ogen@latest --target pkg/rest -package gsn --clean api/rest/gsn.swagger.yml + +generate-sqlc: + go run github.com/sqlc-dev/sqlc/cmd/sqlc@latest generate -f configs/sqlc.yaml diff --git a/api/rest/gsn.swagger.yml b/api/rest/gsn.swagger.yml new file mode 100644 index 0000000..7b410c9 --- /dev/null +++ b/api/rest/gsn.swagger.yml @@ -0,0 +1,234 @@ +openapi: 3.0.4 +info: + title: Swagger GSN - OpenAPI 3.0 + version: 0.0.1 +paths: + /subscription: + get: + tags: + - Subscriptions + summary: Get current subscriptions + operationId: GetSubscriptions + responses: + 200: + description: Subscriptions list + content: + application/json: + schema: + type: object + required: + - subscriptions + properties: + subscriptions: + type: array + items: + type: object + required: + - id + - type + - status + - created_at + properties: + id: + type: string + format: uuid + type: + type: string + enum: ["station", "satellite"] + status: + type: string + enum: ["active", "offline", "busy"] + created_at: + type: string + format: date-time + default: + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + tags: + - Subscriptions + summary: Remove subscription by subscription ID + operationId: Unsubscribe + parameters: + - name: id + in: query + schema: + type: string + format: uuid + responses: + 200: + description: Succesful operation + default: + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /station: + get: + tags: + - Stations + summary: Get available stations + operationId: GetStations + responses: + 200: + description: Stations list + content: + application/json: + schema: + type: object + required: + - stations + properties: + stations: + type: array + items: + type: object + required: + - id + - slug + - status + properties: + id: + type: string + format: uuid + slug: + type: string + status: + type: string + enum: ["active", "offline", "busy"] + default: + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /station/subscribe: + post: + tags: + - Stations + summary: Subscribe to a given station + operationId: SubscribeStation + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: string + format: uuid + description: Subscription ID + responses: + 200: + description: Successful operation + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: string + format: uuid + default: + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /satellite: + get: + tags: + - Satellites + summary: Get available satellites + operationId: GetSatellites + responses: + 200: + description: Satellites list + content: + application/json: + schema: + type: object + required: + - stations + properties: + satellites: + type: array + items: + type: object + required: + - id + - display_name + - status + properties: + id: + type: string + format: uuid + display_name: + type: string + status: + type: string + enum: ["active", "offline", "busy"] + default: + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /satellite/subscribe: + post: + tags: + - Satellites + summary: Subscribe to a given station + operationId: SubscribeSatellite + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: string + format: uuid + responses: + 200: + description: Successful operation + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: string + format: uuid + default: + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + message: + type: string diff --git a/cmd/api/main.go b/cmd/api/main.go new file mode 100644 index 0000000..66a515c --- /dev/null +++ b/cmd/api/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("gsn") +} diff --git a/configs/sqlc.yaml b/configs/sqlc.yaml new file mode 100644 index 0000000..7420b57 --- /dev/null +++ b/configs/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "../internal/repository/queries/queries.sql" + schema: "../internal/repository/migrations/" + gen: + go: + package: "sqlc" + out: "../internal/repository/sqlc" + sql_package: "pgx/v5" \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..de5c027 --- /dev/null +++ b/go.mod @@ -0,0 +1,40 @@ +module git.intra.yksa.space/gsn/gsn-proxy + +go 1.23.0 + +toolchain go1.23.7 + +require ( + github.com/go-faster/errors v0.7.1 + github.com/go-faster/jx v1.1.0 + github.com/google/uuid v1.6.0 + github.com/jackc/pgx/v5 v5.7.4 + github.com/ogen-go/ogen v1.10.1 + go.opentelemetry.io/otel v1.35.0 + go.opentelemetry.io/otel/metric v1.35.0 + go.opentelemetry.io/otel/trace v1.35.0 + go.uber.org/multierr v1.11.0 +) + +require ( + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-faster/yaml v0.4.6 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + 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/segmentio/asm v1.2.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..645905f --- /dev/null +++ b/go.sum @@ -0,0 +1,90 @@ +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= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg= +github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg= +github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I= +github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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/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= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ogen-go/ogen v1.10.1 h1:oeSN8AF9mhTVfapbMuL8pQTF2ToqyW9xXaStmOhHKTA= +github.com/ogen-go/ogen v1.10.1/go.mod h1:fXCg9PsNYEzJ8ABdmZ2A7j4hMi9EDHP53jzsNtIM3d0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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= +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= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +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.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/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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +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= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/ds/ds.go b/internal/ds/ds.go new file mode 100644 index 0000000..4f96351 --- /dev/null +++ b/internal/ds/ds.go @@ -0,0 +1,17 @@ +package ds + +import "github.com/google/uuid" + +type Status string + +const ( + StatusActive Status = "active" + StatusOffline Status = "offline" + StatusBusy Status = "busy" +) + +type Station struct { + ID uuid.UUID + Slug string + Status Status +} diff --git a/internal/repository/helpers.go b/internal/repository/helpers.go new file mode 100644 index 0000000..b72c22c --- /dev/null +++ b/internal/repository/helpers.go @@ -0,0 +1,22 @@ +package repository + +import ( + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +func UUIDToPg(in uuid.UUID) pgtype.UUID { + if in == uuid.Nil { + return pgtype.UUID{} + } + + return pgtype.UUID{Bytes: in, Valid: true} +} + +func PGToUUID(in pgtype.UUID) uuid.UUID { + if !in.Valid { + return uuid.Nil + } + + return in.Bytes +} diff --git a/internal/repository/migrations/0001_init.up.sql b/internal/repository/migrations/0001_init.up.sql new file mode 100644 index 0000000..053124f --- /dev/null +++ b/internal/repository/migrations/0001_init.up.sql @@ -0,0 +1,29 @@ +create table users ( + id uuid primary key, + login text not null +); + +create type status as enum ('active', 'offline', 'busy'); + +create table stations ( + id uuid primary key, + slug text not null, + status status not null +); + +create table satellites ( + id uuid primary key, + display_name text not null, + status status not null +); + +create table subscriptions ( + id uuid primary key, + user_id uuid not null references users(id), + station_id uuid null references satellites(id), + satellite_id uuid null references stations(id), + created_at timestamp not null, + updated_at timestamp not null, + + check (station_id is not null or satellite_id is not null) +); \ No newline at end of file diff --git a/internal/repository/queries/queries.sql b/internal/repository/queries/queries.sql new file mode 100644 index 0000000..d83310d --- /dev/null +++ b/internal/repository/queries/queries.sql @@ -0,0 +1,4 @@ +-- name: GetStationByID :one +select * +from stations +where id = @id; \ No newline at end of file diff --git a/internal/repository/repository.go b/internal/repository/repository.go new file mode 100644 index 0000000..a49a0b2 --- /dev/null +++ b/internal/repository/repository.go @@ -0,0 +1,26 @@ +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" +) + +type Repository struct { + queries *sqlc.Queries +} + +func (r *Repository) GetStationByID(ctx context.Context, ID uuid.UUID) (ds.Station, error) { + ret, err := r.queries.GetStationByID(ctx, UUIDToPg(ID)) + if err != nil { + return ds.Station{}, err + } + + return ds.Station{ + ID: PGToUUID(ret.ID), + Slug: ret.Slug, + Status: ds.Status(ret.Status), + }, nil +} diff --git a/internal/repository/sqlc/db.go b/internal/repository/sqlc/db.go new file mode 100644 index 0000000..63aabfe --- /dev/null +++ b/internal/repository/sqlc/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 + +package sqlc + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/repository/sqlc/models.go b/internal/repository/sqlc/models.go new file mode 100644 index 0000000..de8cf51 --- /dev/null +++ b/internal/repository/sqlc/models.go @@ -0,0 +1,81 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 + +package sqlc + +import ( + "database/sql/driver" + "fmt" + + "github.com/jackc/pgx/v5/pgtype" +) + +type Status string + +const ( + StatusActive Status = "active" + StatusOffline Status = "offline" + StatusBusy Status = "busy" +) + +func (e *Status) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = Status(s) + case string: + *e = Status(s) + default: + return fmt.Errorf("unsupported scan type for Status: %T", src) + } + return nil +} + +type NullStatus struct { + Status Status + Valid bool // Valid is true if Status is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullStatus) Scan(value interface{}) error { + if value == nil { + ns.Status, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.Status.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullStatus) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.Status), nil +} + +type Satellite struct { + ID pgtype.UUID + DisplayName string + Status Status +} + +type Station struct { + ID pgtype.UUID + Slug string + Status Status +} + +type Subscription struct { + ID pgtype.UUID + UserID pgtype.UUID + StationID pgtype.UUID + SatelliteID pgtype.UUID + CreatedAt pgtype.Timestamp + UpdatedAt pgtype.Timestamp +} + +type User struct { + ID pgtype.UUID + Login string +} diff --git a/internal/repository/sqlc/queries.sql.go b/internal/repository/sqlc/queries.sql.go new file mode 100644 index 0000000..b561785 --- /dev/null +++ b/internal/repository/sqlc/queries.sql.go @@ -0,0 +1,25 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: queries.sql + +package sqlc + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const getStationByID = `-- name: GetStationByID :one +select id, slug, status +from stations +where id = $1 +` + +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 +} diff --git a/internal/transport/rest/deps.go b/internal/transport/rest/deps.go new file mode 100644 index 0000000..ecfb3df --- /dev/null +++ b/internal/transport/rest/deps.go @@ -0,0 +1,4 @@ +package rest + +type Service interface { +} diff --git a/internal/transport/rest/handler.go b/internal/transport/rest/handler.go new file mode 100644 index 0000000..fb29d87 --- /dev/null +++ b/internal/transport/rest/handler.go @@ -0,0 +1,43 @@ +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/pkg/rest/oas_cfg_gen.go b/pkg/rest/oas_cfg_gen.go new file mode 100644 index 0000000..1845c4f --- /dev/null +++ b/pkg/rest/oas_cfg_gen.go @@ -0,0 +1,283 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "net/http" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" + + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/otelogen" +) + +var ( + // Allocate option closure once. + clientSpanKind = trace.WithSpanKind(trace.SpanKindClient) + // Allocate option closure once. + serverSpanKind = trace.WithSpanKind(trace.SpanKindServer) +) + +type ( + optionFunc[C any] func(*C) + otelOptionFunc func(*otelConfig) +) + +type otelConfig struct { + TracerProvider trace.TracerProvider + Tracer trace.Tracer + MeterProvider metric.MeterProvider + Meter metric.Meter +} + +func (cfg *otelConfig) initOTEL() { + if cfg.TracerProvider == nil { + cfg.TracerProvider = otel.GetTracerProvider() + } + if cfg.MeterProvider == nil { + cfg.MeterProvider = otel.GetMeterProvider() + } + cfg.Tracer = cfg.TracerProvider.Tracer(otelogen.Name, + trace.WithInstrumentationVersion(otelogen.SemVersion()), + ) + cfg.Meter = cfg.MeterProvider.Meter(otelogen.Name, + metric.WithInstrumentationVersion(otelogen.SemVersion()), + ) +} + +// ErrorHandler is error handler. +type ErrorHandler = ogenerrors.ErrorHandler + +type serverConfig struct { + otelConfig + NotFound http.HandlerFunc + MethodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string) + ErrorHandler ErrorHandler + Prefix string + Middleware Middleware + MaxMultipartMemory int64 +} + +// ServerOption is server config option. +type ServerOption interface { + applyServer(*serverConfig) +} + +var _ ServerOption = (optionFunc[serverConfig])(nil) + +func (o optionFunc[C]) applyServer(c *C) { + o(c) +} + +var _ ServerOption = (otelOptionFunc)(nil) + +func (o otelOptionFunc) applyServer(c *serverConfig) { + o(&c.otelConfig) +} + +func newServerConfig(opts ...ServerOption) serverConfig { + cfg := serverConfig{ + NotFound: http.NotFound, + MethodNotAllowed: func(w http.ResponseWriter, r *http.Request, allowed string) { + status := http.StatusMethodNotAllowed + if r.Method == "OPTIONS" { + w.Header().Set("Access-Control-Allow-Methods", allowed) + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + status = http.StatusNoContent + } else { + w.Header().Set("Allow", allowed) + } + w.WriteHeader(status) + }, + ErrorHandler: ogenerrors.DefaultErrorHandler, + Middleware: nil, + MaxMultipartMemory: 32 << 20, // 32 MB + } + for _, opt := range opts { + opt.applyServer(&cfg) + } + cfg.initOTEL() + return cfg +} + +type baseServer struct { + cfg serverConfig + requests metric.Int64Counter + errors metric.Int64Counter + duration metric.Float64Histogram +} + +func (s baseServer) notFound(w http.ResponseWriter, r *http.Request) { + s.cfg.NotFound(w, r) +} + +func (s baseServer) notAllowed(w http.ResponseWriter, r *http.Request, allowed string) { + s.cfg.MethodNotAllowed(w, r, allowed) +} + +func (cfg serverConfig) baseServer() (s baseServer, err error) { + s = baseServer{cfg: cfg} + if s.requests, err = otelogen.ServerRequestCountCounter(s.cfg.Meter); err != nil { + return s, err + } + if s.errors, err = otelogen.ServerErrorsCountCounter(s.cfg.Meter); err != nil { + return s, err + } + if s.duration, err = otelogen.ServerDurationHistogram(s.cfg.Meter); err != nil { + return s, err + } + return s, nil +} + +type clientConfig struct { + otelConfig + Client ht.Client +} + +// ClientOption is client config option. +type ClientOption interface { + applyClient(*clientConfig) +} + +var _ ClientOption = (optionFunc[clientConfig])(nil) + +func (o optionFunc[C]) applyClient(c *C) { + o(c) +} + +var _ ClientOption = (otelOptionFunc)(nil) + +func (o otelOptionFunc) applyClient(c *clientConfig) { + o(&c.otelConfig) +} + +func newClientConfig(opts ...ClientOption) clientConfig { + cfg := clientConfig{ + Client: http.DefaultClient, + } + for _, opt := range opts { + opt.applyClient(&cfg) + } + cfg.initOTEL() + return cfg +} + +type baseClient struct { + cfg clientConfig + requests metric.Int64Counter + errors metric.Int64Counter + duration metric.Float64Histogram +} + +func (cfg clientConfig) baseClient() (c baseClient, err error) { + c = baseClient{cfg: cfg} + if c.requests, err = otelogen.ClientRequestCountCounter(c.cfg.Meter); err != nil { + return c, err + } + if c.errors, err = otelogen.ClientErrorsCountCounter(c.cfg.Meter); err != nil { + return c, err + } + if c.duration, err = otelogen.ClientDurationHistogram(c.cfg.Meter); err != nil { + return c, err + } + return c, nil +} + +// Option is config option. +type Option interface { + ServerOption + ClientOption +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// +// If none is specified, the global provider is used. +func WithTracerProvider(provider trace.TracerProvider) Option { + return otelOptionFunc(func(cfg *otelConfig) { + if provider != nil { + cfg.TracerProvider = provider + } + }) +} + +// WithMeterProvider specifies a meter provider to use for creating a meter. +// +// If none is specified, the otel.GetMeterProvider() is used. +func WithMeterProvider(provider metric.MeterProvider) Option { + return otelOptionFunc(func(cfg *otelConfig) { + if provider != nil { + cfg.MeterProvider = provider + } + }) +} + +// WithClient specifies http client to use. +func WithClient(client ht.Client) ClientOption { + return optionFunc[clientConfig](func(cfg *clientConfig) { + if client != nil { + cfg.Client = client + } + }) +} + +// WithNotFound specifies Not Found handler to use. +func WithNotFound(notFound http.HandlerFunc) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if notFound != nil { + cfg.NotFound = notFound + } + }) +} + +// WithMethodNotAllowed specifies Method Not Allowed handler to use. +func WithMethodNotAllowed(methodNotAllowed func(w http.ResponseWriter, r *http.Request, allowed string)) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if methodNotAllowed != nil { + cfg.MethodNotAllowed = methodNotAllowed + } + }) +} + +// WithErrorHandler specifies error handler to use. +func WithErrorHandler(h ErrorHandler) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if h != nil { + cfg.ErrorHandler = h + } + }) +} + +// WithPathPrefix specifies server path prefix. +func WithPathPrefix(prefix string) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + cfg.Prefix = prefix + }) +} + +// WithMiddleware specifies middlewares to use. +func WithMiddleware(m ...Middleware) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + switch len(m) { + case 0: + cfg.Middleware = nil + case 1: + cfg.Middleware = m[0] + default: + cfg.Middleware = middleware.ChainMiddlewares(m...) + } + }) +} + +// WithMaxMultipartMemory specifies limit of memory for storing file parts. +// File parts which can't be stored in memory will be stored on disk in temporary files. +func WithMaxMultipartMemory(max int64) ServerOption { + return optionFunc[serverConfig](func(cfg *serverConfig) { + if max > 0 { + cfg.MaxMultipartMemory = max + } + }) +} diff --git a/pkg/rest/oas_client_gen.go b/pkg/rest/oas_client_gen.go new file mode 100644 index 0000000..a2360a1 --- /dev/null +++ b/pkg/rest/oas_client_gen.go @@ -0,0 +1,573 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "context" + "net/url" + "strings" + "time" + + "github.com/go-faster/errors" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "go.opentelemetry.io/otel/trace" + + "github.com/ogen-go/ogen/conv" + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/otelogen" + "github.com/ogen-go/ogen/uri" +) + +func trimTrailingSlashes(u *url.URL) { + u.Path = strings.TrimRight(u.Path, "/") + u.RawPath = strings.TrimRight(u.RawPath, "/") +} + +// Invoker invokes operations described by OpenAPI v3 specification. +type Invoker interface { + // GetSatellites invokes GetSatellites operation. + // + // Get available satellites. + // + // GET /satellite + GetSatellites(ctx context.Context) (*GetSatellitesOK, error) + // GetStations invokes GetStations operation. + // + // Get available stations. + // + // GET /station + GetStations(ctx context.Context) (*GetStationsOK, error) + // GetSubscriptions invokes GetSubscriptions operation. + // + // Get current subscriptions. + // + // GET /subscription + GetSubscriptions(ctx context.Context) (*GetSubscriptionsOK, error) + // SubscribeSatellite invokes SubscribeSatellite operation. + // + // Subscribe to a given station. + // + // POST /satellite/subscribe + SubscribeSatellite(ctx context.Context, request *SubscribeSatelliteReq) (*SubscribeSatelliteOK, error) + // SubscribeStation invokes SubscribeStation operation. + // + // Subscribe to a given station. + // + // POST /station/subscribe + SubscribeStation(ctx context.Context, request *SubscribeStationReq) (*SubscribeStationOK, error) + // Unsubscribe invokes Unsubscribe operation. + // + // Remove subscription by subscription ID. + // + // DELETE /subscription + Unsubscribe(ctx context.Context, params UnsubscribeParams) error +} + +// Client implements OAS client. +type Client struct { + serverURL *url.URL + baseClient +} +type errorHandler interface { + NewError(ctx context.Context, err error) *ErrorStatusCode +} + +var _ Handler = struct { + errorHandler + *Client +}{} + +// NewClient initializes new Client defined by OAS. +func NewClient(serverURL string, opts ...ClientOption) (*Client, error) { + u, err := url.Parse(serverURL) + if err != nil { + return nil, err + } + trimTrailingSlashes(u) + + c, err := newClientConfig(opts...).baseClient() + if err != nil { + return nil, err + } + return &Client{ + serverURL: u, + baseClient: c, + }, nil +} + +type serverURLKey struct{} + +// WithServerURL sets context key to override server URL. +func WithServerURL(ctx context.Context, u *url.URL) context.Context { + return context.WithValue(ctx, serverURLKey{}, u) +} + +func (c *Client) requestURL(ctx context.Context) *url.URL { + u, ok := ctx.Value(serverURLKey{}).(*url.URL) + if !ok { + return c.serverURL + } + return u +} + +// GetSatellites invokes GetSatellites operation. +// +// Get available satellites. +// +// GET /satellite +func (c *Client) GetSatellites(ctx context.Context) (*GetSatellitesOK, error) { + res, err := c.sendGetSatellites(ctx) + return res, err +} + +func (c *Client) sendGetSatellites(ctx context.Context) (res *GetSatellitesOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("GetSatellites"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/satellite"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetSatellitesOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/satellite" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeGetSatellitesResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// GetStations invokes GetStations operation. +// +// Get available stations. +// +// GET /station +func (c *Client) GetStations(ctx context.Context) (*GetStationsOK, error) { + res, err := c.sendGetStations(ctx) + return res, err +} + +func (c *Client) sendGetStations(ctx context.Context) (res *GetStationsOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("GetStations"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/station"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetStationsOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/station" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeGetStationsResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// GetSubscriptions invokes GetSubscriptions operation. +// +// Get current subscriptions. +// +// GET /subscription +func (c *Client) GetSubscriptions(ctx context.Context) (*GetSubscriptionsOK, error) { + res, err := c.sendGetSubscriptions(ctx) + return res, err +} + +func (c *Client) sendGetSubscriptions(ctx context.Context) (res *GetSubscriptionsOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("GetSubscriptions"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/subscription"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, GetSubscriptionsOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/subscription" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "GET", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeGetSubscriptionsResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// SubscribeSatellite invokes SubscribeSatellite operation. +// +// Subscribe to a given station. +// +// POST /satellite/subscribe +func (c *Client) SubscribeSatellite(ctx context.Context, request *SubscribeSatelliteReq) (*SubscribeSatelliteOK, error) { + res, err := c.sendSubscribeSatellite(ctx, request) + return res, err +} + +func (c *Client) sendSubscribeSatellite(ctx context.Context, request *SubscribeSatelliteReq) (res *SubscribeSatelliteOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("SubscribeSatellite"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/satellite/subscribe"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, SubscribeSatelliteOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/satellite/subscribe" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeSubscribeSatelliteRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeSubscribeSatelliteResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// SubscribeStation invokes SubscribeStation operation. +// +// Subscribe to a given station. +// +// POST /station/subscribe +func (c *Client) SubscribeStation(ctx context.Context, request *SubscribeStationReq) (*SubscribeStationOK, error) { + res, err := c.sendSubscribeStation(ctx, request) + return res, err +} + +func (c *Client) sendSubscribeStation(ctx context.Context, request *SubscribeStationReq) (res *SubscribeStationOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("SubscribeStation"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/station/subscribe"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, SubscribeStationOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/station/subscribe" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "POST", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + if err := encodeSubscribeStationRequest(request, r); err != nil { + return res, errors.Wrap(err, "encode request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeSubscribeStationResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} + +// Unsubscribe invokes Unsubscribe operation. +// +// Remove subscription by subscription ID. +// +// DELETE /subscription +func (c *Client) Unsubscribe(ctx context.Context, params UnsubscribeParams) error { + _, err := c.sendUnsubscribe(ctx, params) + return err +} + +func (c *Client) sendUnsubscribe(ctx context.Context, params UnsubscribeParams) (res *UnsubscribeOK, err error) { + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("Unsubscribe"), + semconv.HTTPRequestMethodKey.String("DELETE"), + semconv.HTTPRouteKey.String("/subscription"), + } + + // Run stopwatch. + startTime := time.Now() + defer func() { + // Use floating point division here for higher precision (instead of Millisecond method). + elapsedDuration := time.Since(startTime) + c.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), metric.WithAttributes(otelAttrs...)) + }() + + // Increment request counter. + c.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + + // Start a span for this request. + ctx, span := c.cfg.Tracer.Start(ctx, UnsubscribeOperation, + trace.WithAttributes(otelAttrs...), + clientSpanKind, + ) + // Track stage for error reporting. + var stage string + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, stage) + c.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...)) + } + span.End() + }() + + stage = "BuildURL" + u := uri.Clone(c.requestURL(ctx)) + var pathParts [1]string + pathParts[0] = "/subscription" + uri.AddPathParts(u, pathParts[:]...) + + stage = "EncodeQueryParams" + q := uri.NewQueryEncoder() + { + // Encode "id" parameter. + cfg := uri.QueryParameterEncodingConfig{ + Name: "id", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.EncodeParam(cfg, func(e uri.Encoder) error { + if val, ok := params.ID.Get(); ok { + return e.EncodeValue(conv.UUIDToString(val)) + } + return nil + }); err != nil { + return res, errors.Wrap(err, "encode query") + } + } + u.RawQuery = q.Values().Encode() + + stage = "EncodeRequest" + r, err := ht.NewRequest(ctx, "DELETE", u) + if err != nil { + return res, errors.Wrap(err, "create request") + } + + stage = "SendRequest" + resp, err := c.cfg.Client.Do(r) + if err != nil { + return res, errors.Wrap(err, "do request") + } + defer resp.Body.Close() + + stage = "DecodeResponse" + result, err := decodeUnsubscribeResponse(resp) + if err != nil { + return res, errors.Wrap(err, "decode response") + } + + return result, nil +} diff --git a/pkg/rest/oas_handlers_gen.go b/pkg/rest/oas_handlers_gen.go new file mode 100644 index 0000000..b65b688 --- /dev/null +++ b/pkg/rest/oas_handlers_gen.go @@ -0,0 +1,868 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "context" + "net/http" + "time" + + "github.com/go-faster/errors" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "go.opentelemetry.io/otel/trace" + + ht "github.com/ogen-go/ogen/http" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/otelogen" +) + +type codeRecorder struct { + http.ResponseWriter + status int +} + +func (c *codeRecorder) WriteHeader(status int) { + c.status = status + c.ResponseWriter.WriteHeader(status) +} + +// handleGetSatellitesRequest handles GetSatellites operation. +// +// Get available satellites. +// +// GET /satellite +func (s *Server) handleGetSatellitesRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("GetSatellites"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/satellite"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), GetSatellitesOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + ) + + var response *GetSatellitesOK + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: GetSatellitesOperation, + OperationSummary: "Get available satellites", + OperationID: "GetSatellites", + Body: nil, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = struct{} + Params = struct{} + Response = *GetSatellitesOK + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetSatellites(ctx) + return response, err + }, + ) + } else { + response, err = s.h.GetSatellites(ctx) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetSatellitesResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleGetStationsRequest handles GetStations operation. +// +// Get available stations. +// +// GET /station +func (s *Server) handleGetStationsRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("GetStations"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/station"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), GetStationsOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + ) + + var response *GetStationsOK + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: GetStationsOperation, + OperationSummary: "Get available stations", + OperationID: "GetStations", + Body: nil, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = struct{} + Params = struct{} + Response = *GetStationsOK + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetStations(ctx) + return response, err + }, + ) + } else { + response, err = s.h.GetStations(ctx) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetStationsResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleGetSubscriptionsRequest handles GetSubscriptions operation. +// +// Get current subscriptions. +// +// GET /subscription +func (s *Server) handleGetSubscriptionsRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("GetSubscriptions"), + semconv.HTTPRequestMethodKey.String("GET"), + semconv.HTTPRouteKey.String("/subscription"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), GetSubscriptionsOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + ) + + var response *GetSubscriptionsOK + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: GetSubscriptionsOperation, + OperationSummary: "Get current subscriptions", + OperationID: "GetSubscriptions", + Body: nil, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = struct{} + Params = struct{} + Response = *GetSubscriptionsOK + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.GetSubscriptions(ctx) + return response, err + }, + ) + } else { + response, err = s.h.GetSubscriptions(ctx) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeGetSubscriptionsResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleSubscribeSatelliteRequest handles SubscribeSatellite operation. +// +// Subscribe to a given station. +// +// POST /satellite/subscribe +func (s *Server) handleSubscribeSatelliteRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("SubscribeSatellite"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/satellite/subscribe"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), SubscribeSatelliteOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: SubscribeSatelliteOperation, + ID: "SubscribeSatellite", + } + ) + request, close, err := s.decodeSubscribeSatelliteRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response *SubscribeSatelliteOK + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: SubscribeSatelliteOperation, + OperationSummary: "Subscribe to a given station", + OperationID: "SubscribeSatellite", + Body: request, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *SubscribeSatelliteReq + Params = struct{} + Response = *SubscribeSatelliteOK + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.SubscribeSatellite(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.SubscribeSatellite(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeSubscribeSatelliteResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleSubscribeStationRequest handles SubscribeStation operation. +// +// Subscribe to a given station. +// +// POST /station/subscribe +func (s *Server) handleSubscribeStationRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("SubscribeStation"), + semconv.HTTPRequestMethodKey.String("POST"), + semconv.HTTPRouteKey.String("/station/subscribe"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), SubscribeStationOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: SubscribeStationOperation, + ID: "SubscribeStation", + } + ) + request, close, err := s.decodeSubscribeStationRequest(r) + if err != nil { + err = &ogenerrors.DecodeRequestError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeRequest", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + defer func() { + if err := close(); err != nil { + recordError("CloseRequest", err) + } + }() + + var response *SubscribeStationOK + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: SubscribeStationOperation, + OperationSummary: "Subscribe to a given station", + OperationID: "SubscribeStation", + Body: request, + Params: middleware.Parameters{}, + Raw: r, + } + + type ( + Request = *SubscribeStationReq + Params = struct{} + Response = *SubscribeStationOK + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + nil, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + response, err = s.h.SubscribeStation(ctx, request) + return response, err + }, + ) + } else { + response, err = s.h.SubscribeStation(ctx, request) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeSubscribeStationResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} + +// handleUnsubscribeRequest handles Unsubscribe operation. +// +// Remove subscription by subscription ID. +// +// DELETE /subscription +func (s *Server) handleUnsubscribeRequest(args [0]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) { + statusWriter := &codeRecorder{ResponseWriter: w} + w = statusWriter + otelAttrs := []attribute.KeyValue{ + otelogen.OperationID("Unsubscribe"), + semconv.HTTPRequestMethodKey.String("DELETE"), + semconv.HTTPRouteKey.String("/subscription"), + } + + // Start a span for this request. + ctx, span := s.cfg.Tracer.Start(r.Context(), UnsubscribeOperation, + trace.WithAttributes(otelAttrs...), + serverSpanKind, + ) + defer span.End() + + // Add Labeler to context. + labeler := &Labeler{attrs: otelAttrs} + ctx = contextWithLabeler(ctx, labeler) + + // Run stopwatch. + startTime := time.Now() + defer func() { + elapsedDuration := time.Since(startTime) + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + code := statusWriter.status + if code != 0 { + codeAttr := semconv.HTTPResponseStatusCode(code) + attrs = append(attrs, codeAttr) + span.SetAttributes(codeAttr) + } + attrOpt := metric.WithAttributes(attrs...) + + // Increment request counter. + s.requests.Add(ctx, 1, attrOpt) + + // Use floating point division here for higher precision (instead of Millisecond method). + s.duration.Record(ctx, float64(elapsedDuration)/float64(time.Millisecond), attrOpt) + }() + + var ( + recordError = func(stage string, err error) { + span.RecordError(err) + + // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#status + // Span Status MUST be left unset if HTTP status code was in the 1xx, 2xx or 3xx ranges, + // unless there was another error (e.g., network error receiving the response body; or 3xx codes with + // max redirects exceeded), in which case status MUST be set to Error. + code := statusWriter.status + if code >= 100 && code < 500 { + span.SetStatus(codes.Error, stage) + } + + attrSet := labeler.AttributeSet() + attrs := attrSet.ToSlice() + if code != 0 { + attrs = append(attrs, semconv.HTTPResponseStatusCode(code)) + } + + s.errors.Add(ctx, 1, metric.WithAttributes(attrs...)) + } + err error + opErrContext = ogenerrors.OperationContext{ + Name: UnsubscribeOperation, + ID: "Unsubscribe", + } + ) + params, err := decodeUnsubscribeParams(args, argsEscaped, r) + if err != nil { + err = &ogenerrors.DecodeParamsError{ + OperationContext: opErrContext, + Err: err, + } + defer recordError("DecodeParams", err) + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + + var response *UnsubscribeOK + if m := s.cfg.Middleware; m != nil { + mreq := middleware.Request{ + Context: ctx, + OperationName: UnsubscribeOperation, + OperationSummary: "Remove subscription by subscription ID", + OperationID: "Unsubscribe", + Body: nil, + Params: middleware.Parameters{ + { + Name: "id", + In: "query", + }: params.ID, + }, + Raw: r, + } + + type ( + Request = struct{} + Params = UnsubscribeParams + Response = *UnsubscribeOK + ) + response, err = middleware.HookMiddleware[ + Request, + Params, + Response, + ]( + m, + mreq, + unpackUnsubscribeParams, + func(ctx context.Context, request Request, params Params) (response Response, err error) { + err = s.h.Unsubscribe(ctx, params) + return response, err + }, + ) + } else { + err = s.h.Unsubscribe(ctx, params) + } + if err != nil { + if errRes, ok := errors.Into[*ErrorStatusCode](err); ok { + if err := encodeErrorResponse(errRes, w, span); err != nil { + defer recordError("Internal", err) + } + return + } + if errors.Is(err, ht.ErrNotImplemented) { + s.cfg.ErrorHandler(ctx, w, r, err) + return + } + if err := encodeErrorResponse(s.h.NewError(ctx, err), w, span); err != nil { + defer recordError("Internal", err) + } + return + } + + if err := encodeUnsubscribeResponse(response, w, span); err != nil { + defer recordError("EncodeResponse", err) + if !errors.Is(err, ht.ErrInternalServerErrorResponse) { + s.cfg.ErrorHandler(ctx, w, r, err) + } + return + } +} diff --git a/pkg/rest/oas_json_gen.go b/pkg/rest/oas_json_gen.go new file mode 100644 index 0000000..813c5f7 --- /dev/null +++ b/pkg/rest/oas_json_gen.go @@ -0,0 +1,1362 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "math/bits" + "strconv" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + + "github.com/ogen-go/ogen/json" + "github.com/ogen-go/ogen/validate" +) + +// Encode implements json.Marshaler. +func (s *Error) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *Error) encodeFields(e *jx.Encoder) { + { + e.FieldStart("code") + e.Int(s.Code) + } + { + e.FieldStart("message") + e.Str(s.Message) + } +} + +var jsonFieldsNameOfError = [2]string{ + 0: "code", + 1: "message", +} + +// Decode decodes Error from json. +func (s *Error) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode Error to nil") + } + var requiredBitSet [1]uint8 + + 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 + if err := func() error { + v, err := d.Str() + s.Message = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"message\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode Error") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000011, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfError) { + name = jsonFieldsNameOfError[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *Error) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *Error) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetSatellitesOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetSatellitesOK) encodeFields(e *jx.Encoder) { + { + if s.Satellites != nil { + e.FieldStart("satellites") + e.ArrStart() + for _, elem := range s.Satellites { + elem.Encode(e) + } + e.ArrEnd() + } + } +} + +var jsonFieldsNameOfGetSatellitesOK = [1]string{ + 0: "satellites", +} + +// Decode decodes GetSatellitesOK from json. +func (s *GetSatellitesOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetSatellitesOK to nil") + } + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "satellites": + if err := func() error { + s.Satellites = make([]GetSatellitesOKSatellitesItem, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem GetSatellitesOKSatellitesItem + if err := elem.Decode(d); err != nil { + return err + } + s.Satellites = append(s.Satellites, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"satellites\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetSatellitesOK") + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetSatellitesOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetSatellitesOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetSatellitesOKSatellitesItem) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetSatellitesOKSatellitesItem) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + json.EncodeUUID(e, s.ID) + } + { + e.FieldStart("display_name") + e.Str(s.DisplayName) + } + { + e.FieldStart("status") + s.Status.Encode(e) + } +} + +var jsonFieldsNameOfGetSatellitesOKSatellitesItem = [3]string{ + 0: "id", + 1: "display_name", + 2: "status", +} + +// Decode decodes GetSatellitesOKSatellitesItem from json. +func (s *GetSatellitesOKSatellitesItem) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetSatellitesOKSatellitesItem to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := json.DecodeUUID(d) + s.ID = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + case "display_name": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.DisplayName = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"display_name\"") + } + case "status": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + if err := s.Status.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"status\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetSatellitesOKSatellitesItem") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetSatellitesOKSatellitesItem) { + name = jsonFieldsNameOfGetSatellitesOKSatellitesItem[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetSatellitesOKSatellitesItem) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetSatellitesOKSatellitesItem) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes GetSatellitesOKSatellitesItemStatus as json. +func (s GetSatellitesOKSatellitesItemStatus) Encode(e *jx.Encoder) { + e.Str(string(s)) +} + +// Decode decodes GetSatellitesOKSatellitesItemStatus from json. +func (s *GetSatellitesOKSatellitesItemStatus) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetSatellitesOKSatellitesItemStatus to nil") + } + v, err := d.StrBytes() + if err != nil { + return err + } + // Try to use constant string. + switch GetSatellitesOKSatellitesItemStatus(v) { + case GetSatellitesOKSatellitesItemStatusActive: + *s = GetSatellitesOKSatellitesItemStatusActive + case GetSatellitesOKSatellitesItemStatusOffline: + *s = GetSatellitesOKSatellitesItemStatusOffline + case GetSatellitesOKSatellitesItemStatusBusy: + *s = GetSatellitesOKSatellitesItemStatusBusy + default: + *s = GetSatellitesOKSatellitesItemStatus(v) + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s GetSatellitesOKSatellitesItemStatus) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetSatellitesOKSatellitesItemStatus) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetStationsOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetStationsOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("stations") + e.ArrStart() + for _, elem := range s.Stations { + elem.Encode(e) + } + e.ArrEnd() + } +} + +var jsonFieldsNameOfGetStationsOK = [1]string{ + 0: "stations", +} + +// Decode decodes GetStationsOK from json. +func (s *GetStationsOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetStationsOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "stations": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + s.Stations = make([]GetStationsOKStationsItem, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem GetStationsOKStationsItem + if err := elem.Decode(d); err != nil { + return err + } + s.Stations = append(s.Stations, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"stations\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetStationsOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetStationsOK) { + name = jsonFieldsNameOfGetStationsOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetStationsOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetStationsOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetStationsOKStationsItem) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetStationsOKStationsItem) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + json.EncodeUUID(e, s.ID) + } + { + e.FieldStart("slug") + e.Str(s.Slug) + } + { + e.FieldStart("status") + s.Status.Encode(e) + } +} + +var jsonFieldsNameOfGetStationsOKStationsItem = [3]string{ + 0: "id", + 1: "slug", + 2: "status", +} + +// Decode decodes GetStationsOKStationsItem from json. +func (s *GetStationsOKStationsItem) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetStationsOKStationsItem to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := json.DecodeUUID(d) + s.ID = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + case "slug": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + v, err := d.Str() + s.Slug = string(v) + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"slug\"") + } + case "status": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + if err := s.Status.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"status\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetStationsOKStationsItem") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetStationsOKStationsItem) { + name = jsonFieldsNameOfGetStationsOKStationsItem[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetStationsOKStationsItem) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetStationsOKStationsItem) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes GetStationsOKStationsItemStatus as json. +func (s GetStationsOKStationsItemStatus) Encode(e *jx.Encoder) { + e.Str(string(s)) +} + +// Decode decodes GetStationsOKStationsItemStatus from json. +func (s *GetStationsOKStationsItemStatus) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetStationsOKStationsItemStatus to nil") + } + v, err := d.StrBytes() + if err != nil { + return err + } + // Try to use constant string. + switch GetStationsOKStationsItemStatus(v) { + case GetStationsOKStationsItemStatusActive: + *s = GetStationsOKStationsItemStatusActive + case GetStationsOKStationsItemStatusOffline: + *s = GetStationsOKStationsItemStatusOffline + case GetStationsOKStationsItemStatusBusy: + *s = GetStationsOKStationsItemStatusBusy + default: + *s = GetStationsOKStationsItemStatus(v) + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s GetStationsOKStationsItemStatus) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetStationsOKStationsItemStatus) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetSubscriptionsOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetSubscriptionsOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("subscriptions") + e.ArrStart() + for _, elem := range s.Subscriptions { + elem.Encode(e) + } + e.ArrEnd() + } +} + +var jsonFieldsNameOfGetSubscriptionsOK = [1]string{ + 0: "subscriptions", +} + +// Decode decodes GetSubscriptionsOK from json. +func (s *GetSubscriptionsOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetSubscriptionsOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "subscriptions": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + s.Subscriptions = make([]GetSubscriptionsOKSubscriptionsItem, 0) + if err := d.Arr(func(d *jx.Decoder) error { + var elem GetSubscriptionsOKSubscriptionsItem + if err := elem.Decode(d); err != nil { + return err + } + s.Subscriptions = append(s.Subscriptions, elem) + return nil + }); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"subscriptions\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetSubscriptionsOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetSubscriptionsOK) { + name = jsonFieldsNameOfGetSubscriptionsOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetSubscriptionsOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetSubscriptionsOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *GetSubscriptionsOKSubscriptionsItem) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *GetSubscriptionsOKSubscriptionsItem) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + json.EncodeUUID(e, s.ID) + } + { + e.FieldStart("type") + s.Type.Encode(e) + } + { + e.FieldStart("status") + s.Status.Encode(e) + } + { + e.FieldStart("created_at") + json.EncodeDateTime(e, s.CreatedAt) + } +} + +var jsonFieldsNameOfGetSubscriptionsOKSubscriptionsItem = [4]string{ + 0: "id", + 1: "type", + 2: "status", + 3: "created_at", +} + +// Decode decodes GetSubscriptionsOKSubscriptionsItem from json. +func (s *GetSubscriptionsOKSubscriptionsItem) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetSubscriptionsOKSubscriptionsItem to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := json.DecodeUUID(d) + s.ID = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + case "type": + requiredBitSet[0] |= 1 << 1 + if err := func() error { + if err := s.Type.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"type\"") + } + case "status": + requiredBitSet[0] |= 1 << 2 + if err := func() error { + if err := s.Status.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"status\"") + } + case "created_at": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + v, err := json.DecodeDateTime(d) + s.CreatedAt = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"created_at\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode GetSubscriptionsOKSubscriptionsItem") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00001111, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfGetSubscriptionsOKSubscriptionsItem) { + name = jsonFieldsNameOfGetSubscriptionsOKSubscriptionsItem[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *GetSubscriptionsOKSubscriptionsItem) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetSubscriptionsOKSubscriptionsItem) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes GetSubscriptionsOKSubscriptionsItemStatus as json. +func (s GetSubscriptionsOKSubscriptionsItemStatus) Encode(e *jx.Encoder) { + e.Str(string(s)) +} + +// Decode decodes GetSubscriptionsOKSubscriptionsItemStatus from json. +func (s *GetSubscriptionsOKSubscriptionsItemStatus) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetSubscriptionsOKSubscriptionsItemStatus to nil") + } + v, err := d.StrBytes() + if err != nil { + return err + } + // Try to use constant string. + switch GetSubscriptionsOKSubscriptionsItemStatus(v) { + case GetSubscriptionsOKSubscriptionsItemStatusActive: + *s = GetSubscriptionsOKSubscriptionsItemStatusActive + case GetSubscriptionsOKSubscriptionsItemStatusOffline: + *s = GetSubscriptionsOKSubscriptionsItemStatusOffline + case GetSubscriptionsOKSubscriptionsItemStatusBusy: + *s = GetSubscriptionsOKSubscriptionsItemStatusBusy + default: + *s = GetSubscriptionsOKSubscriptionsItemStatus(v) + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s GetSubscriptionsOKSubscriptionsItemStatus) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetSubscriptionsOKSubscriptionsItemStatus) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode encodes GetSubscriptionsOKSubscriptionsItemType as json. +func (s GetSubscriptionsOKSubscriptionsItemType) Encode(e *jx.Encoder) { + e.Str(string(s)) +} + +// Decode decodes GetSubscriptionsOKSubscriptionsItemType from json. +func (s *GetSubscriptionsOKSubscriptionsItemType) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode GetSubscriptionsOKSubscriptionsItemType to nil") + } + v, err := d.StrBytes() + if err != nil { + return err + } + // Try to use constant string. + switch GetSubscriptionsOKSubscriptionsItemType(v) { + case GetSubscriptionsOKSubscriptionsItemTypeStation: + *s = GetSubscriptionsOKSubscriptionsItemTypeStation + case GetSubscriptionsOKSubscriptionsItemTypeSatellite: + *s = GetSubscriptionsOKSubscriptionsItemTypeSatellite + default: + *s = GetSubscriptionsOKSubscriptionsItemType(v) + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s GetSubscriptionsOKSubscriptionsItemType) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *GetSubscriptionsOKSubscriptionsItemType) 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() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *SubscribeSatelliteOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + json.EncodeUUID(e, s.ID) + } +} + +var jsonFieldsNameOfSubscribeSatelliteOK = [1]string{ + 0: "id", +} + +// Decode decodes SubscribeSatelliteOK from json. +func (s *SubscribeSatelliteOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SubscribeSatelliteOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := json.DecodeUUID(d) + s.ID = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode SubscribeSatelliteOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfSubscribeSatelliteOK) { + name = jsonFieldsNameOfSubscribeSatelliteOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *SubscribeSatelliteOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SubscribeSatelliteOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *SubscribeSatelliteReq) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *SubscribeSatelliteReq) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + json.EncodeUUID(e, s.ID) + } +} + +var jsonFieldsNameOfSubscribeSatelliteReq = [1]string{ + 0: "id", +} + +// Decode decodes SubscribeSatelliteReq from json. +func (s *SubscribeSatelliteReq) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SubscribeSatelliteReq to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := json.DecodeUUID(d) + s.ID = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode SubscribeSatelliteReq") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfSubscribeSatelliteReq) { + name = jsonFieldsNameOfSubscribeSatelliteReq[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *SubscribeSatelliteReq) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SubscribeSatelliteReq) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *SubscribeStationOK) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *SubscribeStationOK) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + json.EncodeUUID(e, s.ID) + } +} + +var jsonFieldsNameOfSubscribeStationOK = [1]string{ + 0: "id", +} + +// Decode decodes SubscribeStationOK from json. +func (s *SubscribeStationOK) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SubscribeStationOK to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := json.DecodeUUID(d) + s.ID = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode SubscribeStationOK") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfSubscribeStationOK) { + name = jsonFieldsNameOfSubscribeStationOK[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *SubscribeStationOK) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SubscribeStationOK) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} + +// Encode implements json.Marshaler. +func (s *SubscribeStationReq) Encode(e *jx.Encoder) { + e.ObjStart() + s.encodeFields(e) + e.ObjEnd() +} + +// encodeFields encodes fields. +func (s *SubscribeStationReq) encodeFields(e *jx.Encoder) { + { + e.FieldStart("id") + json.EncodeUUID(e, s.ID) + } +} + +var jsonFieldsNameOfSubscribeStationReq = [1]string{ + 0: "id", +} + +// Decode decodes SubscribeStationReq from json. +func (s *SubscribeStationReq) Decode(d *jx.Decoder) error { + if s == nil { + return errors.New("invalid: unable to decode SubscribeStationReq to nil") + } + var requiredBitSet [1]uint8 + + if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { + switch string(k) { + case "id": + requiredBitSet[0] |= 1 << 0 + if err := func() error { + v, err := json.DecodeUUID(d) + s.ID = v + if err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"id\"") + } + default: + return d.Skip() + } + return nil + }); err != nil { + return errors.Wrap(err, "decode SubscribeStationReq") + } + // Validate required fields. + var failures []validate.FieldError + for i, mask := range [1]uint8{ + 0b00000001, + } { + if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { + // Mask only required fields and check equality to mask using XOR. + // + // If XOR result is not zero, result is not equal to expected, so some fields are missed. + // Bits of fields which would be set are actually bits of missed fields. + missed := bits.OnesCount8(result) + for bitN := 0; bitN < missed; bitN++ { + bitIdx := bits.TrailingZeros8(result) + fieldIdx := i*8 + bitIdx + var name string + if fieldIdx < len(jsonFieldsNameOfSubscribeStationReq) { + name = jsonFieldsNameOfSubscribeStationReq[fieldIdx] + } else { + name = strconv.Itoa(fieldIdx) + } + failures = append(failures, validate.FieldError{ + Name: name, + Error: validate.ErrFieldRequired, + }) + // Reset bit. + result &^= 1 << bitIdx + } + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + + return nil +} + +// MarshalJSON implements stdjson.Marshaler. +func (s *SubscribeStationReq) MarshalJSON() ([]byte, error) { + e := jx.Encoder{} + s.Encode(&e) + return e.Bytes(), nil +} + +// UnmarshalJSON implements stdjson.Unmarshaler. +func (s *SubscribeStationReq) UnmarshalJSON(data []byte) error { + d := jx.DecodeBytes(data) + return s.Decode(d) +} diff --git a/pkg/rest/oas_labeler_gen.go b/pkg/rest/oas_labeler_gen.go new file mode 100644 index 0000000..47bce6a --- /dev/null +++ b/pkg/rest/oas_labeler_gen.go @@ -0,0 +1,42 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "context" + + "go.opentelemetry.io/otel/attribute" +) + +// Labeler is used to allow adding custom attributes to the server request metrics. +type Labeler struct { + attrs []attribute.KeyValue +} + +// Add attributes to the Labeler. +func (l *Labeler) Add(attrs ...attribute.KeyValue) { + l.attrs = append(l.attrs, attrs...) +} + +// AttributeSet returns the attributes added to the Labeler as an attribute.Set. +func (l *Labeler) AttributeSet() attribute.Set { + return attribute.NewSet(l.attrs...) +} + +type labelerContextKey struct{} + +// LabelerFromContext retrieves the Labeler from the provided context, if present. +// +// If no Labeler was found in the provided context a new, empty Labeler is returned and the second +// return value is false. In this case it is safe to use the Labeler but any attributes added to +// it will not be used. +func LabelerFromContext(ctx context.Context) (*Labeler, bool) { + if l, ok := ctx.Value(labelerContextKey{}).(*Labeler); ok { + return l, true + } + return &Labeler{}, false +} + +func contextWithLabeler(ctx context.Context, l *Labeler) context.Context { + return context.WithValue(ctx, labelerContextKey{}, l) +} diff --git a/pkg/rest/oas_middleware_gen.go b/pkg/rest/oas_middleware_gen.go new file mode 100644 index 0000000..9d62f34 --- /dev/null +++ b/pkg/rest/oas_middleware_gen.go @@ -0,0 +1,10 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "github.com/ogen-go/ogen/middleware" +) + +// Middleware is middleware type. +type Middleware = middleware.Middleware diff --git a/pkg/rest/oas_operations_gen.go b/pkg/rest/oas_operations_gen.go new file mode 100644 index 0000000..0e76612 --- /dev/null +++ b/pkg/rest/oas_operations_gen.go @@ -0,0 +1,15 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +// OperationName is the ogen operation name +type OperationName = string + +const ( + GetSatellitesOperation OperationName = "GetSatellites" + GetStationsOperation OperationName = "GetStations" + GetSubscriptionsOperation OperationName = "GetSubscriptions" + SubscribeSatelliteOperation OperationName = "SubscribeSatellite" + SubscribeStationOperation OperationName = "SubscribeStation" + UnsubscribeOperation OperationName = "Unsubscribe" +) diff --git a/pkg/rest/oas_parameters_gen.go b/pkg/rest/oas_parameters_gen.go new file mode 100644 index 0000000..81bee64 --- /dev/null +++ b/pkg/rest/oas_parameters_gen.go @@ -0,0 +1,78 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "net/http" + + "github.com/google/uuid" + + "github.com/ogen-go/ogen/conv" + "github.com/ogen-go/ogen/middleware" + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/uri" +) + +// UnsubscribeParams is parameters of Unsubscribe operation. +type UnsubscribeParams struct { + ID OptUUID +} + +func unpackUnsubscribeParams(packed middleware.Parameters) (params UnsubscribeParams) { + { + key := middleware.ParameterKey{ + Name: "id", + In: "query", + } + if v, ok := packed[key]; ok { + params.ID = v.(OptUUID) + } + } + return params +} + +func decodeUnsubscribeParams(args [0]string, argsEscaped bool, r *http.Request) (params UnsubscribeParams, _ error) { + q := uri.NewQueryDecoder(r.URL.Query()) + // Decode query: id. + if err := func() error { + cfg := uri.QueryParameterDecodingConfig{ + Name: "id", + Style: uri.QueryStyleForm, + Explode: true, + } + + if err := q.HasParam(cfg); err == nil { + if err := q.DecodeParam(cfg, func(d uri.Decoder) error { + var paramsDotIDVal uuid.UUID + if err := func() error { + val, err := d.DecodeValue() + if err != nil { + return err + } + + c, err := conv.ToUUID(val) + if err != nil { + return err + } + + paramsDotIDVal = c + return nil + }(); err != nil { + return err + } + params.ID.SetTo(paramsDotIDVal) + return nil + }); err != nil { + return err + } + } + return nil + }(); err != nil { + return params, &ogenerrors.DecodeParamError{ + Name: "id", + In: "query", + Err: err, + } + } + return params, nil +} diff --git a/pkg/rest/oas_request_decoders_gen.go b/pkg/rest/oas_request_decoders_gen.go new file mode 100644 index 0000000..7dd553c --- /dev/null +++ b/pkg/rest/oas_request_decoders_gen.go @@ -0,0 +1,142 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "go.uber.org/multierr" + + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/validate" +) + +func (s *Server) decodeSubscribeSatelliteRequest(r *http.Request) ( + req *SubscribeSatelliteReq, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = multierr.Append(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = multierr.Append(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + if err != nil { + return req, close, err + } + + if len(buf) == 0 { + return req, close, validate.ErrBodyRequired + } + + d := jx.DecodeBytes(buf) + + var request SubscribeSatelliteReq + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, close, err + } + return &request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} + +func (s *Server) decodeSubscribeStationRequest(r *http.Request) ( + req *SubscribeStationReq, + close func() error, + rerr error, +) { + var closers []func() error + close = func() error { + var merr error + // Close in reverse order, to match defer behavior. + for i := len(closers) - 1; i >= 0; i-- { + c := closers[i] + merr = multierr.Append(merr, c()) + } + return merr + } + defer func() { + if rerr != nil { + rerr = multierr.Append(rerr, close()) + } + }() + ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return req, close, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + if r.ContentLength == 0 { + return req, close, validate.ErrBodyRequired + } + buf, err := io.ReadAll(r.Body) + if err != nil { + return req, close, err + } + + if len(buf) == 0 { + return req, close, validate.ErrBodyRequired + } + + d := jx.DecodeBytes(buf) + + var request SubscribeStationReq + if err := func() error { + if err := request.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return req, close, err + } + return &request, close, nil + default: + return req, close, validate.InvalidContentType(ct) + } +} diff --git a/pkg/rest/oas_request_encoders_gen.go b/pkg/rest/oas_request_encoders_gen.go new file mode 100644 index 0000000..d7ef56b --- /dev/null +++ b/pkg/rest/oas_request_encoders_gen.go @@ -0,0 +1,40 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "bytes" + "net/http" + + "github.com/go-faster/jx" + + ht "github.com/ogen-go/ogen/http" +) + +func encodeSubscribeSatelliteRequest( + req *SubscribeSatelliteReq, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} + +func encodeSubscribeStationRequest( + req *SubscribeStationReq, + r *http.Request, +) error { + const contentType = "application/json" + e := new(jx.Encoder) + { + req.Encode(e) + } + encoded := e.Bytes() + ht.SetBody(r, bytes.NewReader(encoded), contentType) + return nil +} diff --git a/pkg/rest/oas_response_decoders_gen.go b/pkg/rest/oas_response_decoders_gen.go new file mode 100644 index 0000000..94e5c4e --- /dev/null +++ b/pkg/rest/oas_response_decoders_gen.go @@ -0,0 +1,508 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "io" + "mime" + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + + "github.com/ogen-go/ogen/ogenerrors" + "github.com/ogen-go/ogen/validate" +) + +func decodeGetSatellitesResponse(resp *http.Response) (res *GetSatellitesOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response GetSatellitesOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeGetStationsResponse(resp *http.Response) (res *GetStationsOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response GetStationsOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeGetSubscriptionsResponse(resp *http.Response) (res *GetSubscriptionsOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response GetSubscriptionsOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + // Validate response. + if err := func() error { + if err := response.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + return res, errors.Wrap(err, "validate") + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeSubscribeSatelliteResponse(resp *http.Response) (res *SubscribeSatelliteOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response SubscribeSatelliteOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeSubscribeStationResponse(resp *http.Response) (res *SubscribeStationOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response SubscribeStationOK + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &response, nil + default: + return res, validate.InvalidContentType(ct) + } + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} + +func decodeUnsubscribeResponse(resp *http.Response) (res *UnsubscribeOK, _ error) { + switch resp.StatusCode { + case 200: + // Code 200. + return &UnsubscribeOK{}, nil + } + // Convenient error response. + defRes, err := func() (res *ErrorStatusCode, err error) { + ct, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return res, errors.Wrap(err, "parse media type") + } + switch { + case ct == "application/json": + buf, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + d := jx.DecodeBytes(buf) + + var response Error + if err := func() error { + if err := response.Decode(d); err != nil { + return err + } + if err := d.Skip(); err != io.EOF { + return errors.New("unexpected trailing data") + } + return nil + }(); err != nil { + err = &ogenerrors.DecodeBodyError{ + ContentType: ct, + Body: buf, + Err: err, + } + return res, err + } + return &ErrorStatusCode{ + StatusCode: resp.StatusCode, + Response: response, + }, nil + default: + return res, validate.InvalidContentType(ct) + } + }() + if err != nil { + return res, errors.Wrapf(err, "default (code %d)", resp.StatusCode) + } + return res, errors.Wrap(defRes, "error") +} diff --git a/pkg/rest/oas_response_encoders_gen.go b/pkg/rest/oas_response_encoders_gen.go new file mode 100644 index 0000000..af02cda --- /dev/null +++ b/pkg/rest/oas_response_encoders_gen.go @@ -0,0 +1,118 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "net/http" + + "github.com/go-faster/errors" + "github.com/go-faster/jx" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" + + ht "github.com/ogen-go/ogen/http" +) + +func encodeGetSatellitesResponse(response *GetSatellitesOK, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + +func encodeGetStationsResponse(response *GetStationsOK, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + +func encodeGetSubscriptionsResponse(response *GetSubscriptionsOK, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + +func encodeSubscribeSatelliteResponse(response *SubscribeSatelliteOK, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + +func encodeSubscribeStationResponse(response *SubscribeStationOK, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + e := new(jx.Encoder) + response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + return nil +} + +func encodeUnsubscribeResponse(response *UnsubscribeOK, w http.ResponseWriter, span trace.Span) error { + w.WriteHeader(200) + span.SetStatus(codes.Ok, http.StatusText(200)) + + return nil +} + +func encodeErrorResponse(response *ErrorStatusCode, w http.ResponseWriter, span trace.Span) error { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + code := response.StatusCode + if code == 0 { + // Set default status code. + code = http.StatusOK + } + w.WriteHeader(code) + if st := http.StatusText(code); code >= http.StatusBadRequest { + span.SetStatus(codes.Error, st) + } else { + span.SetStatus(codes.Ok, st) + } + + e := new(jx.Encoder) + response.Response.Encode(e) + if _, err := e.WriteTo(w); err != nil { + return errors.Wrap(err, "write") + } + + if code >= http.StatusInternalServerError { + return errors.Wrapf(ht.ErrInternalServerErrorResponse, "code: %d, message: %s", code, http.StatusText(code)) + } + return nil + +} diff --git a/pkg/rest/oas_router_gen.go b/pkg/rest/oas_router_gen.go new file mode 100644 index 0000000..ed7414a --- /dev/null +++ b/pkg/rest/oas_router_gen.go @@ -0,0 +1,396 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "net/http" + "net/url" + "strings" + + "github.com/ogen-go/ogen/uri" +) + +func (s *Server) cutPrefix(path string) (string, bool) { + prefix := s.cfg.Prefix + if prefix == "" { + return path, true + } + if !strings.HasPrefix(path, prefix) { + // Prefix doesn't match. + return "", false + } + // Cut prefix from the path. + return strings.TrimPrefix(path, prefix), true +} + +// ServeHTTP serves http request as defined by OpenAPI v3 specification, +// calling handler that matches the path or returning not found error. +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + elem := r.URL.Path + elemIsEscaped := false + if rawPath := r.URL.RawPath; rawPath != "" { + if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok { + elem = normalized + elemIsEscaped = strings.ContainsRune(elem, '%') + } + } + + elem, ok := s.cutPrefix(elem) + if !ok || len(elem) == 0 { + s.notFound(w, r) + return + } + + // Static code generated router with unwrapped path search. + switch { + default: + if len(elem) == 0 { + break + } + switch elem[0] { + case '/': // Prefix: "/s" + + if l := len("/s"); len(elem) >= l && elem[0:l] == "/s" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'a': // Prefix: "atellite" + + if l := len("atellite"); len(elem) >= l && elem[0:l] == "atellite" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch r.Method { + case "GET": + s.handleGetSatellitesRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + switch elem[0] { + case '/': // Prefix: "/subscribe" + + if l := len("/subscribe"); len(elem) >= l && elem[0:l] == "/subscribe" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleSubscribeSatelliteRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + + case 't': // Prefix: "tation" + + if l := len("tation"); len(elem) >= l && elem[0:l] == "tation" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch r.Method { + case "GET": + s.handleGetStationsRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "GET") + } + + return + } + switch elem[0] { + case '/': // Prefix: "/subscribe" + + if l := len("/subscribe"); len(elem) >= l && elem[0:l] == "/subscribe" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "POST": + s.handleSubscribeStationRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "POST") + } + + return + } + + } + + case 'u': // Prefix: "ubscription" + + if l := len("ubscription"); len(elem) >= l && elem[0:l] == "ubscription" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch r.Method { + case "DELETE": + s.handleUnsubscribeRequest([0]string{}, elemIsEscaped, w, r) + case "GET": + s.handleGetSubscriptionsRequest([0]string{}, elemIsEscaped, w, r) + default: + s.notAllowed(w, r, "DELETE,GET") + } + + return + } + + } + + } + } + s.notFound(w, r) +} + +// Route is route object. +type Route struct { + name string + summary string + operationID string + pathPattern string + count int + args [0]string +} + +// Name returns ogen operation name. +// +// It is guaranteed to be unique and not empty. +func (r Route) Name() string { + return r.name +} + +// Summary returns OpenAPI summary. +func (r Route) Summary() string { + return r.summary +} + +// OperationID returns OpenAPI operationId. +func (r Route) OperationID() string { + return r.operationID +} + +// PathPattern returns OpenAPI path. +func (r Route) PathPattern() string { + return r.pathPattern +} + +// Args returns parsed arguments. +func (r Route) Args() []string { + return r.args[:r.count] +} + +// FindRoute finds Route for given method and path. +// +// Note: this method does not unescape path or handle reserved characters in path properly. Use FindPath instead. +func (s *Server) FindRoute(method, path string) (Route, bool) { + return s.FindPath(method, &url.URL{Path: path}) +} + +// FindPath finds Route for given method and URL. +func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) { + var ( + elem = u.Path + args = r.args + ) + if rawPath := u.RawPath; rawPath != "" { + if normalized, ok := uri.NormalizeEscapedPath(rawPath); ok { + elem = normalized + } + defer func() { + for i, arg := range r.args[:r.count] { + if unescaped, err := url.PathUnescape(arg); err == nil { + r.args[i] = unescaped + } + } + }() + } + + elem, ok := s.cutPrefix(elem) + if !ok { + return r, false + } + + // Static code generated router with unwrapped path search. + switch { + default: + if len(elem) == 0 { + break + } + switch elem[0] { + case '/': // Prefix: "/s" + + if l := len("/s"); len(elem) >= l && elem[0:l] == "/s" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + break + } + switch elem[0] { + case 'a': // Prefix: "atellite" + + if l := len("atellite"); len(elem) >= l && elem[0:l] == "atellite" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "GET": + r.name = GetSatellitesOperation + r.summary = "Get available satellites" + r.operationID = "GetSatellites" + r.pathPattern = "/satellite" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + switch elem[0] { + case '/': // Prefix: "/subscribe" + + if l := len("/subscribe"); len(elem) >= l && elem[0:l] == "/subscribe" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = SubscribeSatelliteOperation + r.summary = "Subscribe to a given station" + r.operationID = "SubscribeSatellite" + r.pathPattern = "/satellite/subscribe" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + } + + case 't': // Prefix: "tation" + + if l := len("tation"); len(elem) >= l && elem[0:l] == "tation" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + switch method { + case "GET": + r.name = GetStationsOperation + r.summary = "Get available stations" + r.operationID = "GetStations" + r.pathPattern = "/station" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + switch elem[0] { + case '/': // Prefix: "/subscribe" + + if l := len("/subscribe"); len(elem) >= l && elem[0:l] == "/subscribe" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "POST": + r.name = SubscribeStationOperation + r.summary = "Subscribe to a given station" + r.operationID = "SubscribeStation" + r.pathPattern = "/station/subscribe" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + } + + case 'u': // Prefix: "ubscription" + + if l := len("ubscription"); len(elem) >= l && elem[0:l] == "ubscription" { + elem = elem[l:] + } else { + break + } + + if len(elem) == 0 { + // Leaf node. + switch method { + case "DELETE": + r.name = UnsubscribeOperation + r.summary = "Remove subscription by subscription ID" + r.operationID = "Unsubscribe" + r.pathPattern = "/subscription" + r.args = args + r.count = 0 + return r, true + case "GET": + r.name = GetSubscriptionsOperation + r.summary = "Get current subscriptions" + r.operationID = "GetSubscriptions" + r.pathPattern = "/subscription" + r.args = args + r.count = 0 + return r, true + default: + return + } + } + + } + + } + } + return r, false +} diff --git a/pkg/rest/oas_schemas_gen.go b/pkg/rest/oas_schemas_gen.go new file mode 100644 index 0000000..27919b3 --- /dev/null +++ b/pkg/rest/oas_schemas_gen.go @@ -0,0 +1,519 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "fmt" + "time" + + "github.com/go-faster/errors" + "github.com/google/uuid" +) + +func (s *ErrorStatusCode) Error() string { + return fmt.Sprintf("code %d: %+v", s.StatusCode, s.Response) +} + +// 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 +} + +// GetMessage returns the value of Message. +func (s *Error) GetMessage() string { + return s.Message +} + +// SetCode sets the value of Code. +func (s *Error) SetCode(val int) { + s.Code = val +} + +// SetMessage sets the value of Message. +func (s *Error) SetMessage(val string) { + s.Message = val +} + +// ErrorStatusCode wraps Error with StatusCode. +type ErrorStatusCode struct { + StatusCode int + Response Error +} + +// GetStatusCode returns the value of StatusCode. +func (s *ErrorStatusCode) GetStatusCode() int { + return s.StatusCode +} + +// GetResponse returns the value of Response. +func (s *ErrorStatusCode) GetResponse() Error { + return s.Response +} + +// SetStatusCode sets the value of StatusCode. +func (s *ErrorStatusCode) SetStatusCode(val int) { + s.StatusCode = val +} + +// SetResponse sets the value of Response. +func (s *ErrorStatusCode) SetResponse(val Error) { + s.Response = val +} + +type GetSatellitesOK struct { + Satellites []GetSatellitesOKSatellitesItem `json:"satellites"` +} + +// GetSatellites returns the value of Satellites. +func (s *GetSatellitesOK) GetSatellites() []GetSatellitesOKSatellitesItem { + return s.Satellites +} + +// SetSatellites sets the value of Satellites. +func (s *GetSatellitesOK) SetSatellites(val []GetSatellitesOKSatellitesItem) { + s.Satellites = val +} + +type GetSatellitesOKSatellitesItem struct { + ID uuid.UUID `json:"id"` + DisplayName string `json:"display_name"` + Status GetSatellitesOKSatellitesItemStatus `json:"status"` +} + +// GetID returns the value of ID. +func (s *GetSatellitesOKSatellitesItem) GetID() uuid.UUID { + return s.ID +} + +// GetDisplayName returns the value of DisplayName. +func (s *GetSatellitesOKSatellitesItem) GetDisplayName() string { + return s.DisplayName +} + +// GetStatus returns the value of Status. +func (s *GetSatellitesOKSatellitesItem) GetStatus() GetSatellitesOKSatellitesItemStatus { + return s.Status +} + +// SetID sets the value of ID. +func (s *GetSatellitesOKSatellitesItem) SetID(val uuid.UUID) { + s.ID = val +} + +// SetDisplayName sets the value of DisplayName. +func (s *GetSatellitesOKSatellitesItem) SetDisplayName(val string) { + s.DisplayName = val +} + +// SetStatus sets the value of Status. +func (s *GetSatellitesOKSatellitesItem) SetStatus(val GetSatellitesOKSatellitesItemStatus) { + s.Status = val +} + +type GetSatellitesOKSatellitesItemStatus string + +const ( + GetSatellitesOKSatellitesItemStatusActive GetSatellitesOKSatellitesItemStatus = "active" + GetSatellitesOKSatellitesItemStatusOffline GetSatellitesOKSatellitesItemStatus = "offline" + GetSatellitesOKSatellitesItemStatusBusy GetSatellitesOKSatellitesItemStatus = "busy" +) + +// AllValues returns all GetSatellitesOKSatellitesItemStatus values. +func (GetSatellitesOKSatellitesItemStatus) AllValues() []GetSatellitesOKSatellitesItemStatus { + return []GetSatellitesOKSatellitesItemStatus{ + GetSatellitesOKSatellitesItemStatusActive, + GetSatellitesOKSatellitesItemStatusOffline, + GetSatellitesOKSatellitesItemStatusBusy, + } +} + +// MarshalText implements encoding.TextMarshaler. +func (s GetSatellitesOKSatellitesItemStatus) MarshalText() ([]byte, error) { + switch s { + case GetSatellitesOKSatellitesItemStatusActive: + return []byte(s), nil + case GetSatellitesOKSatellitesItemStatusOffline: + return []byte(s), nil + case GetSatellitesOKSatellitesItemStatusBusy: + return []byte(s), nil + default: + return nil, errors.Errorf("invalid value: %q", s) + } +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (s *GetSatellitesOKSatellitesItemStatus) UnmarshalText(data []byte) error { + switch GetSatellitesOKSatellitesItemStatus(data) { + case GetSatellitesOKSatellitesItemStatusActive: + *s = GetSatellitesOKSatellitesItemStatusActive + return nil + case GetSatellitesOKSatellitesItemStatusOffline: + *s = GetSatellitesOKSatellitesItemStatusOffline + return nil + case GetSatellitesOKSatellitesItemStatusBusy: + *s = GetSatellitesOKSatellitesItemStatusBusy + return nil + default: + return errors.Errorf("invalid value: %q", data) + } +} + +type GetStationsOK struct { + Stations []GetStationsOKStationsItem `json:"stations"` +} + +// GetStations returns the value of Stations. +func (s *GetStationsOK) GetStations() []GetStationsOKStationsItem { + return s.Stations +} + +// SetStations sets the value of Stations. +func (s *GetStationsOK) SetStations(val []GetStationsOKStationsItem) { + s.Stations = val +} + +type GetStationsOKStationsItem struct { + ID uuid.UUID `json:"id"` + Slug string `json:"slug"` + Status GetStationsOKStationsItemStatus `json:"status"` +} + +// GetID returns the value of ID. +func (s *GetStationsOKStationsItem) GetID() uuid.UUID { + return s.ID +} + +// GetSlug returns the value of Slug. +func (s *GetStationsOKStationsItem) GetSlug() string { + return s.Slug +} + +// GetStatus returns the value of Status. +func (s *GetStationsOKStationsItem) GetStatus() GetStationsOKStationsItemStatus { + return s.Status +} + +// SetID sets the value of ID. +func (s *GetStationsOKStationsItem) SetID(val uuid.UUID) { + s.ID = val +} + +// SetSlug sets the value of Slug. +func (s *GetStationsOKStationsItem) SetSlug(val string) { + s.Slug = val +} + +// SetStatus sets the value of Status. +func (s *GetStationsOKStationsItem) SetStatus(val GetStationsOKStationsItemStatus) { + s.Status = val +} + +type GetStationsOKStationsItemStatus string + +const ( + GetStationsOKStationsItemStatusActive GetStationsOKStationsItemStatus = "active" + GetStationsOKStationsItemStatusOffline GetStationsOKStationsItemStatus = "offline" + GetStationsOKStationsItemStatusBusy GetStationsOKStationsItemStatus = "busy" +) + +// AllValues returns all GetStationsOKStationsItemStatus values. +func (GetStationsOKStationsItemStatus) AllValues() []GetStationsOKStationsItemStatus { + return []GetStationsOKStationsItemStatus{ + GetStationsOKStationsItemStatusActive, + GetStationsOKStationsItemStatusOffline, + GetStationsOKStationsItemStatusBusy, + } +} + +// MarshalText implements encoding.TextMarshaler. +func (s GetStationsOKStationsItemStatus) MarshalText() ([]byte, error) { + switch s { + case GetStationsOKStationsItemStatusActive: + return []byte(s), nil + case GetStationsOKStationsItemStatusOffline: + return []byte(s), nil + case GetStationsOKStationsItemStatusBusy: + return []byte(s), nil + default: + return nil, errors.Errorf("invalid value: %q", s) + } +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (s *GetStationsOKStationsItemStatus) UnmarshalText(data []byte) error { + switch GetStationsOKStationsItemStatus(data) { + case GetStationsOKStationsItemStatusActive: + *s = GetStationsOKStationsItemStatusActive + return nil + case GetStationsOKStationsItemStatusOffline: + *s = GetStationsOKStationsItemStatusOffline + return nil + case GetStationsOKStationsItemStatusBusy: + *s = GetStationsOKStationsItemStatusBusy + return nil + default: + return errors.Errorf("invalid value: %q", data) + } +} + +type GetSubscriptionsOK struct { + Subscriptions []GetSubscriptionsOKSubscriptionsItem `json:"subscriptions"` +} + +// GetSubscriptions returns the value of Subscriptions. +func (s *GetSubscriptionsOK) GetSubscriptions() []GetSubscriptionsOKSubscriptionsItem { + return s.Subscriptions +} + +// SetSubscriptions sets the value of Subscriptions. +func (s *GetSubscriptionsOK) SetSubscriptions(val []GetSubscriptionsOKSubscriptionsItem) { + s.Subscriptions = val +} + +type GetSubscriptionsOKSubscriptionsItem struct { + ID uuid.UUID `json:"id"` + Type GetSubscriptionsOKSubscriptionsItemType `json:"type"` + Status GetSubscriptionsOKSubscriptionsItemStatus `json:"status"` + CreatedAt time.Time `json:"created_at"` +} + +// GetID returns the value of ID. +func (s *GetSubscriptionsOKSubscriptionsItem) GetID() uuid.UUID { + return s.ID +} + +// GetType returns the value of Type. +func (s *GetSubscriptionsOKSubscriptionsItem) GetType() GetSubscriptionsOKSubscriptionsItemType { + return s.Type +} + +// GetStatus returns the value of Status. +func (s *GetSubscriptionsOKSubscriptionsItem) GetStatus() GetSubscriptionsOKSubscriptionsItemStatus { + return s.Status +} + +// GetCreatedAt returns the value of CreatedAt. +func (s *GetSubscriptionsOKSubscriptionsItem) GetCreatedAt() time.Time { + return s.CreatedAt +} + +// SetID sets the value of ID. +func (s *GetSubscriptionsOKSubscriptionsItem) SetID(val uuid.UUID) { + s.ID = val +} + +// SetType sets the value of Type. +func (s *GetSubscriptionsOKSubscriptionsItem) SetType(val GetSubscriptionsOKSubscriptionsItemType) { + s.Type = val +} + +// SetStatus sets the value of Status. +func (s *GetSubscriptionsOKSubscriptionsItem) SetStatus(val GetSubscriptionsOKSubscriptionsItemStatus) { + s.Status = val +} + +// SetCreatedAt sets the value of CreatedAt. +func (s *GetSubscriptionsOKSubscriptionsItem) SetCreatedAt(val time.Time) { + s.CreatedAt = val +} + +type GetSubscriptionsOKSubscriptionsItemStatus string + +const ( + GetSubscriptionsOKSubscriptionsItemStatusActive GetSubscriptionsOKSubscriptionsItemStatus = "active" + GetSubscriptionsOKSubscriptionsItemStatusOffline GetSubscriptionsOKSubscriptionsItemStatus = "offline" + GetSubscriptionsOKSubscriptionsItemStatusBusy GetSubscriptionsOKSubscriptionsItemStatus = "busy" +) + +// AllValues returns all GetSubscriptionsOKSubscriptionsItemStatus values. +func (GetSubscriptionsOKSubscriptionsItemStatus) AllValues() []GetSubscriptionsOKSubscriptionsItemStatus { + return []GetSubscriptionsOKSubscriptionsItemStatus{ + GetSubscriptionsOKSubscriptionsItemStatusActive, + GetSubscriptionsOKSubscriptionsItemStatusOffline, + GetSubscriptionsOKSubscriptionsItemStatusBusy, + } +} + +// MarshalText implements encoding.TextMarshaler. +func (s GetSubscriptionsOKSubscriptionsItemStatus) MarshalText() ([]byte, error) { + switch s { + case GetSubscriptionsOKSubscriptionsItemStatusActive: + return []byte(s), nil + case GetSubscriptionsOKSubscriptionsItemStatusOffline: + return []byte(s), nil + case GetSubscriptionsOKSubscriptionsItemStatusBusy: + return []byte(s), nil + default: + return nil, errors.Errorf("invalid value: %q", s) + } +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (s *GetSubscriptionsOKSubscriptionsItemStatus) UnmarshalText(data []byte) error { + switch GetSubscriptionsOKSubscriptionsItemStatus(data) { + case GetSubscriptionsOKSubscriptionsItemStatusActive: + *s = GetSubscriptionsOKSubscriptionsItemStatusActive + return nil + case GetSubscriptionsOKSubscriptionsItemStatusOffline: + *s = GetSubscriptionsOKSubscriptionsItemStatusOffline + return nil + case GetSubscriptionsOKSubscriptionsItemStatusBusy: + *s = GetSubscriptionsOKSubscriptionsItemStatusBusy + return nil + default: + return errors.Errorf("invalid value: %q", data) + } +} + +type GetSubscriptionsOKSubscriptionsItemType string + +const ( + GetSubscriptionsOKSubscriptionsItemTypeStation GetSubscriptionsOKSubscriptionsItemType = "station" + GetSubscriptionsOKSubscriptionsItemTypeSatellite GetSubscriptionsOKSubscriptionsItemType = "satellite" +) + +// AllValues returns all GetSubscriptionsOKSubscriptionsItemType values. +func (GetSubscriptionsOKSubscriptionsItemType) AllValues() []GetSubscriptionsOKSubscriptionsItemType { + return []GetSubscriptionsOKSubscriptionsItemType{ + GetSubscriptionsOKSubscriptionsItemTypeStation, + GetSubscriptionsOKSubscriptionsItemTypeSatellite, + } +} + +// MarshalText implements encoding.TextMarshaler. +func (s GetSubscriptionsOKSubscriptionsItemType) MarshalText() ([]byte, error) { + switch s { + case GetSubscriptionsOKSubscriptionsItemTypeStation: + return []byte(s), nil + case GetSubscriptionsOKSubscriptionsItemTypeSatellite: + return []byte(s), nil + default: + return nil, errors.Errorf("invalid value: %q", s) + } +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (s *GetSubscriptionsOKSubscriptionsItemType) UnmarshalText(data []byte) error { + switch GetSubscriptionsOKSubscriptionsItemType(data) { + case GetSubscriptionsOKSubscriptionsItemTypeStation: + *s = GetSubscriptionsOKSubscriptionsItemTypeStation + return nil + case GetSubscriptionsOKSubscriptionsItemTypeSatellite: + *s = GetSubscriptionsOKSubscriptionsItemTypeSatellite + return nil + default: + return errors.Errorf("invalid value: %q", data) + } +} + +// NewOptUUID returns new OptUUID with value set to v. +func NewOptUUID(v uuid.UUID) OptUUID { + return OptUUID{ + Value: v, + Set: true, + } +} + +// OptUUID is optional uuid.UUID. +type OptUUID struct { + Value uuid.UUID + Set bool +} + +// IsSet returns true if OptUUID was set. +func (o OptUUID) IsSet() bool { return o.Set } + +// Reset unsets value. +func (o *OptUUID) Reset() { + var v uuid.UUID + o.Value = v + o.Set = false +} + +// SetTo sets value to v. +func (o *OptUUID) SetTo(v uuid.UUID) { + o.Set = true + o.Value = v +} + +// Get returns value and boolean that denotes whether value was set. +func (o OptUUID) Get() (v uuid.UUID, 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 OptUUID) Or(d uuid.UUID) uuid.UUID { + if v, ok := o.Get(); ok { + return v + } + return d +} + +type SubscribeSatelliteOK struct { + ID uuid.UUID `json:"id"` +} + +// GetID returns the value of ID. +func (s *SubscribeSatelliteOK) GetID() uuid.UUID { + return s.ID +} + +// SetID sets the value of ID. +func (s *SubscribeSatelliteOK) SetID(val uuid.UUID) { + s.ID = val +} + +type SubscribeSatelliteReq struct { + ID uuid.UUID `json:"id"` +} + +// GetID returns the value of ID. +func (s *SubscribeSatelliteReq) GetID() uuid.UUID { + return s.ID +} + +// SetID sets the value of ID. +func (s *SubscribeSatelliteReq) SetID(val uuid.UUID) { + s.ID = val +} + +type SubscribeStationOK struct { + ID uuid.UUID `json:"id"` +} + +// GetID returns the value of ID. +func (s *SubscribeStationOK) GetID() uuid.UUID { + return s.ID +} + +// SetID sets the value of ID. +func (s *SubscribeStationOK) SetID(val uuid.UUID) { + s.ID = val +} + +type SubscribeStationReq struct { + // Subscription ID. + ID uuid.UUID `json:"id"` +} + +// GetID returns the value of ID. +func (s *SubscribeStationReq) GetID() uuid.UUID { + return s.ID +} + +// SetID sets the value of ID. +func (s *SubscribeStationReq) SetID(val uuid.UUID) { + s.ID = val +} + +// UnsubscribeOK is response for Unsubscribe operation. +type UnsubscribeOK struct{} diff --git a/pkg/rest/oas_server_gen.go b/pkg/rest/oas_server_gen.go new file mode 100644 index 0000000..4016c0e --- /dev/null +++ b/pkg/rest/oas_server_gen.go @@ -0,0 +1,70 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "context" +) + +// Handler handles operations described by OpenAPI v3 specification. +type Handler interface { + // GetSatellites implements GetSatellites operation. + // + // Get available satellites. + // + // GET /satellite + GetSatellites(ctx context.Context) (*GetSatellitesOK, error) + // GetStations implements GetStations operation. + // + // Get available stations. + // + // GET /station + GetStations(ctx context.Context) (*GetStationsOK, error) + // GetSubscriptions implements GetSubscriptions operation. + // + // Get current subscriptions. + // + // GET /subscription + GetSubscriptions(ctx context.Context) (*GetSubscriptionsOK, error) + // SubscribeSatellite implements SubscribeSatellite operation. + // + // Subscribe to a given station. + // + // POST /satellite/subscribe + SubscribeSatellite(ctx context.Context, req *SubscribeSatelliteReq) (*SubscribeSatelliteOK, error) + // SubscribeStation implements SubscribeStation operation. + // + // Subscribe to a given station. + // + // POST /station/subscribe + SubscribeStation(ctx context.Context, req *SubscribeStationReq) (*SubscribeStationOK, error) + // Unsubscribe implements Unsubscribe operation. + // + // Remove subscription by subscription ID. + // + // DELETE /subscription + Unsubscribe(ctx context.Context, params UnsubscribeParams) error + // NewError creates *ErrorStatusCode from error returned by handler. + // + // Used for common default response. + NewError(ctx context.Context, err error) *ErrorStatusCode +} + +// Server implements http server based on OpenAPI v3 specification and +// calls Handler to handle requests. +type Server struct { + h Handler + baseServer +} + +// NewServer creates new Server. +func NewServer(h Handler, opts ...ServerOption) (*Server, error) { + s, err := newServerConfig(opts...).baseServer() + if err != nil { + return nil, err + } + return &Server{ + h: h, + baseServer: s, + }, nil +} diff --git a/pkg/rest/oas_unimplemented_gen.go b/pkg/rest/oas_unimplemented_gen.go new file mode 100644 index 0000000..0ab88b2 --- /dev/null +++ b/pkg/rest/oas_unimplemented_gen.go @@ -0,0 +1,76 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "context" + + ht "github.com/ogen-go/ogen/http" +) + +// UnimplementedHandler is no-op Handler which returns http.ErrNotImplemented. +type UnimplementedHandler struct{} + +var _ Handler = UnimplementedHandler{} + +// GetSatellites implements GetSatellites operation. +// +// Get available satellites. +// +// GET /satellite +func (UnimplementedHandler) GetSatellites(ctx context.Context) (r *GetSatellitesOK, _ error) { + return r, ht.ErrNotImplemented +} + +// GetStations implements GetStations operation. +// +// Get available stations. +// +// GET /station +func (UnimplementedHandler) GetStations(ctx context.Context) (r *GetStationsOK, _ error) { + return r, ht.ErrNotImplemented +} + +// GetSubscriptions implements GetSubscriptions operation. +// +// Get current subscriptions. +// +// GET /subscription +func (UnimplementedHandler) GetSubscriptions(ctx context.Context) (r *GetSubscriptionsOK, _ error) { + return r, ht.ErrNotImplemented +} + +// SubscribeSatellite implements SubscribeSatellite operation. +// +// Subscribe to a given station. +// +// POST /satellite/subscribe +func (UnimplementedHandler) SubscribeSatellite(ctx context.Context, req *SubscribeSatelliteReq) (r *SubscribeSatelliteOK, _ error) { + return r, ht.ErrNotImplemented +} + +// SubscribeStation implements SubscribeStation operation. +// +// Subscribe to a given station. +// +// POST /station/subscribe +func (UnimplementedHandler) SubscribeStation(ctx context.Context, req *SubscribeStationReq) (r *SubscribeStationOK, _ error) { + return r, ht.ErrNotImplemented +} + +// Unsubscribe implements Unsubscribe operation. +// +// Remove subscription by subscription ID. +// +// DELETE /subscription +func (UnimplementedHandler) Unsubscribe(ctx context.Context, params UnsubscribeParams) error { + return ht.ErrNotImplemented +} + +// NewError creates *ErrorStatusCode from error returned by handler. +// +// Used for common default response. +func (UnimplementedHandler) NewError(ctx context.Context, err error) (r *ErrorStatusCode) { + r = new(ErrorStatusCode) + return r +} diff --git a/pkg/rest/oas_validators_gen.go b/pkg/rest/oas_validators_gen.go new file mode 100644 index 0000000..0223c6a --- /dev/null +++ b/pkg/rest/oas_validators_gen.go @@ -0,0 +1,258 @@ +// Code generated by ogen, DO NOT EDIT. + +package gsn + +import ( + "fmt" + + "github.com/go-faster/errors" + + "github.com/ogen-go/ogen/validate" +) + +func (s *GetSatellitesOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + var failures []validate.FieldError + for i, elem := range s.Satellites { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "satellites", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *GetSatellitesOKSatellitesItem) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Status.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "status", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s GetSatellitesOKSatellitesItemStatus) Validate() error { + switch s { + case "active": + return nil + case "offline": + return nil + case "busy": + return nil + default: + return errors.Errorf("invalid value: %v", s) + } +} + +func (s *GetStationsOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.Stations == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range s.Stations { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "stations", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *GetStationsOKStationsItem) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Status.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "status", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s GetStationsOKStationsItemStatus) Validate() error { + switch s { + case "active": + return nil + case "offline": + return nil + case "busy": + return nil + default: + return errors.Errorf("invalid value: %v", s) + } +} + +func (s *GetSubscriptionsOK) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if s.Subscriptions == nil { + return errors.New("nil is invalid value") + } + var failures []validate.FieldError + for i, elem := range s.Subscriptions { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "subscriptions", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s *GetSubscriptionsOKSubscriptionsItem) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Type.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "type", + Error: err, + }) + } + if err := func() error { + if err := s.Status.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "status", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + +func (s GetSubscriptionsOKSubscriptionsItemStatus) Validate() error { + switch s { + case "active": + return nil + case "offline": + return nil + case "busy": + return nil + default: + return errors.Errorf("invalid value: %v", s) + } +} + +func (s GetSubscriptionsOKSubscriptionsItemType) Validate() error { + switch s { + case "station": + return nil + case "satellite": + return nil + default: + return errors.Errorf("invalid value: %v", s) + } +}