chore: good enough
This commit is contained in:
parent
568f001ede
commit
bf16559d0d
30 changed files with 585 additions and 973 deletions
|
@ -7,6 +7,7 @@ RUN go mod download
|
|||
|
||||
# Development
|
||||
FROM base as dev
|
||||
RUN go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.1.0
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
|
||||
CMD ["air"]
|
||||
|
||||
|
@ -16,5 +17,6 @@ RUN go build cmd/server.go
|
|||
|
||||
# Production
|
||||
FROM gcr.io/distroless/static-debian12 as prod
|
||||
COPY --from=build /app/server /
|
||||
COPY --from=build /app/server /app/
|
||||
COPY --from=build /app/migrations /app/migrations
|
||||
ENTRYPOINT ["/server"]
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
### Development
|
||||
|
||||
Tools required:
|
||||
* Justfile
|
||||
* [Just](https://github.com/casey/just)
|
||||
* Docker and Docker Compose
|
||||
|
||||
```sh
|
||||
just run
|
||||
# Api is available at http://localhost:1234
|
||||
# Swagger is available at http://localhost:1235
|
||||
|
||||
curl -v localhost:1234/healthz
|
||||
```
|
||||
|
|
|
@ -8,7 +8,7 @@ info:
|
|||
description: >
|
||||
Example OpenAPI Golang server.
|
||||
servers:
|
||||
- url: http://localhost:1323/api/v1
|
||||
- url: http://localhost:1234
|
||||
description: Local server
|
||||
tags:
|
||||
- name: health
|
||||
|
@ -131,38 +131,6 @@ paths:
|
|||
$ref: '#/components/responses/NotFound'
|
||||
default:
|
||||
$ref: '#/components/responses/UnexpectedError'
|
||||
/users/{id}/group:
|
||||
put:
|
||||
description: Update user group by id
|
||||
tags:
|
||||
- users
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/Id'
|
||||
requestBody:
|
||||
description: Group object
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
group:
|
||||
$ref: '#/components/schemas/Id'
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
default:
|
||||
$ref: '#/components/responses/UnexpectedError'
|
||||
/groups:
|
||||
get:
|
||||
description: Get all groups
|
||||
|
@ -317,7 +285,7 @@ components:
|
|||
- id
|
||||
- name
|
||||
- email
|
||||
- group
|
||||
- group_id
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/Id'
|
||||
|
@ -327,13 +295,14 @@ components:
|
|||
email:
|
||||
type: string
|
||||
example: john@example.com
|
||||
group:
|
||||
$ref: '#/components/schemas/Group'
|
||||
group_id:
|
||||
$ref: '#/components/schemas/Id'
|
||||
UserUpdate:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- email
|
||||
- group_id
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
|
@ -341,22 +310,24 @@ components:
|
|||
email:
|
||||
type: string
|
||||
example: john@example.com
|
||||
group_id:
|
||||
$ref: '#/components/schemas/Id'
|
||||
Group:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
- users
|
||||
- user_ids
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/Id'
|
||||
name:
|
||||
type: string
|
||||
example: admins
|
||||
users:
|
||||
user_ids:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
$ref: '#/components/schemas/Id'
|
||||
GroupUpdate:
|
||||
type: object
|
||||
required:
|
||||
|
|
|
@ -1,23 +1,68 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/mentos1386/golang-rest-example/pkg/api"
|
||||
"github.com/mentos1386/golang-rest-example/pkg/openapi"
|
||||
|
||||
"github.com/ogen-go/ogen/middleware"
|
||||
"github.com/rs/cors"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
service := &api.ApiService{}
|
||||
func Logging(logger *zap.Logger) middleware.Middleware {
|
||||
return func(
|
||||
req middleware.Request,
|
||||
next func(req middleware.Request) (middleware.Response, error),
|
||||
) (middleware.Response, error) {
|
||||
logger := logger.With(
|
||||
zap.String("operation", req.OperationName),
|
||||
zap.String("operationId", req.OperationID),
|
||||
)
|
||||
logger.Info("Handling request")
|
||||
resp, err := next(req)
|
||||
if err != nil {
|
||||
logger.Error("Fail", zap.Error(err))
|
||||
} else {
|
||||
var fields []zapcore.Field
|
||||
// Some response types may have a status code.
|
||||
// ogen provides a getter for it.
|
||||
//
|
||||
// You can write your own interface to match any response type.
|
||||
if tresp, ok := resp.Type.(interface{ GetStatusCode() int }); ok {
|
||||
fields = []zapcore.Field{
|
||||
zap.Int("status_code", tresp.GetStatusCode()),
|
||||
}
|
||||
}
|
||||
logger.Info("Success", fields...)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
srv, err := openapi.NewServer(service)
|
||||
func main() {
|
||||
service := api.NewApiService()
|
||||
logger, _ := zap.NewDevelopment()
|
||||
|
||||
srv, err := openapi.NewServer(service, openapi.WithMiddleware(Logging(logger)))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Starting server on :1234")
|
||||
if err := http.ListenAndServe(":1234", srv); err != nil {
|
||||
address := fmt.Sprintf(":%d", service.Config.Port)
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedHeaders: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
})
|
||||
|
||||
logger.Info("Starting server", zap.String("address", address))
|
||||
if err := http.ListenAndServe(address, c.Handler(srv)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,14 @@ services:
|
|||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: example
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
app:
|
||||
build:
|
||||
|
@ -17,5 +24,20 @@ services:
|
|||
- 1234:1234
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:example@db:5432/postgres
|
||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres?sslmode=disable
|
||||
- PORT=1234
|
||||
|
||||
swagger:
|
||||
image: swaggerapi/swagger-ui
|
||||
ports:
|
||||
- 1235:8080
|
||||
environment:
|
||||
- SWAGGER_JSON=/api/openapi.yaml
|
||||
volumes:
|
||||
- ./api:/api
|
||||
depends_on:
|
||||
- app
|
||||
|
|
9
go.mod
9
go.mod
|
@ -5,11 +5,16 @@ go 1.21.6
|
|||
require (
|
||||
github.com/go-faster/errors v0.7.1
|
||||
github.com/go-faster/jx v1.1.0
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/ogen-go/ogen v0.81.2
|
||||
github.com/rs/cors v1.10.1
|
||||
go.opentelemetry.io/otel v1.22.0
|
||||
go.opentelemetry.io/otel/metric v1.22.0
|
||||
go.opentelemetry.io/otel/trace v1.22.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
go.uber.org/zap v1.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -20,10 +25,12 @@ require (
|
|||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // 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.uber.org/zap v1.26.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
||||
golang.org/x/net v0.20.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
|
|
48
go.sum
48
go.sum
|
@ -1,7 +1,22 @@
|
|||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/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/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M=
|
||||
github.com/dhui/dktest v0.4.0/go.mod h1:v/Dbz1LgCBOi2Uki2nUqLBGa83hWBGFMu5MrgMDCc78=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
|
@ -17,27 +32,54 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
|||
github.com/go-logr/logr v1.4.1/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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
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/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/ogen-go/ogen v0.81.2 h1:Dj5vSgC/1oqLE5t0T5qd4ARgsKTupJWsh3rW9/C7Lvk=
|
||||
github.com/ogen-go/ogen v0.81.2/go.mod h1:10Ch7SIzBMSLB8TVEt8KclMKkRyJ5qCh4Cfs0pdeoh8=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
|
||||
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
|
||||
|
@ -46,6 +88,8 @@ go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er
|
|||
go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY=
|
||||
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
|
||||
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
|
@ -54,6 +98,8 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
|||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
|
@ -64,6 +110,8 @@ golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
|||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
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=
|
||||
|
|
17
justfile
17
justfile
|
@ -5,16 +5,29 @@ _default:
|
|||
|
||||
# Run the app in development mode
|
||||
run:
|
||||
docker compose up --build app
|
||||
docker compose up --build app swagger
|
||||
|
||||
delete:
|
||||
docker compose down
|
||||
|
||||
# Generate OpenAPI files
|
||||
gen:
|
||||
docker compose run --rm app \
|
||||
docker compose run --build --rm app \
|
||||
go generate ./...
|
||||
|
||||
|
||||
# Create a new migration file
|
||||
migration-create name:
|
||||
@docker compose run --build --rm app \
|
||||
migrate create -ext sql -dir migrations -seq {{name}}
|
||||
|
||||
# Build production image
|
||||
build:
|
||||
docker build \
|
||||
--build-arg GO_VERSION=$(GO_VERSION) \
|
||||
-t golang-rest-example \
|
||||
.
|
||||
|
||||
# Run pgcli to connect to the database
|
||||
db-cli:
|
||||
docker compose exec db psql -U postgres -d postgres
|
||||
|
|
0
migrations/000001_init.down.sql
Normal file
0
migrations/000001_init.down.sql
Normal file
15
migrations/000001_init.up.sql
Normal file
15
migrations/000001_init.up.sql
Normal file
|
@ -0,0 +1,15 @@
|
|||
CREATE TABLE IF NOT EXISTS groups(
|
||||
id serial PRIMARY KEY,
|
||||
name VARCHAR (300) UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users(
|
||||
id serial PRIMARY KEY,
|
||||
email VARCHAR (300) UNIQUE NOT NULL,
|
||||
name VARCHAR (100) UNIQUE NOT NULL,
|
||||
|
||||
group_id INT,
|
||||
CONSTRAINT fk_users_groups_group_id
|
||||
FOREIGN KEY(group_id)
|
||||
REFERENCES groups(id)
|
||||
);
|
|
@ -1,11 +1,70 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
migrate "github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/mentos1386/golang-rest-example/pkg/config"
|
||||
"github.com/mentos1386/golang-rest-example/pkg/openapi"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ApiService struct {
|
||||
logger *zap.Logger
|
||||
db *sql.DB
|
||||
groups []*openapi.Group
|
||||
|
||||
Config *config.Config
|
||||
|
||||
openapi.UnimplementedHandler
|
||||
}
|
||||
|
||||
func NewApiService() *ApiService {
|
||||
logger, _ := zap.NewDevelopment()
|
||||
|
||||
config, err := config.NewConfig()
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to load the config", zap.Error(err))
|
||||
}
|
||||
|
||||
db, err := sql.Open("postgres", config.DatabaseURL)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to connect to the database", zap.Error(err))
|
||||
}
|
||||
db.SetMaxOpenConns(10)
|
||||
db.SetMaxIdleConns(10)
|
||||
db.SetConnMaxLifetime(time.Hour)
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to ping the database", zap.Error(err))
|
||||
}
|
||||
logger.Info("Connected to the database")
|
||||
|
||||
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to create the driver", zap.Error(err))
|
||||
}
|
||||
m, err := migrate.NewWithDatabaseInstance(
|
||||
"file:///app/migrations",
|
||||
"postgres", driver)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to create the migration", zap.Error(err))
|
||||
}
|
||||
err = m.Up()
|
||||
if err != nil && err != migrate.ErrNoChange {
|
||||
logger.Fatal("Failed to apply the migrations", zap.Error(err))
|
||||
}
|
||||
|
||||
logger.Info("Migrations applied")
|
||||
|
||||
return &ApiService{
|
||||
logger: logger,
|
||||
db: db,
|
||||
Config: config,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,139 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/mentos1386/golang-rest-example/pkg/openapi"
|
||||
)
|
||||
|
||||
func (u *ApiService) GroupsGet(ctx context.Context) ([]openapi.Group, error) {
|
||||
var groups []openapi.Group
|
||||
for _, group := range u.groups {
|
||||
groups = append(groups, *group)
|
||||
type Group struct {
|
||||
ID int64
|
||||
Name string
|
||||
UserIds []int64
|
||||
}
|
||||
groups = append(groups, openapi.Group{ID: openapi.ID(1), Name: "Admins"})
|
||||
|
||||
func (u *ApiService) getUsersForGroupId(id openapi.ID) ([]openapi.ID, error) {
|
||||
rows, err := u.db.Query("SELECT id FROM users WHERE group_id = $1", id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var userIds []openapi.ID
|
||||
for rows.Next() {
|
||||
var userId int64
|
||||
err = rows.Scan(&userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIds = append(userIds, openapi.ID(userId))
|
||||
}
|
||||
return userIds, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) GroupsGet(ctx context.Context) ([]openapi.Group, error) {
|
||||
rows, err := u.db.Query("SELECT * FROM groups")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var groups []openapi.Group
|
||||
|
||||
for rows.Next() {
|
||||
var group Group
|
||||
err = rows.Scan(&group.ID, &group.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIds, err := u.getUsersForGroupId(openapi.ID(group.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groups = append(groups, openapi.Group{
|
||||
ID: openapi.ID(group.ID),
|
||||
Name: group.Name,
|
||||
UserIds: userIds,
|
||||
})
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) GroupsPost(ctx context.Context, group *openapi.GroupUpdate) (*openapi.Group, error) {
|
||||
row := u.db.QueryRow("INSERT INTO groups (name) VALUES ($1) RETURNING id", group.Name)
|
||||
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Just created group has no users
|
||||
var userIds []openapi.ID
|
||||
|
||||
return &openapi.Group{
|
||||
ID: openapi.ID(id),
|
||||
Name: group.Name,
|
||||
UserIds: userIds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) GroupsIDGet(ctx context.Context, params openapi.GroupsIDGetParams) (openapi.GroupsIDGetRes, error) {
|
||||
row := u.db.QueryRow("SELECT * FROM groups WHERE id = $1", params.ID)
|
||||
var group Group
|
||||
err := row.Scan(&group.ID, &group.Name)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return &openapi.Error{Message: "Group not found", Code: 404}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userIds, err := u.getUsersForGroupId(openapi.ID(group.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &openapi.Group{
|
||||
ID: openapi.ID(group.ID),
|
||||
Name: group.Name,
|
||||
UserIds: userIds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) GroupsIDPut(ctx context.Context, group *openapi.GroupUpdate, params openapi.GroupsIDPutParams) (openapi.GroupsIDPutRes, error) {
|
||||
res, err := u.db.Exec("UPDATE groups SET name = $1 WHERE id = $2", group.Name, params.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rows, _ := res.RowsAffected(); rows == 0 {
|
||||
return &openapi.Error{Message: "Group not found", Code: 404}, nil
|
||||
}
|
||||
|
||||
userIds, err := u.getUsersForGroupId(params.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &openapi.Group{
|
||||
ID: params.ID,
|
||||
Name: group.Name,
|
||||
UserIds: userIds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) GroupsIDDelete(ctx context.Context, params openapi.GroupsIDDeleteParams) (openapi.GroupsIDDeleteRes, error) {
|
||||
res, err := u.db.Exec("DELETE FROM groups WHERE id = $1", params.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rows, _ := res.RowsAffected(); rows == 0 {
|
||||
return &openapi.Error{Message: "Group not found", Code: 404}, nil
|
||||
}
|
||||
|
||||
return &openapi.Ok{Message: "OK"}, nil
|
||||
}
|
||||
|
|
15
pkg/api/healthz.go
Normal file
15
pkg/api/healthz.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/mentos1386/golang-rest-example/pkg/openapi"
|
||||
)
|
||||
|
||||
func (u *ApiService) HealthzGet(ctx context.Context) (*openapi.Ok, error) {
|
||||
err := u.db.Ping()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &openapi.Ok{Message: "OK"}, nil
|
||||
}
|
105
pkg/api/users.go
105
pkg/api/users.go
|
@ -1,3 +1,106 @@
|
|||
package api
|
||||
|
||||
type UserServer struct{}
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/mentos1386/golang-rest-example/pkg/openapi"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
Name string
|
||||
Email string
|
||||
GroupID int64
|
||||
}
|
||||
|
||||
func (u *ApiService) UsersGet(ctx context.Context) ([]openapi.User, error) {
|
||||
rows, err := u.db.Query("SELECT * FROM users")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []openapi.User
|
||||
|
||||
for rows.Next() {
|
||||
var user User
|
||||
err = rows.Scan(&user.ID, &user.Name, &user.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users = append(users, openapi.User{
|
||||
ID: openapi.ID(user.ID),
|
||||
Name: user.Name,
|
||||
})
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) UsersPost(ctx context.Context, user *openapi.UserUpdate) (*openapi.User, error) {
|
||||
row := u.db.QueryRow("INSERT INTO users (name, email, group_id) VALUES ($1, $2, $3) RETURNING id", user.Name, user.Email, user.GroupID)
|
||||
|
||||
var id int64
|
||||
err := row.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &openapi.User{
|
||||
ID: openapi.ID(id),
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
GroupID: user.GroupID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) UsersIDGet(ctx context.Context, params openapi.UsersIDGetParams) (openapi.UsersIDGetRes, error) {
|
||||
row := u.db.QueryRow("SELECT * FROM users WHERE id = $1", params.ID)
|
||||
var user User
|
||||
err := row.Scan(&user.ID, &user.Name)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return &openapi.Error{Message: "User not found", Code: 404}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
return &openapi.User{
|
||||
ID: openapi.ID(user.ID),
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
GroupID: openapi.ID(user.GroupID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) UsersIDPut(ctx context.Context, user *openapi.UserUpdate, params openapi.UsersIDPutParams) (openapi.UsersIDPutRes, error) {
|
||||
res, err := u.db.Exec("UPDATE users SET name = $1, email = $2, group_id = $3 WHERE id = $4", user.Name, user.Email, user.GroupID, params.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rows, _ := res.RowsAffected(); rows == 0 {
|
||||
return &openapi.Error{Message: "User not found", Code: 404}, nil
|
||||
}
|
||||
|
||||
return &openapi.User{
|
||||
ID: params.ID,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
GroupID: openapi.ID(user.GroupID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *ApiService) UsersIDDelete(ctx context.Context, params openapi.UsersIDDeleteParams) (openapi.UsersIDDeleteRes, error) {
|
||||
res, err := u.db.Exec("DELETE FROM users WHERE id = $1", params.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rows, _ := res.RowsAffected(); rows == 0 {
|
||||
return &openapi.Error{Message: "User not found", Code: 404}, nil
|
||||
}
|
||||
|
||||
return &openapi.Ok{Message: "OK"}, nil
|
||||
}
|
||||
|
|
19
pkg/config/config.go
Normal file
19
pkg/config/config.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port int `envconfig:"PORT" default:"8080"`
|
||||
DatabaseURL string `envconfig:"DATABASE_URL" required:"true"`
|
||||
}
|
||||
|
||||
func NewConfig() (*Config, error) {
|
||||
var cfg Config
|
||||
err := envconfig.Process("", &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
|
@ -74,12 +74,6 @@ type Invoker interface {
|
|||
//
|
||||
// GET /users/{id}
|
||||
UsersIDGet(ctx context.Context, params UsersIDGetParams) (UsersIDGetRes, error)
|
||||
// UsersIDGroupPut invokes PUT /users/{id}/group operation.
|
||||
//
|
||||
// Update user group by id.
|
||||
//
|
||||
// PUT /users/{id}/group
|
||||
UsersIDGroupPut(ctx context.Context, request *UsersIDGroupPutReq, params UsersIDGroupPutParams) (UsersIDGroupPutRes, error)
|
||||
// UsersIDPut invokes PUT /users/{id} operation.
|
||||
//
|
||||
// Update user by id.
|
||||
|
@ -894,102 +888,6 @@ func (c *Client) sendUsersIDGet(ctx context.Context, params UsersIDGetParams) (r
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// UsersIDGroupPut invokes PUT /users/{id}/group operation.
|
||||
//
|
||||
// Update user group by id.
|
||||
//
|
||||
// PUT /users/{id}/group
|
||||
func (c *Client) UsersIDGroupPut(ctx context.Context, request *UsersIDGroupPutReq, params UsersIDGroupPutParams) (UsersIDGroupPutRes, error) {
|
||||
res, err := c.sendUsersIDGroupPut(ctx, request, params)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c *Client) sendUsersIDGroupPut(ctx context.Context, request *UsersIDGroupPutReq, params UsersIDGroupPutParams) (res UsersIDGroupPutRes, err error) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
semconv.HTTPMethodKey.String("PUT"),
|
||||
semconv.HTTPRouteKey.String("/users/{id}/group"),
|
||||
}
|
||||
|
||||
// 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(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, "UsersIDGroupPut",
|
||||
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 [3]string
|
||||
pathParts[0] = "/users/"
|
||||
{
|
||||
// Encode "id" parameter.
|
||||
e := uri.NewPathEncoder(uri.PathEncoderConfig{
|
||||
Param: "id",
|
||||
Style: uri.PathStyleSimple,
|
||||
Explode: false,
|
||||
})
|
||||
if err := func() error {
|
||||
if unwrapped := int64(params.ID); true {
|
||||
return e.EncodeValue(conv.Int64ToString(unwrapped))
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return res, errors.Wrap(err, "encode path")
|
||||
}
|
||||
encoded, err := e.Result()
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "encode path")
|
||||
}
|
||||
pathParts[1] = encoded
|
||||
}
|
||||
pathParts[2] = "/group"
|
||||
uri.AddPathParts(u, pathParts[:]...)
|
||||
|
||||
stage = "EncodeRequest"
|
||||
r, err := ht.NewRequest(ctx, "PUT", u)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "create request")
|
||||
}
|
||||
if err := encodeUsersIDGroupPutRequest(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 := decodeUsersIDGroupPutResponse(resp)
|
||||
if err != nil {
|
||||
return res, errors.Wrap(err, "decode response")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// UsersIDPut invokes PUT /users/{id} operation.
|
||||
//
|
||||
// Update user by id.
|
||||
|
|
|
@ -1019,137 +1019,6 @@ func (s *Server) handleUsersIDGetRequest(args [1]string, argsEscaped bool, w htt
|
|||
}
|
||||
}
|
||||
|
||||
// handleUsersIDGroupPutRequest handles PUT /users/{id}/group operation.
|
||||
//
|
||||
// Update user group by id.
|
||||
//
|
||||
// PUT /users/{id}/group
|
||||
func (s *Server) handleUsersIDGroupPutRequest(args [1]string, argsEscaped bool, w http.ResponseWriter, r *http.Request) {
|
||||
otelAttrs := []attribute.KeyValue{
|
||||
semconv.HTTPMethodKey.String("PUT"),
|
||||
semconv.HTTPRouteKey.String("/users/{id}/group"),
|
||||
}
|
||||
|
||||
// Start a span for this request.
|
||||
ctx, span := s.cfg.Tracer.Start(r.Context(), "UsersIDGroupPut",
|
||||
trace.WithAttributes(otelAttrs...),
|
||||
serverSpanKind,
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
// Run stopwatch.
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedDuration := time.Since(startTime)
|
||||
// Use floating point division here for higher precision (instead of Millisecond method).
|
||||
s.duration.Record(ctx, float64(float64(elapsedDuration)/float64(time.Millisecond)), metric.WithAttributes(otelAttrs...))
|
||||
}()
|
||||
|
||||
// Increment request counter.
|
||||
s.requests.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
|
||||
|
||||
var (
|
||||
recordError = func(stage string, err error) {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, stage)
|
||||
s.errors.Add(ctx, 1, metric.WithAttributes(otelAttrs...))
|
||||
}
|
||||
err error
|
||||
opErrContext = ogenerrors.OperationContext{
|
||||
Name: "UsersIDGroupPut",
|
||||
ID: "",
|
||||
}
|
||||
)
|
||||
params, err := decodeUsersIDGroupPutParams(args, argsEscaped, r)
|
||||
if err != nil {
|
||||
err = &ogenerrors.DecodeParamsError{
|
||||
OperationContext: opErrContext,
|
||||
Err: err,
|
||||
}
|
||||
recordError("DecodeParams", err)
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
request, close, err := s.decodeUsersIDGroupPutRequest(r)
|
||||
if err != nil {
|
||||
err = &ogenerrors.DecodeRequestError{
|
||||
OperationContext: opErrContext,
|
||||
Err: err,
|
||||
}
|
||||
recordError("DecodeRequest", err)
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := close(); err != nil {
|
||||
recordError("CloseRequest", err)
|
||||
}
|
||||
}()
|
||||
|
||||
var response UsersIDGroupPutRes
|
||||
if m := s.cfg.Middleware; m != nil {
|
||||
mreq := middleware.Request{
|
||||
Context: ctx,
|
||||
OperationName: "UsersIDGroupPut",
|
||||
OperationSummary: "",
|
||||
OperationID: "",
|
||||
Body: request,
|
||||
Params: middleware.Parameters{
|
||||
{
|
||||
Name: "id",
|
||||
In: "path",
|
||||
}: params.ID,
|
||||
},
|
||||
Raw: r,
|
||||
}
|
||||
|
||||
type (
|
||||
Request = *UsersIDGroupPutReq
|
||||
Params = UsersIDGroupPutParams
|
||||
Response = UsersIDGroupPutRes
|
||||
)
|
||||
response, err = middleware.HookMiddleware[
|
||||
Request,
|
||||
Params,
|
||||
Response,
|
||||
](
|
||||
m,
|
||||
mreq,
|
||||
unpackUsersIDGroupPutParams,
|
||||
func(ctx context.Context, request Request, params Params) (response Response, err error) {
|
||||
response, err = s.h.UsersIDGroupPut(ctx, request, params)
|
||||
return response, err
|
||||
},
|
||||
)
|
||||
} else {
|
||||
response, err = s.h.UsersIDGroupPut(ctx, request, params)
|
||||
}
|
||||
if err != nil {
|
||||
if errRes, ok := errors.Into[*ErrorStatusCode](err); ok {
|
||||
if err := encodeErrorResponse(errRes, w, span); err != nil {
|
||||
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 {
|
||||
recordError("Internal", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := encodeUsersIDGroupPutResponse(response, w, span); err != nil {
|
||||
recordError("EncodeResponse", err)
|
||||
if !errors.Is(err, ht.ErrInternalServerErrorResponse) {
|
||||
s.cfg.ErrorHandler(ctx, w, r, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handleUsersIDPutRequest handles PUT /users/{id} operation.
|
||||
//
|
||||
// Update user by id.
|
||||
|
|
|
@ -21,10 +21,6 @@ type UsersIDGetRes interface {
|
|||
usersIDGetRes()
|
||||
}
|
||||
|
||||
type UsersIDGroupPutRes interface {
|
||||
usersIDGroupPutRes()
|
||||
}
|
||||
|
||||
type UsersIDPutRes interface {
|
||||
usersIDPutRes()
|
||||
}
|
||||
|
|
|
@ -143,9 +143,9 @@ func (s *Group) encodeFields(e *jx.Encoder) {
|
|||
e.Str(s.Name)
|
||||
}
|
||||
{
|
||||
e.FieldStart("users")
|
||||
e.FieldStart("user_ids")
|
||||
e.ArrStart()
|
||||
for _, elem := range s.Users {
|
||||
for _, elem := range s.UserIds {
|
||||
elem.Encode(e)
|
||||
}
|
||||
e.ArrEnd()
|
||||
|
@ -155,7 +155,7 @@ func (s *Group) encodeFields(e *jx.Encoder) {
|
|||
var jsonFieldsNameOfGroup = [3]string{
|
||||
0: "id",
|
||||
1: "name",
|
||||
2: "users",
|
||||
2: "user_ids",
|
||||
}
|
||||
|
||||
// Decode decodes Group from json.
|
||||
|
@ -189,23 +189,23 @@ func (s *Group) Decode(d *jx.Decoder) error {
|
|||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"name\"")
|
||||
}
|
||||
case "users":
|
||||
case "user_ids":
|
||||
requiredBitSet[0] |= 1 << 2
|
||||
if err := func() error {
|
||||
s.Users = make([]User, 0)
|
||||
s.UserIds = make([]ID, 0)
|
||||
if err := d.Arr(func(d *jx.Decoder) error {
|
||||
var elem User
|
||||
var elem ID
|
||||
if err := elem.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Users = append(s.Users, elem)
|
||||
s.UserIds = append(s.UserIds, elem)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"users\"")
|
||||
return errors.Wrap(err, "decode field \"user_ids\"")
|
||||
}
|
||||
default:
|
||||
return d.Skip()
|
||||
|
@ -495,39 +495,6 @@ func (s *Ok) UnmarshalJSON(data []byte) error {
|
|||
return s.Decode(d)
|
||||
}
|
||||
|
||||
// Encode encodes ID as json.
|
||||
func (o OptID) Encode(e *jx.Encoder) {
|
||||
if !o.Set {
|
||||
return
|
||||
}
|
||||
o.Value.Encode(e)
|
||||
}
|
||||
|
||||
// Decode decodes ID from json.
|
||||
func (o *OptID) Decode(d *jx.Decoder) error {
|
||||
if o == nil {
|
||||
return errors.New("invalid: unable to decode OptID to nil")
|
||||
}
|
||||
o.Set = true
|
||||
if err := o.Value.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements stdjson.Marshaler.
|
||||
func (s OptID) MarshalJSON() ([]byte, error) {
|
||||
e := jx.Encoder{}
|
||||
s.Encode(&e)
|
||||
return e.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements stdjson.Unmarshaler.
|
||||
func (s *OptID) UnmarshalJSON(data []byte) error {
|
||||
d := jx.DecodeBytes(data)
|
||||
return s.Decode(d)
|
||||
}
|
||||
|
||||
// Encode implements json.Marshaler.
|
||||
func (s *User) Encode(e *jx.Encoder) {
|
||||
e.ObjStart()
|
||||
|
@ -550,8 +517,8 @@ func (s *User) encodeFields(e *jx.Encoder) {
|
|||
e.Str(s.Email)
|
||||
}
|
||||
{
|
||||
e.FieldStart("group")
|
||||
s.Group.Encode(e)
|
||||
e.FieldStart("group_id")
|
||||
s.GroupID.Encode(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -559,7 +526,7 @@ var jsonFieldsNameOfUser = [4]string{
|
|||
0: "id",
|
||||
1: "name",
|
||||
2: "email",
|
||||
3: "group",
|
||||
3: "group_id",
|
||||
}
|
||||
|
||||
// Decode decodes User from json.
|
||||
|
@ -605,15 +572,15 @@ func (s *User) Decode(d *jx.Decoder) error {
|
|||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"email\"")
|
||||
}
|
||||
case "group":
|
||||
case "group_id":
|
||||
requiredBitSet[0] |= 1 << 3
|
||||
if err := func() error {
|
||||
if err := s.Group.Decode(d); err != nil {
|
||||
if err := s.GroupID.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"group\"")
|
||||
return errors.Wrap(err, "decode field \"group_id\"")
|
||||
}
|
||||
default:
|
||||
return d.Skip()
|
||||
|
@ -688,11 +655,16 @@ func (s *UserUpdate) encodeFields(e *jx.Encoder) {
|
|||
e.FieldStart("email")
|
||||
e.Str(s.Email)
|
||||
}
|
||||
{
|
||||
e.FieldStart("group_id")
|
||||
s.GroupID.Encode(e)
|
||||
}
|
||||
}
|
||||
|
||||
var jsonFieldsNameOfUserUpdate = [2]string{
|
||||
var jsonFieldsNameOfUserUpdate = [3]string{
|
||||
0: "name",
|
||||
1: "email",
|
||||
2: "group_id",
|
||||
}
|
||||
|
||||
// Decode decodes UserUpdate from json.
|
||||
|
@ -728,6 +700,16 @@ func (s *UserUpdate) Decode(d *jx.Decoder) error {
|
|||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"email\"")
|
||||
}
|
||||
case "group_id":
|
||||
requiredBitSet[0] |= 1 << 2
|
||||
if err := func() error {
|
||||
if err := s.GroupID.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"group_id\"")
|
||||
}
|
||||
default:
|
||||
return d.Skip()
|
||||
}
|
||||
|
@ -738,7 +720,7 @@ func (s *UserUpdate) Decode(d *jx.Decoder) error {
|
|||
// Validate required fields.
|
||||
var failures []validate.FieldError
|
||||
for i, mask := range [1]uint8{
|
||||
0b00000011,
|
||||
0b00000111,
|
||||
} {
|
||||
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
|
||||
// Mask only required fields and check equality to mask using XOR.
|
||||
|
@ -783,66 +765,3 @@ func (s *UserUpdate) UnmarshalJSON(data []byte) error {
|
|||
d := jx.DecodeBytes(data)
|
||||
return s.Decode(d)
|
||||
}
|
||||
|
||||
// Encode implements json.Marshaler.
|
||||
func (s *UsersIDGroupPutReq) Encode(e *jx.Encoder) {
|
||||
e.ObjStart()
|
||||
s.encodeFields(e)
|
||||
e.ObjEnd()
|
||||
}
|
||||
|
||||
// encodeFields encodes fields.
|
||||
func (s *UsersIDGroupPutReq) encodeFields(e *jx.Encoder) {
|
||||
{
|
||||
if s.Group.Set {
|
||||
e.FieldStart("group")
|
||||
s.Group.Encode(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var jsonFieldsNameOfUsersIDGroupPutReq = [1]string{
|
||||
0: "group",
|
||||
}
|
||||
|
||||
// Decode decodes UsersIDGroupPutReq from json.
|
||||
func (s *UsersIDGroupPutReq) Decode(d *jx.Decoder) error {
|
||||
if s == nil {
|
||||
return errors.New("invalid: unable to decode UsersIDGroupPutReq to nil")
|
||||
}
|
||||
|
||||
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
|
||||
switch string(k) {
|
||||
case "group":
|
||||
if err := func() error {
|
||||
s.Group.Reset()
|
||||
if err := s.Group.Decode(d); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return errors.Wrap(err, "decode field \"group\"")
|
||||
}
|
||||
default:
|
||||
return d.Skip()
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "decode UsersIDGroupPutReq")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements stdjson.Marshaler.
|
||||
func (s *UsersIDGroupPutReq) MarshalJSON() ([]byte, error) {
|
||||
e := jx.Encoder{}
|
||||
s.Encode(&e)
|
||||
return e.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements stdjson.Unmarshaler.
|
||||
func (s *UsersIDGroupPutReq) UnmarshalJSON(data []byte) error {
|
||||
d := jx.DecodeBytes(data)
|
||||
return s.Decode(d)
|
||||
}
|
||||
|
|
|
@ -375,78 +375,6 @@ func decodeUsersIDGetParams(args [1]string, argsEscaped bool, r *http.Request) (
|
|||
return params, nil
|
||||
}
|
||||
|
||||
// UsersIDGroupPutParams is parameters of PUT /users/{id}/group operation.
|
||||
type UsersIDGroupPutParams struct {
|
||||
ID ID
|
||||
}
|
||||
|
||||
func unpackUsersIDGroupPutParams(packed middleware.Parameters) (params UsersIDGroupPutParams) {
|
||||
{
|
||||
key := middleware.ParameterKey{
|
||||
Name: "id",
|
||||
In: "path",
|
||||
}
|
||||
params.ID = packed[key].(ID)
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func decodeUsersIDGroupPutParams(args [1]string, argsEscaped bool, r *http.Request) (params UsersIDGroupPutParams, _ error) {
|
||||
// Decode path: id.
|
||||
if err := func() error {
|
||||
param := args[0]
|
||||
if argsEscaped {
|
||||
unescaped, err := url.PathUnescape(args[0])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unescape path")
|
||||
}
|
||||
param = unescaped
|
||||
}
|
||||
if len(param) > 0 {
|
||||
d := uri.NewPathDecoder(uri.PathDecoderConfig{
|
||||
Param: "id",
|
||||
Value: param,
|
||||
Style: uri.PathStyleSimple,
|
||||
Explode: false,
|
||||
})
|
||||
|
||||
if err := func() error {
|
||||
var paramsDotIDVal int64
|
||||
if err := func() error {
|
||||
val, err := d.DecodeValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := conv.ToInt64(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paramsDotIDVal = c
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
params.ID = ID(paramsDotIDVal)
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return validate.ErrFieldRequired
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return params, &ogenerrors.DecodeParamError{
|
||||
Name: "id",
|
||||
In: "path",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// UsersIDPutParams is parameters of PUT /users/{id} operation.
|
||||
type UsersIDPutParams struct {
|
||||
ID ID
|
||||
|
|
|
@ -141,69 +141,6 @@ func (s *Server) decodeGroupsPostRequest(r *http.Request) (
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Server) decodeUsersIDGroupPutRequest(r *http.Request) (
|
||||
req *UsersIDGroupPutReq,
|
||||
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 UsersIDGroupPutReq
|
||||
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) decodeUsersIDPutRequest(r *http.Request) (
|
||||
req *UserUpdate,
|
||||
close func() error,
|
||||
|
|
|
@ -39,20 +39,6 @@ func encodeGroupsPostRequest(
|
|||
return nil
|
||||
}
|
||||
|
||||
func encodeUsersIDGroupPutRequest(
|
||||
req *UsersIDGroupPutReq,
|
||||
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 encodeUsersIDPutRequest(
|
||||
req *UserUpdate,
|
||||
r *http.Request,
|
||||
|
|
|
@ -725,23 +725,6 @@ func decodeUsersGetResponse(resp *http.Response) (res []User, _ error) {
|
|||
if response == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range response {
|
||||
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 {
|
||||
return res, errors.Wrap(err, "validate")
|
||||
|
@ -947,142 +930,6 @@ func decodeUsersIDGetResponse(resp *http.Response) (res UsersIDGetRes, _ error)
|
|||
}
|
||||
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)
|
||||
}
|
||||
case 404:
|
||||
// Code 404.
|
||||
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 &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 decodeUsersIDGroupPutResponse(resp *http.Response) (res UsersIDGroupPutRes, _ 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 User
|
||||
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)
|
||||
|
@ -1201,15 +1048,6 @@ func decodeUsersIDPutResponse(resp *http.Response) (res UsersIDPutRes, _ error)
|
|||
}
|
||||
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)
|
||||
|
@ -1328,15 +1166,6 @@ func decodeUsersPostResponse(resp *http.Response) (res *User, _ error) {
|
|||
}
|
||||
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)
|
||||
|
|
|
@ -242,39 +242,6 @@ func encodeUsersIDGetResponse(response UsersIDGetRes, w http.ResponseWriter, spa
|
|||
}
|
||||
}
|
||||
|
||||
func encodeUsersIDGroupPutResponse(response UsersIDGroupPutRes, w http.ResponseWriter, span trace.Span) error {
|
||||
switch response := response.(type) {
|
||||
case *User:
|
||||
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
|
||||
|
||||
case *Error:
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(404)
|
||||
span.SetStatus(codes.Error, http.StatusText(404))
|
||||
|
||||
e := new(jx.Encoder)
|
||||
response.Encode(e)
|
||||
if _, err := e.WriteTo(w); err != nil {
|
||||
return errors.Wrap(err, "write")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return errors.Errorf("unexpected response type: %T", response)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeUsersIDPutResponse(response UsersIDPutRes, w http.ResponseWriter, span trace.Span) error {
|
||||
switch response := response.(type) {
|
||||
case *User:
|
||||
|
|
|
@ -172,15 +172,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// Param: "id"
|
||||
// Match until "/"
|
||||
idx := strings.IndexByte(elem, '/')
|
||||
if idx < 0 {
|
||||
idx = len(elem)
|
||||
}
|
||||
args[0] = elem[:idx]
|
||||
elem = elem[idx:]
|
||||
// Leaf parameter
|
||||
args[0] = elem
|
||||
elem = ""
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch r.Method {
|
||||
case "DELETE":
|
||||
s.handleUsersIDDeleteRequest([1]string{
|
||||
|
@ -200,31 +197,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
return
|
||||
}
|
||||
switch elem[0] {
|
||||
case '/': // Prefix: "/group"
|
||||
origElem := elem
|
||||
if l := len("/group"); len(elem) >= l && elem[0:l] == "/group" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
// Leaf node.
|
||||
switch r.Method {
|
||||
case "PUT":
|
||||
s.handleUsersIDGroupPutRequest([1]string{
|
||||
args[0],
|
||||
}, elemIsEscaped, w, r)
|
||||
default:
|
||||
s.notAllowed(w, r, "PUT")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
elem = origElem
|
||||
}
|
||||
|
||||
elem = origElem
|
||||
}
|
||||
|
@ -472,17 +444,14 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
|
|||
}
|
||||
|
||||
// Param: "id"
|
||||
// Match until "/"
|
||||
idx := strings.IndexByte(elem, '/')
|
||||
if idx < 0 {
|
||||
idx = len(elem)
|
||||
}
|
||||
args[0] = elem[:idx]
|
||||
elem = elem[idx:]
|
||||
// Leaf parameter
|
||||
args[0] = elem
|
||||
elem = ""
|
||||
|
||||
if len(elem) == 0 {
|
||||
switch method {
|
||||
case "DELETE":
|
||||
// Leaf: UsersIDDelete
|
||||
r.name = "UsersIDDelete"
|
||||
r.summary = ""
|
||||
r.operationID = ""
|
||||
|
@ -491,6 +460,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
|
|||
r.count = 1
|
||||
return r, true
|
||||
case "GET":
|
||||
// Leaf: UsersIDGet
|
||||
r.name = "UsersIDGet"
|
||||
r.summary = ""
|
||||
r.operationID = ""
|
||||
|
@ -499,6 +469,7 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
|
|||
r.count = 1
|
||||
return r, true
|
||||
case "PUT":
|
||||
// Leaf: UsersIDPut
|
||||
r.name = "UsersIDPut"
|
||||
r.summary = ""
|
||||
r.operationID = ""
|
||||
|
@ -510,33 +481,6 @@ func (s *Server) FindPath(method string, u *url.URL) (r Route, _ bool) {
|
|||
return
|
||||
}
|
||||
}
|
||||
switch elem[0] {
|
||||
case '/': // Prefix: "/group"
|
||||
origElem := elem
|
||||
if l := len("/group"); len(elem) >= l && elem[0:l] == "/group" {
|
||||
elem = elem[l:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
if len(elem) == 0 {
|
||||
switch method {
|
||||
case "PUT":
|
||||
// Leaf: UsersIDGroupPut
|
||||
r.name = "UsersIDGroupPut"
|
||||
r.summary = ""
|
||||
r.operationID = ""
|
||||
r.pathPattern = "/users/{id}/group"
|
||||
r.args = args
|
||||
r.count = 1
|
||||
return r, true
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
elem = origElem
|
||||
}
|
||||
|
||||
elem = origElem
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ func (*Error) groupsIDGetRes() {}
|
|||
func (*Error) groupsIDPutRes() {}
|
||||
func (*Error) usersIDDeleteRes() {}
|
||||
func (*Error) usersIDGetRes() {}
|
||||
func (*Error) usersIDGroupPutRes() {}
|
||||
func (*Error) usersIDPutRes() {}
|
||||
|
||||
// ErrorStatusCode wraps Error with StatusCode.
|
||||
|
@ -74,7 +73,7 @@ func (s *ErrorStatusCode) SetResponse(val Error) {
|
|||
type Group struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Users []User `json:"users"`
|
||||
UserIds []ID `json:"user_ids"`
|
||||
}
|
||||
|
||||
// GetID returns the value of ID.
|
||||
|
@ -87,9 +86,9 @@ func (s *Group) GetName() string {
|
|||
return s.Name
|
||||
}
|
||||
|
||||
// GetUsers returns the value of Users.
|
||||
func (s *Group) GetUsers() []User {
|
||||
return s.Users
|
||||
// GetUserIds returns the value of UserIds.
|
||||
func (s *Group) GetUserIds() []ID {
|
||||
return s.UserIds
|
||||
}
|
||||
|
||||
// SetID sets the value of ID.
|
||||
|
@ -102,9 +101,9 @@ func (s *Group) SetName(val string) {
|
|||
s.Name = val
|
||||
}
|
||||
|
||||
// SetUsers sets the value of Users.
|
||||
func (s *Group) SetUsers(val []User) {
|
||||
s.Users = val
|
||||
// SetUserIds sets the value of UserIds.
|
||||
func (s *Group) SetUserIds(val []ID) {
|
||||
s.UserIds = val
|
||||
}
|
||||
|
||||
func (*Group) groupsIDGetRes() {}
|
||||
|
@ -145,58 +144,12 @@ func (s *Ok) SetMessage(val string) {
|
|||
func (*Ok) groupsIDDeleteRes() {}
|
||||
func (*Ok) usersIDDeleteRes() {}
|
||||
|
||||
// NewOptID returns new OptID with value set to v.
|
||||
func NewOptID(v ID) OptID {
|
||||
return OptID{
|
||||
Value: v,
|
||||
Set: true,
|
||||
}
|
||||
}
|
||||
|
||||
// OptID is optional ID.
|
||||
type OptID struct {
|
||||
Value ID
|
||||
Set bool
|
||||
}
|
||||
|
||||
// IsSet returns true if OptID was set.
|
||||
func (o OptID) IsSet() bool { return o.Set }
|
||||
|
||||
// Reset unsets value.
|
||||
func (o *OptID) Reset() {
|
||||
var v ID
|
||||
o.Value = v
|
||||
o.Set = false
|
||||
}
|
||||
|
||||
// SetTo sets value to v.
|
||||
func (o *OptID) SetTo(v ID) {
|
||||
o.Set = true
|
||||
o.Value = v
|
||||
}
|
||||
|
||||
// Get returns value and boolean that denotes whether value was set.
|
||||
func (o OptID) Get() (v ID, 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 OptID) Or(d ID) ID {
|
||||
if v, ok := o.Get(); ok {
|
||||
return v
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Ref: #/components/schemas/User
|
||||
type User struct {
|
||||
ID ID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Group Group `json:"group"`
|
||||
GroupID ID `json:"group_id"`
|
||||
}
|
||||
|
||||
// GetID returns the value of ID.
|
||||
|
@ -214,9 +167,9 @@ func (s *User) GetEmail() string {
|
|||
return s.Email
|
||||
}
|
||||
|
||||
// GetGroup returns the value of Group.
|
||||
func (s *User) GetGroup() Group {
|
||||
return s.Group
|
||||
// GetGroupID returns the value of GroupID.
|
||||
func (s *User) GetGroupID() ID {
|
||||
return s.GroupID
|
||||
}
|
||||
|
||||
// SetID sets the value of ID.
|
||||
|
@ -234,19 +187,19 @@ func (s *User) SetEmail(val string) {
|
|||
s.Email = val
|
||||
}
|
||||
|
||||
// SetGroup sets the value of Group.
|
||||
func (s *User) SetGroup(val Group) {
|
||||
s.Group = val
|
||||
// SetGroupID sets the value of GroupID.
|
||||
func (s *User) SetGroupID(val ID) {
|
||||
s.GroupID = val
|
||||
}
|
||||
|
||||
func (*User) usersIDGetRes() {}
|
||||
func (*User) usersIDGroupPutRes() {}
|
||||
func (*User) usersIDPutRes() {}
|
||||
|
||||
// Ref: #/components/schemas/UserUpdate
|
||||
type UserUpdate struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
GroupID ID `json:"group_id"`
|
||||
}
|
||||
|
||||
// GetName returns the value of Name.
|
||||
|
@ -259,6 +212,11 @@ func (s *UserUpdate) GetEmail() string {
|
|||
return s.Email
|
||||
}
|
||||
|
||||
// GetGroupID returns the value of GroupID.
|
||||
func (s *UserUpdate) GetGroupID() ID {
|
||||
return s.GroupID
|
||||
}
|
||||
|
||||
// SetName sets the value of Name.
|
||||
func (s *UserUpdate) SetName(val string) {
|
||||
s.Name = val
|
||||
|
@ -269,16 +227,7 @@ func (s *UserUpdate) SetEmail(val string) {
|
|||
s.Email = val
|
||||
}
|
||||
|
||||
type UsersIDGroupPutReq struct {
|
||||
Group OptID `json:"group"`
|
||||
}
|
||||
|
||||
// GetGroup returns the value of Group.
|
||||
func (s *UsersIDGroupPutReq) GetGroup() OptID {
|
||||
return s.Group
|
||||
}
|
||||
|
||||
// SetGroup sets the value of Group.
|
||||
func (s *UsersIDGroupPutReq) SetGroup(val OptID) {
|
||||
s.Group = val
|
||||
// SetGroupID sets the value of GroupID.
|
||||
func (s *UserUpdate) SetGroupID(val ID) {
|
||||
s.GroupID = val
|
||||
}
|
||||
|
|
|
@ -60,12 +60,6 @@ type Handler interface {
|
|||
//
|
||||
// GET /users/{id}
|
||||
UsersIDGet(ctx context.Context, params UsersIDGetParams) (UsersIDGetRes, error)
|
||||
// UsersIDGroupPut implements PUT /users/{id}/group operation.
|
||||
//
|
||||
// Update user group by id.
|
||||
//
|
||||
// PUT /users/{id}/group
|
||||
UsersIDGroupPut(ctx context.Context, req *UsersIDGroupPutReq, params UsersIDGroupPutParams) (UsersIDGroupPutRes, error)
|
||||
// UsersIDPut implements PUT /users/{id} operation.
|
||||
//
|
||||
// Update user by id.
|
||||
|
|
|
@ -92,15 +92,6 @@ func (UnimplementedHandler) UsersIDGet(ctx context.Context, params UsersIDGetPar
|
|||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// UsersIDGroupPut implements PUT /users/{id}/group operation.
|
||||
//
|
||||
// Update user group by id.
|
||||
//
|
||||
// PUT /users/{id}/group
|
||||
func (UnimplementedHandler) UsersIDGroupPut(ctx context.Context, req *UsersIDGroupPutReq, params UsersIDGroupPutParams) (r UsersIDGroupPutRes, _ error) {
|
||||
return r, ht.ErrNotImplemented
|
||||
}
|
||||
|
||||
// UsersIDPut implements PUT /users/{id} operation.
|
||||
//
|
||||
// Update user by id.
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
package openapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"github.com/ogen-go/ogen/validate"
|
||||
|
@ -17,53 +15,13 @@ func (s *Group) Validate() error {
|
|||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Users == nil {
|
||||
if s.UserIds == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s.Users {
|
||||
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: "users",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *User) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := s.Group.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "group",
|
||||
Name: "user_ids",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
|
|
1
tmp/build-errors.log
Normal file
1
tmp/build-errors.log
Normal file
|
@ -0,0 +1 @@
|
|||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
Reference in a new issue