Skip to content

Commit

Permalink
implemented more missing functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
jakecoffman committed Feb 25, 2021
1 parent c61f51c commit 0c82dcb
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 43 deletions.
106 changes: 87 additions & 19 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"fmt"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"regexp"
"strconv"
"strings"
)

Expand All @@ -27,10 +29,14 @@ type Info struct {
}

type JsonSchema struct {
Type string `json:"type,omitempty"`
Properties map[string]JsonSchema `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
Example interface{} `json:"example,omitempty"`
Type string `json:"type,omitempty"`
Properties map[string]JsonSchema `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
Example interface{} `json:"example,omitempty"`
Description string `json:"description,omitempty"`
Minimum float64 `json:"minimum,omitempty"`
Maximum float64 `json:"maximum,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
}

type Path struct {
Expand All @@ -40,6 +46,7 @@ type Path struct {
Post *Operation `json:"post,omitempty"`
Put *Operation `json:"put,omitempty"`
Delete *Operation `json:"delete,omitempty"`
Patch *Operation `json:"patch,omitempty"`
}

type Operation struct {
Expand All @@ -55,7 +62,11 @@ type Parameter struct {
Type string `json:"type,omitempty"`
Schema *Ref `json:"schema,omitempty"`

Required *bool `json:"required,omitempty"`
Required *bool `json:"required,omitempty"`
Description string `json:"description,omitempty"`
Minimum *float64 `json:"minimum,omitempty"`
Maximum *float64 `json:"maximum,omitempty"`
Enum []interface{} `json:"enum,omitempty"`
}

type Ref struct {
Expand Down Expand Up @@ -145,6 +156,9 @@ func (r *Router) Serve(addr string) error {
case "put":
path.Put = &Operation{Responses: DefaultResponse}
operation = path.Put
case "patch":
path.Patch = &Operation{Responses: DefaultResponse}
operation = path.Patch
case "delete":
path.Delete = &Operation{Responses: DefaultResponse}
operation = path.Delete
Expand All @@ -155,22 +169,32 @@ func (r *Router) Serve(addr string) error {

if spec.Validate.Path != nil {
for name, field := range spec.Validate.Path {
operation.Parameters = append(operation.Parameters, Parameter{
In: "path",
Name: name,
Type: field.Type,
Required: field.IsRequired,
})
param := Parameter{
In: "path",
Name: name,
Type: field.kind,
Required: field.required,
Description: field.description,
Enum: field.enum,
Minimum: field.min,
Maximum: field.max,
}
operation.Parameters = append(operation.Parameters, param)
}
}
if spec.Validate.Query != nil {
for name, field := range spec.Validate.Query {
operation.Parameters = append(operation.Parameters, Parameter{
In: "query",
Name: name,
Type: field.Type,
Required: field.IsRequired,
})
param := Parameter{
In: "query",
Name: name,
Type: field.kind,
Required: field.required,
Description: field.description,
Enum: field.enum,
Minimum: field.min,
Maximum: field.max,
}
operation.Parameters = append(operation.Parameters, param)
}
}
if spec.Validate.Body != nil {
Expand Down Expand Up @@ -208,7 +232,51 @@ func preHandler(spec Spec) gin.HandlerFunc {
if val.Query != nil {
values := c.Request.URL.Query()
for field, schema := range val.Query {
if err := schema.Validate(values.Get(field)); err != nil {
// query values are always strings, so we must try to convert
queryValue := values.Get(field)

// don't try to convert if the field is empty
if queryValue == "" {
if schema.required != nil && *schema.required {
c.AbortWithStatusJSON(400, fmt.Sprintf("Query validation failed for field %v: %v", field, ErrRequired))
}
return
}
var convertedValue interface{}
switch schema.kind {
case KindBoolean:
if queryValue == "true" {
convertedValue = true
} else if queryValue == "false" {
convertedValue = false
} else {
c.AbortWithStatusJSON(400, fmt.Sprintf("Query validation failed for field %v: %v", field, ErrWrongType))
return
}
case KindString:
convertedValue = queryValue
case KindNumber:
var err error
convertedValue, err = strconv.ParseFloat(queryValue, 64)
if err != nil {
c.AbortWithStatusJSON(400, fmt.Sprintf("Query validation failed for field %v: %v", field, ErrWrongType))
return
}
case KindInteger:
var err error
convertedValue, err = strconv.Atoi(queryValue)
if err != nil {
c.AbortWithStatusJSON(400, fmt.Sprintf("Query validation failed for field %v: %v", field, ErrWrongType))
return
}
case KindArray:
// TODO I'm not sure how this works yet
c.AbortWithStatusJSON(http.StatusNotImplemented, "TODO")
return
default:
c.AbortWithStatusJSON(400, fmt.Sprintf("Validation not possible due to kind: %v", schema.kind))
}
if err := schema.Validate(convertedValue); err != nil {
c.AbortWithStatusJSON(400, fmt.Sprintf("Query validation failed for field %v: %v", field, err.Error()))
return
}
Expand All @@ -235,7 +303,7 @@ func preHandler(spec Spec) gin.HandlerFunc {
if val.Path != nil {
for field, schema := range val.Path {
path := c.Param(field)
if schema.IsRequired != nil && *schema.IsRequired && path == "" {
if schema.required != nil && *schema.required && path == "" {
c.AbortWithStatusJSON(400, fmt.Sprintf("Missing path param"))
return
}
Expand Down
104 changes: 80 additions & 24 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
)

type Field struct {
Type string `json:"type"`
Maximum *float64 `json:"maximum,omitempty"`
Minimum *float64 `json:"minimum,omitempty"`
IsRequired *bool `json:"required,omitempty"`
Ex interface{} `json:"example,omitempty"`
kind string
max *float64
min *float64
required *bool
example interface{}
description string
enum []interface{}
}

var (
Expand All @@ -20,68 +22,114 @@ var (
)

func (f *Field) Validate(value interface{}) error {
if value == nil && *f.IsRequired {
if value == nil && f.required != nil && *f.required {
return ErrRequired
}
if value == nil {
return nil
}

switch v := value.(type) {
case int:
if f.Type != "number" {
if f.kind != "number" {
return ErrWrongType
}
if f.Maximum != nil && float64(v) > *f.Maximum {
if f.max != nil && float64(v) > *f.max {
return ErrMaximum
}
if f.Minimum != nil && float64(v) < *f.Minimum {
if f.min != nil && float64(v) < *f.min {
return ErrMinimum
}
case float64:
if f.Type != "number" {
if f.kind != "number" {
return ErrWrongType
}
if f.Maximum != nil && v > *f.Maximum {
if f.max != nil && v > *f.max {
return ErrMaximum
}
if f.Minimum != nil && v < *f.Minimum {
if f.min != nil && v < *f.min {
return ErrMinimum
}
case string:
if f.Type != "string" {
if f.kind != "string" {
return ErrWrongType
}
case bool:
if f.kind != "boolean" {
return ErrWrongType
}
case []interface{}:
if f.kind != "array" {
return ErrWrongType
}
default:
return ErrWrongType
return fmt.Errorf("unhandled type %v", v)
}

return nil
}

const (
KindNumber = "number"
KindString = "string"
KindBoolean = "boolean"
KindArray = "array"
KindFile = "file"
KindInteger = "integer"
)

func Number() Field {
return Field{Type: "number"}
return Field{kind: KindNumber}
}

func String() Field {
return Field{Type: "string"}
return Field{kind: KindString}
}

func Boolean() Field {
return Field{kind: KindBoolean}
}

func Array() Field {
return Field{kind: KindArray}
}

func File() Field {
return Field{kind: KindFile}
}

func Integer() Field {
return Field{kind: KindInteger}
}

func (f Field) Min(min float64) Field {
f.Minimum = &min
f.min = &min
return f
}

func (f Field) Max(max float64) Field {
f.Maximum = &max
f.max = &max
return f
}

func (f Field) Required() Field {
required := true
f.IsRequired = &required
f.required = &required
return f
}

func (f Field) Example(ex interface{}) Field {
f.Ex = ex
f.example = ex
return f
}

func (f Field) Description(description string) Field {
f.description = description
return f
}

func (f Field) Enum(values ...interface{}) Field {
f.enum = values
return f
}

Expand All @@ -92,13 +140,21 @@ func ToJsonSchema(fields map[string]Field) JsonSchema {
}

for name, field := range fields {
schema.Properties[name] = JsonSchema{
Type: field.Type,
Example: field.Ex,
prop := JsonSchema{
Type: field.kind,
Example: field.example,
Description: field.description,
}
if field.IsRequired != nil && *field.IsRequired {
if field.required != nil && *field.required {
schema.Required = append(schema.Required, name)
}
if field.min != nil {
prop.Minimum = *field.min
}
if field.max != nil {
prop.Maximum = *field.max
}
schema.Properties[name] = prop
}

return schema
Expand Down

0 comments on commit 0c82dcb

Please sign in to comment.