OapiRequestValidator middleware not working
Hi,
I'm using Gin with OapiRequestValidator, but it seems that the middleware does not handle invalid request data. Did i miss something?
A tiny example:
import(
"net/http"
middleware "github.com/deepmap/oapi-codegen/pkg/gin-middleware"
// [...]
"some.repo/test/example-app/internal/api"
"some.repo/test/example-app/internal/handler"
"github.com/gin-gonic/gin"
)
func main()
g := gin.New()
swagger, _ := api.GetSwagger()
g.Use(middleware.OapiRequestValidator(swagger))
todo := handler.NewTodo()
api.RegisterHandlers(g, todo)
srv := &http.Server{
Addr: os.Getenv(":3000"),
Handler: g,
}
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic(err)
}
)
That looks correct to me. Could you construct a tiny, runnable example which demonstrates the problem?
That looks correct to me. Could you construct a tiny, runnable example which demonstrates the problem?
I have one:
OpenAPI
openapi: 3.0.3
info:
title: Local Storage API
version: v2
servers:
- url: /v2/local_storage
tags:
- name: Mount methods
- name: Mount
description: |-
<SchemaDefinition schemaRef="#/components/schemas/Mount" />
x-tagGroups:
- name: Mount
tags:
- Mount methods
- name: Schemas
tags:
- Mount
paths:
/mount:
get:
summary: Get all mounted volumes
description: |-
Get all volumes currently mounted on the system. Volumes can be filtered by corresponding query parameters.
operationId: getMounts
tags:
- Mount methods
parameters:
- name: id
in: query
description: |-
Filter the results by id
schema:
type: string
example: "0"
- name: mount_point
in: query
description: |-
Filter the results by mount point
schema:
type: string
example: "/"
- name: type
in: query
description: |-
Filter the results by type
schema:
type: string
example: "ext4"
- name: source
in: query
description: |-
Filter the results by source
schema:
type: string
example: "/dev/sda1"
responses:
'200':
$ref: '#/components/responses/GetMountsResponseOK'
post:
summary: Mount a volume
description: |-
(TODO)
operationId: mount
tags:
- Mount methods
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Mount'
responses:
'200':
$ref: '#/components/responses/AddMountResponseOK'
'400':
$ref: '#/components/responses/ResponseBadRequest'
'403':
$ref: '#/components/responses/ResponseForbidden'
'409':
$ref: '#/components/responses/ResponseConflict'
put:
summary: Update a mount volume
description: |-
Updating a mount volume is equivalent to unmounting the volume and mounting it again with the new parameters.
operationId: updateMount
tags:
- Mount methods
parameters:
- name: mount_point
in: query
required: true
description: |-
Filter the results by mount point
schema:
type: string
example: "/"
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Mount'
responses:
'200':
$ref: '#/components/responses/UpdateMountResponseOK'
'400':
$ref: '#/components/responses/ResponseBadRequest'
'403':
$ref: '#/components/responses/ResponseForbidden'
'404':
$ref: '#/components/responses/ResponseNotFound'
'409':
$ref: '#/components/responses/ResponseConflict'
delete:
summary: Umount volume
description: |-
(TODO)
operationId: Umount
tags:
- Mount methods
parameters:
- name: mount_point
in: query
required: true
description: |-
Filter the results by mount point
schema:
type: string
example: "/DATA"
- name: persist
in: query
description: |-
`true` if the mount should be removed from `/etc/fstab`, `false` otherwise
schema:
type: boolean
default: true
responses:
'200':
$ref: '#/components/responses/UmountResponseOK'
'400':
$ref: '#/components/responses/ResponseBadRequest'
'403':
$ref: '#/components/responses/ResponseForbidden'
'404':
$ref: '#/components/responses/ResponseNotFound'
'409':
$ref: '#/components/responses/ResponseConflict'
components:
responses:
GetMountsResponseOK:
description: OK
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Mount'
AddMountResponseOK:
description: OK
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
data:
$ref: '#/components/schemas/Mount'
UpdateMountResponseOK:
description: OK
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
- type: object
properties:
data:
$ref: '#/components/schemas/Mount'
UmountResponseOK:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/BaseResponse'
ResponseBadRequest:
description: Bad Request
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
example:
message: "Bad Request"
ResponseNotFound:
description: Not Found
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
example:
message: "Not Found"
ResponseForbidden:
description: Forbidden
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
example:
message: "Forbidden"
ResponseConflict:
description: Conflict
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/BaseResponse'
example:
message: "Conflict"
schemas:
BaseResponse:
properties:
message:
readOnly: true
description: message returned by server side if there is any
type: string
example: ""
Mount:
type: object
minProperties: 1
properties:
id:
type: integer
readOnly: true
mount_point:
type: string
nullable: false
example: "/DATA"
fstype:
type: string
nullable: false
example: "fuse.mergerfs"
source:
type: string
nullable: false
example: "/mnt/a:/mnt/b"
options:
type: string
example: "defaults,allow_other,use_ino,category.create=mfs,moveonenospc=true,minfreespace=1M"
persist:
type: boolean
description: |-
`true` if the mount should be persisted in `/etc/fstab`, `false` otherwise
default: false
extended:
type: object
description: |-
Extended properties of the mount
additionalProperties:
type: string
example:
"mergerfs.srcmounts": "/mnt/a:/mnt/b"
HTTP Request:
curl --location --request PUT 'http://localhost:8080/v2/local_storage/mount?mount_point=/media' \
--header 'Content-Type: application/json' \
--data-raw '{
"Mountpoint": "/media",
"FSType": "mergerfs",
"Source": "/mnt/sdb:/mnt/sdc",
"Options": "defaults,allow_other,use_ino,category.create=mfs,moveonenospc=true,minfreespace=1M",
"Persist": true
}'
Note in the request JSON, all fields are camelCase, where OpenAPI yaml expects snake_case:
{
"Mountpoint": "/media",
"FSType": "mergerfs",
"Source": "/mnt/sdb:/mnt/sdc",
"Options": "defaults,allow_other,use_ino,category.create=mfs,moveonenospc=true,minfreespace=1M",
"Persist": true
}
So the actual request the server side sees is
{
"mount_point": null,
"fstype": null,
"source": null,
"options": null,
"Persist": true
}
The validator should fail, because in the YAML, the mandatory fields are marked with nullable: false but it's in fact not honored:
mount_point:
type: string
nullable: false
example: "/DATA"
fstype:
type: string
nullable: false
example: "fuse.mergerfs"
source:
type: string
nullable: false
example: "/mnt/a:/mnt/b"
We simply wrap the openapi3 filter from https://github.com/getkin/kin-openapi to do request validation.
The bug lies in there, perhaps you could come up with a reproduction case for their project, and file an Issue there?
Here https://github.com/getkin/kin-openapi/issues/598