diff --git a/assets/openapi.yaml b/assets/openapi.yaml index 8d92894..58fbe8b 100644 --- a/assets/openapi.yaml +++ b/assets/openapi.yaml @@ -76,6 +76,9 @@ paths: minimum: 1 maximum: 5 default: 3 + x-oapi-codegen-extra-tags: + validate: gt=1,lte=5 + default: "3" - name: minDepth in: query description: "Minimum depth of the JSON object" @@ -84,6 +87,9 @@ paths: minimum: 1 maximum: 3 default: 1 + x-oapi-codegen-extra-tags: + validate: gt=0,lte=3 + default: "1" - name: maxElems in: query description: "Maximum number of elements per JSON object" @@ -92,6 +98,9 @@ paths: minimum: 1 maximum: 10 default: 3 + x-oapi-codegen-extra-tags: + validate: gt=0,lte=10 + default: "3" responses: '200': description: JSON data. @@ -118,6 +127,9 @@ paths: minimum: 1 maximum: 10000 default: 10 + x-oapi-codegen-extra-tags: + validate: gt=1,lte=10000 + default: "10" responses: '200': description: Log data. @@ -230,6 +242,12 @@ paths: type: array items: $ref: '#/components/schemas/User' + '400': + description: Bad request - validation failed. + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' # --- Streaming Endpoints --- /stream/json/user: @@ -279,7 +297,11 @@ paths: summary: "Returns the request data as JSON" parameters: - $ref: '#/components/parameters/DelayParam' - - $ref: '#/components/parameters/FormatParam' + requestBody: + content: + '*/*': + schema: + type: string responses: '200': description: "Request data" @@ -293,7 +315,11 @@ paths: summary: "Returns the request data as JSON" parameters: - $ref: '#/components/parameters/DelayParam' - - $ref: '#/components/parameters/FormatParam' + requestBody: + content: + '*/*': + schema: + type: string responses: '200': description: "Request data" @@ -307,7 +333,11 @@ paths: summary: "Returns the request data as JSON" parameters: - $ref: '#/components/parameters/DelayParam' - - $ref: '#/components/parameters/FormatParam' + requestBody: + content: + '*/*': + schema: + type: string responses: '200': description: "Request data" @@ -321,7 +351,11 @@ paths: summary: "Returns the request data as JSON" parameters: - $ref: '#/components/parameters/DelayParam' - - $ref: '#/components/parameters/FormatParam' + requestBody: + content: + '*/*': + schema: + type: string responses: '200': description: "Request data" @@ -335,7 +369,11 @@ paths: summary: "Returns the request data as JSON" parameters: - $ref: '#/components/parameters/DelayParam' - - $ref: '#/components/parameters/FormatParam' + requestBody: + content: + '*/*': + schema: + type: string responses: '200': description: "Request data" @@ -345,9 +383,9 @@ paths: type: object # --- Status --- - /status/{status}: + /status/{code}: parameters: - - name: status + - name: code in: path description: "HTTP status code that should be returned." required: true @@ -497,11 +535,35 @@ components: x-oapi-codegen-extra-tags: fake: "{randomstring:[male,female,other]}" contact: - type: object $ref: '#/components/schemas/Contact' address: - type: object $ref: '#/components/schemas/Address' + ValidationError: + type: object + properties: + status_code: + type: integer + message: + type: string + errors: + $ref: '#/components/schemas/ValidationMessages' + required: + - status_code + - message + ValidationMessages: + type: array + items: + $ref: '#/components/schemas/ValidationMessage' + ValidationMessage: + type: object + properties: + field: + type: string + message: + type: string + required: + - field + - message Log: type: object properties: @@ -529,6 +591,9 @@ components: type: string enum: [ json, text ] default: json + x-oapi-codegen-extra-tags: + validate: oneOf=json,text + default: "json" DelayParam: name: delay in: query @@ -539,6 +604,18 @@ components: minimum: 0 maximum: 10000 default: 0 + StatusParam: + name: status + in: query + description: "HTTP status code that should be returned." + schema: + type: integer + minimum: 100 + maximum: 599 + default: 200 + x-oapi-codegen-extra-tags: + validate: gt=100,lte=599 + default: "200" LogLevels: name: logLevels in: query @@ -548,24 +625,31 @@ components: items: type: string default: [ debug, info, warn, error ] + x-oapi-codegen-extra-tags: + default: '[\" debug\",\" info\",\" warn\",\" error\" ]' LogLevelWeights: - name: logLevelWeights - in: query - description: "Log level weights (default: `1,5,3,2`)" - schema: - type: array - items: - type: number - default: [ 1, 5, 3, 2 ] + name: logLevelWeights + in: query + description: "Log level weights (default: `1,5,3,2`)" + schema: + type: array + items: + type: number + default: [ 1, 5, 3, 2 ] + x-oapi-codegen-extra-tags: + default: "[1, 5, 3, 2]" StreamInterval: - name: interval - in: query - description: "Interval in milliseconds between streamed responses (min: 0; max: 5000)" - schema: - type: integer - minimum: 0 - maximum: 5000 - default: 250 + name: interval + in: query + description: "Interval in milliseconds between streamed responses (min: 0; max: 5000)" + schema: + type: integer + minimum: 0 + maximum: 5000 + default: 250 + x-oapi-codegen-extra-tags: + validate: gt=0,lte=5000 + default: "250" Count: name: count in: query @@ -575,7 +659,9 @@ components: minimum: 1 maximum: 10000 default: 10 - + x-oapi-codegen-extra-tags: + validate: gt=1,lte=10000 + default: "10" tags: - name: Meta description: "Endpoints providing meta functionality." diff --git a/cmd/httb/main.go b/cmd/httb/main.go index 5a6373e..6d616cb 100644 --- a/cmd/httb/main.go +++ b/cmd/httb/main.go @@ -34,7 +34,12 @@ func init() { } func main() { - if err := service.NewService(cfg).Start(); err != nil { + svc, err := service.NewService(cfg) + if err != nil { + slog.Error("failed to init service", "error", err) + } + + if err := svc.Start(); err != nil { slog.Error("failed to start service", "error", err) } } diff --git a/go.mod b/go.mod index e917a69..460f166 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,14 @@ require ( github.com/bombsimon/wsl/v4 v4.4.1 github.com/brianvoe/gofakeit/v7 v7.2.1 github.com/caarlos0/env/v11 v11.3.1 + github.com/creasty/defaults v1.8.0 + github.com/go-playground/locales v0.14.1 + github.com/go-playground/universal-translator v0.18.1 + github.com/go-playground/validator/v10 v10.14.1 github.com/golangci/golangci-lint v1.62.2 github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 github.com/oapi-codegen/runtime v1.1.1 + github.com/samber/slog-http v1.5.1 ) require ( @@ -56,6 +61,7 @@ require ( github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/getkin/kin-openapi v0.127.0 // indirect github.com/ghostiam/protogetter v0.3.8 // indirect github.com/go-critic/go-critic v0.11.5 // indirect @@ -107,6 +113,7 @@ require ( github.com/lasiar/canonicalheader v1.1.2 // indirect github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect + github.com/leodido/go-urn v1.2.4 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // indirect @@ -147,7 +154,6 @@ require ( github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect - github.com/samber/slog-http v1.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect @@ -196,6 +202,7 @@ require ( go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.22.0 // indirect diff --git a/go.sum b/go.sum index ffdb920..ea53d26 100644 --- a/go.sum +++ b/go.sum @@ -128,6 +128,8 @@ github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTi github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk= +github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= @@ -160,6 +162,8 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY= github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= @@ -181,6 +185,14 @@ github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1 github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= +github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -378,6 +390,8 @@ github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJ github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= @@ -585,6 +599,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -668,6 +683,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/internal/pkg/api/api.go b/internal/pkg/api/api.go index ddb4f0c..c46d8fa 100644 --- a/internal/pkg/api/api.go +++ b/internal/pkg/api/api.go @@ -33,36 +33,6 @@ const ( GetPingParamsFormatText GetPingParamsFormat = "text" ) -// Defines values for DeleteReturnParamsFormat. -const ( - DeleteReturnParamsFormatJson DeleteReturnParamsFormat = "json" - DeleteReturnParamsFormatText DeleteReturnParamsFormat = "text" -) - -// Defines values for GetReturnParamsFormat. -const ( - GetReturnParamsFormatJson GetReturnParamsFormat = "json" - GetReturnParamsFormatText GetReturnParamsFormat = "text" -) - -// Defines values for PatchReturnParamsFormat. -const ( - PatchReturnParamsFormatJson PatchReturnParamsFormat = "json" - PatchReturnParamsFormatText PatchReturnParamsFormat = "text" -) - -// Defines values for PostReturnParamsFormat. -const ( - PostReturnParamsFormatJson PostReturnParamsFormat = "json" - PostReturnParamsFormatText PostReturnParamsFormat = "text" -) - -// Defines values for PutReturnParamsFormat. -const ( - Json PutReturnParamsFormat = "json" - Text PutReturnParamsFormat = "text" -) - // Address defines model for Address. type Address struct { City *string `fake:"{city}" json:"city,omitempty"` @@ -95,7 +65,7 @@ type StatusCodeMessage struct { // User defines model for User. type User struct { Address *Address `json:"address,omitempty"` - Age *int `json:"age,omitempty"` + Age *int `fake:"{number:0,100}" json:"age,omitempty"` Contact *Contact `json:"contact,omitempty"` FirstName *string `fake:"{firstname}" json:"firstName,omitempty"` Gender *UserGender `fake:"{randomstring:[male,female,other]}" json:"gender,omitempty"` @@ -105,6 +75,22 @@ type User struct { // UserGender defines model for User.Gender. type UserGender string +// ValidationError defines model for ValidationError. +type ValidationError struct { + Errors *ValidationMessages `json:"errors,omitempty"` + Message string `json:"message"` + StatusCode int `json:"status_code"` +} + +// ValidationMessage defines model for ValidationMessage. +type ValidationMessage struct { + Field string `json:"field"` + Message string `json:"message"` +} + +// ValidationMessages defines model for ValidationMessages. +type ValidationMessages = []ValidationMessage + // Count defines model for Count. type Count = int @@ -129,13 +115,13 @@ type GetJsonRandomParams struct { Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` // MaxDepth Maximum depth of the JSON object - MaxDepth *int `form:"maxDepth,omitempty" json:"maxDepth,omitempty"` + MaxDepth *int `default:"3" form:"maxDepth,omitempty" json:"maxDepth,omitempty" validate:"gt=1,lte=5"` // MinDepth Minimum depth of the JSON object - MinDepth *int `form:"minDepth,omitempty" json:"minDepth,omitempty"` + MinDepth *int `default:"1" form:"minDepth,omitempty" json:"minDepth,omitempty" validate:"gt=0,lte=3"` // MaxElems Maximum number of elements per JSON object - MaxElems *int `form:"maxElems,omitempty" json:"maxElems,omitempty"` + MaxElems *int `default:"3" form:"maxElems,omitempty" json:"maxElems,omitempty" validate:"gt=0,lte=10"` } // GetJsonRandomAddressParams defines parameters for GetJsonRandomAddress. @@ -150,7 +136,7 @@ type GetJsonRandomAddressesParams struct { Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` // Count Number of entries to return (min: 1; max: 10000) - Count *Count `form:"count,omitempty" json:"count,omitempty"` + Count *Count `default:"10" form:"count,omitempty" json:"count,omitempty" validate:"gt=1,lte=10000"` } // GetJsonRandomContactParams defines parameters for GetJsonRandomContact. @@ -165,7 +151,7 @@ type GetJsonRandomContactsParams struct { Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` // Count Number of entries to return (min: 1; max: 10000) - Count *Count `form:"count,omitempty" json:"count,omitempty"` + Count *Count `default:"10" form:"count,omitempty" json:"count,omitempty" validate:"gt=1,lte=10000"` } // GetJsonRandomLogParams defines parameters for GetJsonRandomLog. @@ -174,13 +160,13 @@ type GetJsonRandomLogParams struct { Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` // LogLevels Log levels to use (default: `debug,info,warn,error`) - LogLevels *LogLevels `form:"logLevels,omitempty" json:"logLevels,omitempty"` + LogLevels *LogLevels `default:"[\" debug\",\" info\",\" warn\",\" error\" ]" form:"logLevels,omitempty" json:"logLevels,omitempty"` // LogLevelWeights Log level weights (default: `1,5,3,2`) - LogLevelWeights *LogLevelWeights `form:"logLevelWeights,omitempty" json:"logLevelWeights,omitempty"` + LogLevelWeights *LogLevelWeights `default:"[1, 5, 3, 2]" form:"logLevelWeights,omitempty" json:"logLevelWeights,omitempty"` // Count Number of log entries to return (min: 1; max: 10000) - Count *int `form:"count,omitempty" json:"count,omitempty"` + Count *int `default:"10" form:"count,omitempty" json:"count,omitempty" validate:"gt=1,lte=10000"` } // GetJsonRandomUserParams defines parameters for GetJsonRandomUser. @@ -195,13 +181,13 @@ type GetJsonRandomUsersParams struct { Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` // Count Number of entries to return (min: 1; max: 10000) - Count *Count `form:"count,omitempty" json:"count,omitempty"` + Count *Count `default:"10" form:"count,omitempty" json:"count,omitempty" validate:"gt=1,lte=10000"` } // GetPingParams defines parameters for GetPing. type GetPingParams struct { // Format Response format (default: `json`) - Format *GetPingParamsFormat `form:"format,omitempty" json:"format,omitempty"` + Format *GetPingParamsFormat `default:"json" form:"format,omitempty" json:"format,omitempty" validate:"oneOf=json,text"` // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` @@ -214,88 +200,58 @@ type GetPingParamsFormat string type DeleteReturnParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` - - // Format Response format (default: `json`) - Format *DeleteReturnParamsFormat `form:"format,omitempty" json:"format,omitempty"` } -// DeleteReturnParamsFormat defines parameters for DeleteReturn. -type DeleteReturnParamsFormat string - // GetReturnParams defines parameters for GetReturn. type GetReturnParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` - - // Format Response format (default: `json`) - Format *GetReturnParamsFormat `form:"format,omitempty" json:"format,omitempty"` } -// GetReturnParamsFormat defines parameters for GetReturn. -type GetReturnParamsFormat string - // PatchReturnParams defines parameters for PatchReturn. type PatchReturnParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` - - // Format Response format (default: `json`) - Format *PatchReturnParamsFormat `form:"format,omitempty" json:"format,omitempty"` } -// PatchReturnParamsFormat defines parameters for PatchReturn. -type PatchReturnParamsFormat string - // PostReturnParams defines parameters for PostReturn. type PostReturnParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` - - // Format Response format (default: `json`) - Format *PostReturnParamsFormat `form:"format,omitempty" json:"format,omitempty"` } -// PostReturnParamsFormat defines parameters for PostReturn. -type PostReturnParamsFormat string - // PutReturnParams defines parameters for PutReturn. type PutReturnParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` - - // Format Response format (default: `json`) - Format *PutReturnParamsFormat `form:"format,omitempty" json:"format,omitempty"` } -// PutReturnParamsFormat defines parameters for PutReturn. -type PutReturnParamsFormat string - -// DeleteStatusStatusParams defines parameters for DeleteStatusStatus. -type DeleteStatusStatusParams struct { +// DeleteStatusCodeParams defines parameters for DeleteStatusCode. +type DeleteStatusCodeParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` } -// GetStatusStatusParams defines parameters for GetStatusStatus. -type GetStatusStatusParams struct { +// GetStatusCodeParams defines parameters for GetStatusCode. +type GetStatusCodeParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` } -// PatchStatusStatusParams defines parameters for PatchStatusStatus. -type PatchStatusStatusParams struct { +// PatchStatusCodeParams defines parameters for PatchStatusCode. +type PatchStatusCodeParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` } -// PostStatusStatusParams defines parameters for PostStatusStatus. -type PostStatusStatusParams struct { +// PostStatusCodeParams defines parameters for PostStatusCode. +type PostStatusCodeParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` } -// PutStatusStatusParams defines parameters for PutStatusStatus. -type PutStatusStatusParams struct { +// PutStatusCodeParams defines parameters for PutStatusCode. +type PutStatusCodeParams struct { // Delay Delay in milliseconds before the response is sent (min: 0; max: 10000) Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` } @@ -306,13 +262,13 @@ type GetStreamJsonLogsParams struct { Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` // LogLevels Log levels to use (default: `debug,info,warn,error`) - LogLevels *LogLevels `form:"logLevels,omitempty" json:"logLevels,omitempty"` + LogLevels *LogLevels `default:"[\" debug\",\" info\",\" warn\",\" error\" ]" form:"logLevels,omitempty" json:"logLevels,omitempty"` // LogLevelWeights Log level weights (default: `1,5,3,2`) - LogLevelWeights *LogLevelWeights `form:"logLevelWeights,omitempty" json:"logLevelWeights,omitempty"` + LogLevelWeights *LogLevelWeights `default:"[1, 5, 3, 2]" form:"logLevelWeights,omitempty" json:"logLevelWeights,omitempty"` // Interval Interval in milliseconds between streamed responses (min: 0; max: 5000) - Interval *StreamInterval `form:"interval,omitempty" json:"interval,omitempty"` + Interval *StreamInterval `default:"250" form:"interval,omitempty" json:"interval,omitempty" validate:"gt=0,lte=5000"` } // GetStreamJsonUserParams defines parameters for GetStreamJsonUser. @@ -321,7 +277,7 @@ type GetStreamJsonUserParams struct { Delay *DelayParam `form:"delay,omitempty" json:"delay,omitempty"` // Interval Interval in milliseconds between streamed responses (min: 0; max: 5000) - Interval *StreamInterval `form:"interval,omitempty" json:"interval,omitempty"` + Interval *StreamInterval `default:"250" form:"interval,omitempty" json:"interval,omitempty" validate:"gt=0,lte=5000"` } // ServerInterface represents all server handlers. @@ -369,20 +325,20 @@ type ServerInterface interface { // (PUT /return) PutReturn(w http.ResponseWriter, r *http.Request, params PutReturnParams) // Returns the specified HTTP status code and status message - // (DELETE /status/{status}) - DeleteStatusStatus(w http.ResponseWriter, r *http.Request, status int, params DeleteStatusStatusParams) + // (DELETE /status/{code}) + DeleteStatusCode(w http.ResponseWriter, r *http.Request, code int, params DeleteStatusCodeParams) // Returns the specified HTTP status code and status message - // (GET /status/{status}) - GetStatusStatus(w http.ResponseWriter, r *http.Request, status int, params GetStatusStatusParams) + // (GET /status/{code}) + GetStatusCode(w http.ResponseWriter, r *http.Request, code int, params GetStatusCodeParams) // Returns the specified HTTP status code and status message - // (PATCH /status/{status}) - PatchStatusStatus(w http.ResponseWriter, r *http.Request, status int, params PatchStatusStatusParams) + // (PATCH /status/{code}) + PatchStatusCode(w http.ResponseWriter, r *http.Request, code int, params PatchStatusCodeParams) // Returns the specified HTTP status code and status message - // (POST /status/{status}) - PostStatusStatus(w http.ResponseWriter, r *http.Request, status int, params PostStatusStatusParams) + // (POST /status/{code}) + PostStatusCode(w http.ResponseWriter, r *http.Request, code int, params PostStatusCodeParams) // Returns the specified HTTP status code and status message - // (PUT /status/{status}) - PutStatusStatus(w http.ResponseWriter, r *http.Request, status int, params PutStatusStatusParams) + // (PUT /status/{code}) + PutStatusCode(w http.ResponseWriter, r *http.Request, code int, params PutStatusCodeParams) // Streams JSON logs. // (GET /stream/json/logs) GetStreamJsonLogs(w http.ResponseWriter, r *http.Request, params GetStreamJsonLogsParams) @@ -739,14 +695,6 @@ func (siw *ServerInterfaceWrapper) DeleteReturn(w http.ResponseWriter, r *http.R return } - // ------------- Optional query parameter "format" ------------- - - err = runtime.BindQueryParameter("form", true, false, "format", r.URL.Query(), ¶ms.Format) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "format", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeleteReturn(w, r, params) })) @@ -774,14 +722,6 @@ func (siw *ServerInterfaceWrapper) GetReturn(w http.ResponseWriter, r *http.Requ return } - // ------------- Optional query parameter "format" ------------- - - err = runtime.BindQueryParameter("form", true, false, "format", r.URL.Query(), ¶ms.Format) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "format", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetReturn(w, r, params) })) @@ -809,14 +749,6 @@ func (siw *ServerInterfaceWrapper) PatchReturn(w http.ResponseWriter, r *http.Re return } - // ------------- Optional query parameter "format" ------------- - - err = runtime.BindQueryParameter("form", true, false, "format", r.URL.Query(), ¶ms.Format) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "format", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.PatchReturn(w, r, params) })) @@ -844,14 +776,6 @@ func (siw *ServerInterfaceWrapper) PostReturn(w http.ResponseWriter, r *http.Req return } - // ------------- Optional query parameter "format" ------------- - - err = runtime.BindQueryParameter("form", true, false, "format", r.URL.Query(), ¶ms.Format) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "format", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.PostReturn(w, r, params) })) @@ -879,14 +803,6 @@ func (siw *ServerInterfaceWrapper) PutReturn(w http.ResponseWriter, r *http.Requ return } - // ------------- Optional query parameter "format" ------------- - - err = runtime.BindQueryParameter("form", true, false, "format", r.URL.Query(), ¶ms.Format) - if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "format", Err: err}) - return - } - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.PutReturn(w, r, params) })) @@ -898,22 +814,22 @@ func (siw *ServerInterfaceWrapper) PutReturn(w http.ResponseWriter, r *http.Requ handler.ServeHTTP(w, r) } -// DeleteStatusStatus operation middleware -func (siw *ServerInterfaceWrapper) DeleteStatusStatus(w http.ResponseWriter, r *http.Request) { +// DeleteStatusCode operation middleware +func (siw *ServerInterfaceWrapper) DeleteStatusCode(w http.ResponseWriter, r *http.Request) { var err error - // ------------- Path parameter "status" ------------- - var status int + // ------------- Path parameter "code" ------------- + var code int - err = runtime.BindStyledParameterWithOptions("simple", "status", r.PathValue("status"), &status, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "code", r.PathValue("code"), &code, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "code", Err: err}) return } // Parameter object where we will unmarshal all parameters from the context - var params DeleteStatusStatusParams + var params DeleteStatusCodeParams // ------------- Optional query parameter "delay" ------------- @@ -924,7 +840,7 @@ func (siw *ServerInterfaceWrapper) DeleteStatusStatus(w http.ResponseWriter, r * } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteStatusStatus(w, r, status, params) + siw.Handler.DeleteStatusCode(w, r, code, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -934,22 +850,22 @@ func (siw *ServerInterfaceWrapper) DeleteStatusStatus(w http.ResponseWriter, r * handler.ServeHTTP(w, r) } -// GetStatusStatus operation middleware -func (siw *ServerInterfaceWrapper) GetStatusStatus(w http.ResponseWriter, r *http.Request) { +// GetStatusCode operation middleware +func (siw *ServerInterfaceWrapper) GetStatusCode(w http.ResponseWriter, r *http.Request) { var err error - // ------------- Path parameter "status" ------------- - var status int + // ------------- Path parameter "code" ------------- + var code int - err = runtime.BindStyledParameterWithOptions("simple", "status", r.PathValue("status"), &status, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "code", r.PathValue("code"), &code, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "code", Err: err}) return } // Parameter object where we will unmarshal all parameters from the context - var params GetStatusStatusParams + var params GetStatusCodeParams // ------------- Optional query parameter "delay" ------------- @@ -960,7 +876,7 @@ func (siw *ServerInterfaceWrapper) GetStatusStatus(w http.ResponseWriter, r *htt } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.GetStatusStatus(w, r, status, params) + siw.Handler.GetStatusCode(w, r, code, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -970,22 +886,22 @@ func (siw *ServerInterfaceWrapper) GetStatusStatus(w http.ResponseWriter, r *htt handler.ServeHTTP(w, r) } -// PatchStatusStatus operation middleware -func (siw *ServerInterfaceWrapper) PatchStatusStatus(w http.ResponseWriter, r *http.Request) { +// PatchStatusCode operation middleware +func (siw *ServerInterfaceWrapper) PatchStatusCode(w http.ResponseWriter, r *http.Request) { var err error - // ------------- Path parameter "status" ------------- - var status int + // ------------- Path parameter "code" ------------- + var code int - err = runtime.BindStyledParameterWithOptions("simple", "status", r.PathValue("status"), &status, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "code", r.PathValue("code"), &code, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "code", Err: err}) return } // Parameter object where we will unmarshal all parameters from the context - var params PatchStatusStatusParams + var params PatchStatusCodeParams // ------------- Optional query parameter "delay" ------------- @@ -996,7 +912,7 @@ func (siw *ServerInterfaceWrapper) PatchStatusStatus(w http.ResponseWriter, r *h } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.PatchStatusStatus(w, r, status, params) + siw.Handler.PatchStatusCode(w, r, code, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1006,22 +922,22 @@ func (siw *ServerInterfaceWrapper) PatchStatusStatus(w http.ResponseWriter, r *h handler.ServeHTTP(w, r) } -// PostStatusStatus operation middleware -func (siw *ServerInterfaceWrapper) PostStatusStatus(w http.ResponseWriter, r *http.Request) { +// PostStatusCode operation middleware +func (siw *ServerInterfaceWrapper) PostStatusCode(w http.ResponseWriter, r *http.Request) { var err error - // ------------- Path parameter "status" ------------- - var status int + // ------------- Path parameter "code" ------------- + var code int - err = runtime.BindStyledParameterWithOptions("simple", "status", r.PathValue("status"), &status, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "code", r.PathValue("code"), &code, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "code", Err: err}) return } // Parameter object where we will unmarshal all parameters from the context - var params PostStatusStatusParams + var params PostStatusCodeParams // ------------- Optional query parameter "delay" ------------- @@ -1032,7 +948,7 @@ func (siw *ServerInterfaceWrapper) PostStatusStatus(w http.ResponseWriter, r *ht } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.PostStatusStatus(w, r, status, params) + siw.Handler.PostStatusCode(w, r, code, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1042,22 +958,22 @@ func (siw *ServerInterfaceWrapper) PostStatusStatus(w http.ResponseWriter, r *ht handler.ServeHTTP(w, r) } -// PutStatusStatus operation middleware -func (siw *ServerInterfaceWrapper) PutStatusStatus(w http.ResponseWriter, r *http.Request) { +// PutStatusCode operation middleware +func (siw *ServerInterfaceWrapper) PutStatusCode(w http.ResponseWriter, r *http.Request) { var err error - // ------------- Path parameter "status" ------------- - var status int + // ------------- Path parameter "code" ------------- + var code int - err = runtime.BindStyledParameterWithOptions("simple", "status", r.PathValue("status"), &status, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + err = runtime.BindStyledParameterWithOptions("simple", "code", r.PathValue("code"), &code, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) if err != nil { - siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "code", Err: err}) return } // Parameter object where we will unmarshal all parameters from the context - var params PutStatusStatusParams + var params PutStatusCodeParams // ------------- Optional query parameter "delay" ------------- @@ -1068,7 +984,7 @@ func (siw *ServerInterfaceWrapper) PutStatusStatus(w http.ResponseWriter, r *htt } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.PutStatusStatus(w, r, status, params) + siw.Handler.PutStatusCode(w, r, code, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -1298,11 +1214,11 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc("PATCH "+options.BaseURL+"/return", wrapper.PatchReturn) m.HandleFunc("POST "+options.BaseURL+"/return", wrapper.PostReturn) m.HandleFunc("PUT "+options.BaseURL+"/return", wrapper.PutReturn) - m.HandleFunc("DELETE "+options.BaseURL+"/status/{status}", wrapper.DeleteStatusStatus) - m.HandleFunc("GET "+options.BaseURL+"/status/{status}", wrapper.GetStatusStatus) - m.HandleFunc("PATCH "+options.BaseURL+"/status/{status}", wrapper.PatchStatusStatus) - m.HandleFunc("POST "+options.BaseURL+"/status/{status}", wrapper.PostStatusStatus) - m.HandleFunc("PUT "+options.BaseURL+"/status/{status}", wrapper.PutStatusStatus) + m.HandleFunc("DELETE "+options.BaseURL+"/status/{code}", wrapper.DeleteStatusCode) + m.HandleFunc("GET "+options.BaseURL+"/status/{code}", wrapper.GetStatusCode) + m.HandleFunc("PATCH "+options.BaseURL+"/status/{code}", wrapper.PatchStatusCode) + m.HandleFunc("POST "+options.BaseURL+"/status/{code}", wrapper.PostStatusCode) + m.HandleFunc("PUT "+options.BaseURL+"/status/{code}", wrapper.PutStatusCode) m.HandleFunc("GET "+options.BaseURL+"/stream/json/logs", wrapper.GetStreamJsonLogs) m.HandleFunc("GET "+options.BaseURL+"/stream/json/user", wrapper.GetStreamJsonUser) diff --git a/internal/pkg/service/api_json.go b/internal/pkg/service/api_json.go index 51de755..def77ec 100644 --- a/internal/pkg/service/api_json.go +++ b/internal/pkg/service/api_json.go @@ -8,23 +8,15 @@ import ( ) func (s Service) GetJsonRandomLog(w http.ResponseWriter, r *http.Request, params api.GetJsonRandomLogParams) { - if params.Count == nil { - params.Count = new(int) - *params.Count = 1 - } - - if params.LogLevels == nil { - params.LogLevels = new([]string) - *params.LogLevels = []string{"debug", "info", "warn", "error"} - } - - if params.LogLevelWeights == nil { - params.LogLevelWeights = new([]float32) - *params.LogLevelWeights = []float32{1, 5, 2, 1} + if ok := s.Validate(w, ¶ms); !ok { + return } if len(*params.LogLevels) != len(*params.LogLevelWeights) { - sendError(w, http.StatusBadRequest, "log levels and weights must have the same length") + sendJSON(w, http.StatusBadRequest, api.ValidationError{ + Message: "log levels and weights must have the same length", + StatusCode: http.StatusBadRequest, + }) } logLevels := make(map[string]float32) @@ -36,14 +28,14 @@ func (s Service) GetJsonRandomLog(w http.ResponseWriter, r *http.Request, params _, _ = w.Write([]byte(random.NewLog(*params.Count, logLevels).String())) } -func (s Service) GetJsonRandomAddress(w http.ResponseWriter, r *http.Request, params api.GetJsonRandomAddressParams) { +func (s Service) GetJsonRandomAddress(w http.ResponseWriter, r *http.Request, _ api.GetJsonRandomAddressParams) { sendJSON(w, http.StatusOK, random.Address()) } func (s Service) GetJsonRandomAddresses(w http.ResponseWriter, r *http.Request, params api.GetJsonRandomAddressesParams) { - if params.Count == nil { - params.Count = new(int) - *params.Count = 10 + if ok := s.Validate(w, ¶ms); !ok { + + return } addresses := make([]api.Address, *params.Count) @@ -54,14 +46,13 @@ func (s Service) GetJsonRandomAddresses(w http.ResponseWriter, r *http.Request, sendJSON(w, http.StatusOK, addresses) } -func (s Service) GetJsonRandomContact(w http.ResponseWriter, r *http.Request, params api.GetJsonRandomContactParams) { +func (s Service) GetJsonRandomContact(w http.ResponseWriter, r *http.Request, _ api.GetJsonRandomContactParams) { sendJSON(w, http.StatusOK, random.Contact()) } func (s Service) GetJsonRandomContacts(w http.ResponseWriter, r *http.Request, params api.GetJsonRandomContactsParams) { - if params.Count == nil { - params.Count = new(int) - *params.Count = 10 + if ok := s.Validate(w, ¶ms); !ok { + return } contacts := make([]api.Contact, *params.Count) @@ -73,32 +64,20 @@ func (s Service) GetJsonRandomContacts(w http.ResponseWriter, r *http.Request, p } func (s Service) GetJsonRandom(w http.ResponseWriter, r *http.Request, params api.GetJsonRandomParams) { - if params.MinDepth == nil { - params.MinDepth = new(int) - *params.MinDepth = 3 - } - - if params.MaxDepth == nil { - params.MaxDepth = new(int) - *params.MaxDepth = 5 - } - - if params.MaxElems == nil { - params.MaxElems = new(int) - *params.MaxElems = 3 + if ok := s.Validate(w, ¶ms); !ok { + return } sendJSON(w, http.StatusOK, RandomJSON(*params.MinDepth, *params.MaxDepth, *params.MaxElems)) } -func (s Service) GetJsonRandomUser(w http.ResponseWriter, r *http.Request, params api.GetJsonRandomUserParams) { +func (s Service) GetJsonRandomUser(w http.ResponseWriter, r *http.Request, _ api.GetJsonRandomUserParams) { sendJSON(w, http.StatusOK, random.User()) } func (s Service) GetJsonRandomUsers(w http.ResponseWriter, r *http.Request, params api.GetJsonRandomUsersParams) { - if params.Count == nil { - params.Count = new(int) - *params.Count = 10 + if ok := s.Validate(w, ¶ms); !ok { + return } users := make([]api.User, *params.Count) diff --git a/internal/pkg/service/api_other.go b/internal/pkg/service/api_other.go index 6314e7c..7df498d 100644 --- a/internal/pkg/service/api_other.go +++ b/internal/pkg/service/api_other.go @@ -5,6 +5,6 @@ import ( "net/http" ) -func (s Service) GetPing(w http.ResponseWriter, r *http.Request, _ api.GetPingParams) { +func (s Service) GetPing(w http.ResponseWriter, r *http.Request, params api.GetPingParams) { sendFormattedResponse(w, r, "pong", "message") } diff --git a/internal/pkg/service/api_return_request_data.go b/internal/pkg/service/api_return_request_data.go index 4f78cd1..e52058c 100644 --- a/internal/pkg/service/api_return_request_data.go +++ b/internal/pkg/service/api_return_request_data.go @@ -5,23 +5,23 @@ import ( "net/http" ) -func (s Service) DeleteReturn(w http.ResponseWriter, r *http.Request, params api.DeleteReturnParams) { +func (s Service) DeleteReturn(w http.ResponseWriter, r *http.Request, _ api.DeleteReturnParams) { sendRequestData(w, r) } -func (s Service) GetReturn(w http.ResponseWriter, r *http.Request, params api.GetReturnParams) { +func (s Service) GetReturn(w http.ResponseWriter, r *http.Request, _ api.GetReturnParams) { sendRequestData(w, r) } -func (s Service) PatchReturn(w http.ResponseWriter, r *http.Request, params api.PatchReturnParams) { +func (s Service) PatchReturn(w http.ResponseWriter, r *http.Request, _ api.PatchReturnParams) { sendRequestData(w, r) } -func (s Service) PostReturn(w http.ResponseWriter, r *http.Request, params api.PostReturnParams) { +func (s Service) PostReturn(w http.ResponseWriter, r *http.Request, _ api.PostReturnParams) { sendRequestData(w, r) } -func (s Service) PutReturn(w http.ResponseWriter, r *http.Request, params api.PutReturnParams) { +func (s Service) PutReturn(w http.ResponseWriter, r *http.Request, _ api.PutReturnParams) { sendRequestData(w, r) } diff --git a/internal/pkg/service/api_status.go b/internal/pkg/service/api_status.go index d53b4ee..25cb9cf 100644 --- a/internal/pkg/service/api_status.go +++ b/internal/pkg/service/api_status.go @@ -5,29 +5,29 @@ import ( "net/http" ) -func (s Service) DeleteStatusStatus(w http.ResponseWriter, r *http.Request, status int, params api.DeleteStatusStatusParams) { - sendStatus(w, status) +func (s Service) DeleteStatusCode(w http.ResponseWriter, _ *http.Request, code int, _ api.DeleteStatusCodeParams) { + sendStatus(w, code) } -func (s Service) GetStatusStatus(w http.ResponseWriter, r *http.Request, status int, params api.GetStatusStatusParams) { - sendStatus(w, status) +func (s Service) GetStatusCode(w http.ResponseWriter, _ *http.Request, code int, _ api.GetStatusCodeParams) { + sendStatus(w, code) } -func (s Service) PatchStatusStatus(w http.ResponseWriter, r *http.Request, status int, params api.PatchStatusStatusParams) { - sendStatus(w, status) +func (s Service) PatchStatusCode(w http.ResponseWriter, _ *http.Request, code int, _ api.PatchStatusCodeParams) { + sendStatus(w, code) } -func (s Service) PostStatusStatus(w http.ResponseWriter, r *http.Request, status int, params api.PostStatusStatusParams) { - sendStatus(w, status) +func (s Service) PostStatusCode(w http.ResponseWriter, _ *http.Request, code int, _ api.PostStatusCodeParams) { + sendStatus(w, code) } -func (s Service) PutStatusStatus(w http.ResponseWriter, r *http.Request, status int, params api.PutStatusStatusParams) { - sendStatus(w, status) +func (s Service) PutStatusCode(w http.ResponseWriter, _ *http.Request, code int, _ api.PutStatusCodeParams) { + sendStatus(w, code) } -func sendStatus(w http.ResponseWriter, status int) { - sendJSON(w, status, map[string]any{ - "status": status, - "message": http.StatusText(status), +func sendStatus(w http.ResponseWriter, code int) { + sendJSON(w, code, map[string]any{ + "status": code, + "message": http.StatusText(code), }) } diff --git a/internal/pkg/service/api_stream.go b/internal/pkg/service/api_stream.go index 880960b..c599b12 100644 --- a/internal/pkg/service/api_stream.go +++ b/internal/pkg/service/api_stream.go @@ -7,12 +7,18 @@ import ( ) func (s Service) GetStreamJsonUser(w http.ResponseWriter, r *http.Request, params api.GetStreamJsonUserParams) { + if ok := s.Validate(w, ¶ms); !ok { + return + } s.streamJSON(w, r, params.Interval, func() (any, error) { return random.User(), nil }) } func (s Service) GetStreamJsonLogs(w http.ResponseWriter, r *http.Request, params api.GetStreamJsonLogsParams) { + if ok := s.Validate(w, ¶ms); !ok { + return + } s.streamJSON(w, r, params.Interval, func() (any, error) { return random.NewLog(1, nil)[0], nil // TODO: add log levels }) diff --git a/internal/pkg/service/server.go b/internal/pkg/service/server.go index 6e234b7..75099a2 100644 --- a/internal/pkg/service/server.go +++ b/internal/pkg/service/server.go @@ -13,7 +13,10 @@ import ( func (s Service) Start() error { slog.Info("starting httb service") - service := NewService(s.config) + service, err := NewService(s.config) + if err != nil { + return err + } r := http.NewServeMux() h := api.HandlerFromMux(service, r) diff --git a/internal/pkg/service/service.go b/internal/pkg/service/service.go index 79f23a6..ca1b226 100644 --- a/internal/pkg/service/service.go +++ b/internal/pkg/service/service.go @@ -1,13 +1,45 @@ package service import ( + "fmt" + "github.com/go-playground/locales/en" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + entranslations "github.com/go-playground/validator/v10/translations/en" "github.com/marvinjwendt/httb/internal/pkg/config" ) type Service struct { - config *config.Config + config *config.Config + validator *validator.Validate + translator ut.Translator } -func NewService(config *config.Config) *Service { - return &Service{config: config} +func NewService(config *config.Config) (*Service, error) { + val, trans, err := initValidator() + if err != nil { + return nil, err + } + + return &Service{ + config: config, + validator: val, + translator: trans, + }, nil +} + +func initValidator() (*validator.Validate, ut.Translator, error) { + validate := validator.New() + enLocale := en.New() + uni := ut.New(enLocale, enLocale) + translator, found := uni.GetTranslator("en") + if !found { + return nil, nil, fmt.Errorf("could not find en translation") + } + validate = validator.New() + err := entranslations.RegisterDefaultTranslations(validate, translator) + if err != nil { + return nil, nil, fmt.Errorf("failed to register default translation: %w", err) + } + return validate, translator, nil } diff --git a/internal/pkg/service/utils.go b/internal/pkg/service/utils.go index 9725c7a..b7dfdfe 100644 --- a/internal/pkg/service/utils.go +++ b/internal/pkg/service/utils.go @@ -13,12 +13,21 @@ const ( ResponseFormatText ) +func setStatus(w http.ResponseWriter, statusCode int) { + if statusCode < 100 || statusCode > 599 { + w.WriteHeader(http.StatusTeapot) + return + } + + w.WriteHeader(statusCode) +} + func prepareJSON(w http.ResponseWriter, statusCode int) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(statusCode) + setStatus(w, statusCode) } -func sendJSON(w http.ResponseWriter, statusCode int, data interface{}) { +func sendJSON(w http.ResponseWriter, statusCode int, data any) { prepareJSON(w, statusCode) _ = json.NewEncoder(w).Encode(data) } @@ -60,3 +69,7 @@ func sendFormattedResponse(w http.ResponseWriter, r *http.Request, text, keyName sendError(w, http.StatusBadRequest, "invalid format") } } + +func Ptr[A any](value A) *A { + return &value +} diff --git a/internal/pkg/service/validation.go b/internal/pkg/service/validation.go new file mode 100644 index 0000000..44131ff --- /dev/null +++ b/internal/pkg/service/validation.go @@ -0,0 +1,53 @@ +package service + +import ( + "errors" + "fmt" + "github.com/creasty/defaults" + "github.com/go-playground/validator/v10" + "github.com/marvinjwendt/httb/internal/pkg/api" + "net/http" + "strings" +) + +func (s Service) Validate(w http.ResponseWriter, data any) bool { + err := defaults.Set(data) + if err != nil { + sendJSON(w, http.StatusInternalServerError, api.ValidationError{ + StatusCode: http.StatusInternalServerError, + Message: fmt.Sprintf("error setting default value: %s", err.Error()), + }) + return false + } + + err = s.validator.Struct(data) + if err != nil { + var invalidValidationError *validator.InvalidValidationError + if errors.As(err, &invalidValidationError) { + sendJSON(w, http.StatusBadRequest, api.ValidationError{ + Message: "Validation failed: illegal argument was passed", + StatusCode: http.StatusBadRequest, + }) + } + + var result api.ValidationMessages + var errorsMap validator.ValidationErrors + errors.As(err, &errorsMap) + translatedErrors := errorsMap.Translate(s.translator) + for k, v := range translatedErrors { + keySplit := strings.Split(k, ".") + result = append(result, api.ValidationMessage{ + Field: strings.ToLower(keySplit[len(keySplit)-1]), + Message: v, + }) + } + sendJSON(w, http.StatusBadRequest, api.ValidationError{ + Message: "Validation failed", + StatusCode: http.StatusBadRequest, + Errors: &result, + }) + return false + } + + return true +}