Skip to content
This repository has been archived by the owner on Sep 13, 2023. It is now read-only.

Commit

Permalink
Merge pull request #233 from go-dummy/feature/bind
Browse files Browse the repository at this point in the history
feature: Add bad request case
  • Loading branch information
sashamelentyev authored Jan 12, 2022
2 parents 00b06d5 + f72d275 commit a9b8072
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 6 deletions.
6 changes: 6 additions & 0 deletions internal/apischema/apischema.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ type API struct {
type Operation struct {
Method string
Path string
Body map[string]FieldType
Responses []Response
}

type FieldType struct {
Required bool
Type string
}

type Response struct {
StatusCode int
MediaType string
Expand Down
24 changes: 24 additions & 0 deletions internal/apischema/find.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package apischema

import (
"encoding/json"
"errors"
"io"
"net/http"
"strings"
)

Expand All @@ -16,9 +20,12 @@ func (e *FindResponseError) Error() string {
type FindResponseParams struct {
Path string
Method string
Body io.ReadCloser
MediaType string
}

var ErrEmptyRequireField = errors.New("empty require field")

func (a API) FindResponse(params FindResponseParams) (Response, error) {
operation, ok := a.findOperation(params)
if !ok {
Expand All @@ -28,6 +35,23 @@ func (a API) FindResponse(params FindResponseParams) (Response, error) {
}
}

switch params.Method {
case http.MethodPost, http.MethodPut, http.MethodPatch:
var body map[string]interface{}

err := json.NewDecoder(params.Body).Decode(&body)
if err != nil {
return Response{}, err
}

for k, v := range operation.Body {
_, ok := body[k]
if !ok && v.Required {
return Response{}, ErrEmptyRequireField
}
}
}

response, ok := operation.findResponse(params)
if !ok {
return operation.Responses[0], nil
Expand Down
34 changes: 32 additions & 2 deletions internal/openapi3/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (e *SchemaTypeError) Error() string {
return "unknown type " + e.schemaType
}

var EmptyItemsErr = errors.New("empty items in array")
var ErrEmptyItems = errors.New("empty items in array")

type builder struct {
openapi OpenAPI
Expand Down Expand Up @@ -126,6 +126,36 @@ func (b *builder) Set(path, method string, o *Operation) (apischema.Operation, e
operation := apischema.Operation{
Method: method,
Path: path,
Body: make(map[string]apischema.FieldType),
}

body, ok := o.RequestBody.Content["application/json"]
if ok {
var s Schema

if body.Schema.Reference != "" {
schema, err := b.openapi.LookupByReference(body.Schema.Reference)
if err != nil {
return apischema.Operation{}, fmt.Errorf("resolve reference: %w", err)
}

s = schema
} else {
s = body.Schema
}

for _, v := range s.Required {
operation.Body[v] = apischema.FieldType{
Required: true,
}
}

for k, v := range s.Properties {
operation.Body[k] = apischema.FieldType{
Required: operation.Body[k].Required,
Type: v.Type,
}
}
}

for code, resp := range o.Responses {
Expand Down Expand Up @@ -201,7 +231,7 @@ func (b *builder) convertSchema(s Schema) (apischema.Schema, error) {
return apischema.StringSchema{Example: val}, nil
case "array":
if nil == s.Items {
return nil, EmptyItemsErr
return nil, ErrEmptyItems
}

itemsSchema, err := b.convertSchema(*s.Items)
Expand Down
16 changes: 16 additions & 0 deletions internal/openapi3/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ func TestParse_YAML(t *testing.T) {
{
Method: "POST",
Path: "/users",
Body: map[string]apischema.FieldType{
"id": {
Required: true,
Type: "string",
},
"firstName": {
Required: true,
Type: "string",
},
"lastName": {
Required: true,
Type: "string",
},
},
Responses: []apischema.Response{
{
StatusCode: 201,
Expand All @@ -35,6 +49,7 @@ func TestParse_YAML(t *testing.T) {
{
Method: "GET",
Path: "/users",
Body: map[string]apischema.FieldType{},
Responses: []apischema.Response{
{
StatusCode: 200,
Expand Down Expand Up @@ -69,6 +84,7 @@ func TestParse_YAML(t *testing.T) {
{
Method: "GET",
Path: "/users/{userId}",
Body: map[string]apischema.FieldType{},
Responses: []apischema.Response{
{
StatusCode: 200,
Expand Down
1 change: 1 addition & 0 deletions internal/openapi3/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Schema struct {
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Faker string `json:"x-faker,omitempty" yaml:"x-faker,omitempty"`
Required []string `json:"required,omitempty" yaml:"required,omitempty"`

Items *Schema `json:"items,omitempty" yaml:"items,omitempty"`

Expand Down
25 changes: 21 additions & 4 deletions internal/server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package server

import (
"encoding/json"
"errors"
"io"
"net/http"
"strings"

Expand Down Expand Up @@ -33,8 +35,14 @@ func (s *Server) Handler(w http.ResponseWriter, r *http.Request) {

path := RemoveFragment(r.URL.Path)

response, ok := s.Handlers.Get(path, r.Method)
response, ok, err := s.Handlers.Get(path, r.Method, r.Body)
if ok {
if _, ok := err.(*json.SyntaxError); ok || errors.Is(err, apischema.ErrEmptyRequireField) {
w.WriteHeader(http.StatusBadRequest)

return
}

w.WriteHeader(response.StatusCode)
resp := response.ExampleValue(r.Header.Get("X-Example"))

Expand All @@ -59,16 +67,25 @@ func (s *Server) Handler(w http.ResponseWriter, r *http.Request) {
}

// Get -.
func (h Handlers) Get(path, method string) (apischema.Response, bool) {
func (h Handlers) Get(path, method string, body io.ReadCloser) (apischema.Response, bool, error) {
response, err := h.API.FindResponse(apischema.FindResponseParams{
Path: path,
Method: method,
Body: body,
})
if err != nil {
return apischema.Response{}, false
if errors.Is(err, apischema.ErrEmptyRequireField) {
return apischema.Response{}, true, err
}

if _, ok := err.(*json.SyntaxError); ok {
return apischema.Response{}, true, err
}

return apischema.Response{}, false, err
}

return response, true
return response, true, nil
}

func setStatusCode(w http.ResponseWriter, statusCode string) bool {
Expand Down
43 changes: 43 additions & 0 deletions test/testdata/badrequest/openapi3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
openapi: 3.0.3

info:
title: Users dummy API
version: 0.1.0

paths:
/users:
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/User"
responses:
'201':
description: ''
content:
application/json:
schema:
$ref: '#/components/schemas/User'
example:
id: e1afccea-5168-4735-84d4-cb96f6fb5d25
firstName: Elon
lastName: Musk

components:
schemas:
User:
type: object
required:
- id
- firstName
- lastName
properties:
id:
type: string
format: uuid
firstName:
type: string
lastName:
type: string
23 changes: 23 additions & 0 deletions test/testdata/badrequest/pact.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"consumer": {
"name": "consumer"
},
"provider": {
"name": "dummy"
},
"interactions": [
{
"description": "",
"request": {
"method": "POST",
"path": "/users",
"body": {
"id": "e1afccea-5168-4735-84d4-cb96f6fb5d25"
}
},
"response": {
"status": 400
}
}
]
}

0 comments on commit a9b8072

Please sign in to comment.