Skip to content

Commit

Permalink
Merge pull request #43 from nikicc/support-example-tag
Browse files Browse the repository at this point in the history
feat: support example in schema object
  • Loading branch information
wI2L authored Nov 19, 2020
2 parents e161069 + 2ff2d64 commit 91f9fae
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@

# tests
coverage.txt

# GoLand
.idea
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ You can use additional tags. Some will be interpreted by *tonic*, others will be
- `description`: Add a description of the field in the spec.
- `deprecated`: Indicates if the field is deprecated. Accepted values are _1_, _t_, _T_, _TRUE_, _true_, _True_, _0_, _f_, _F_, _FALSE_. Invalid value are considered to be false.
- `enum`: A coma separated list of acceptable values for the parameter.
- `example`: An example value to be used in OpenAPI specification.
- `format`: Override the format of the field in the specification. Read the [documentation](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#dataTypeFormat) for more informations.
- `validate`: Field validation rules. Read the [documentation](https://godoc.org/gopkg.in/go-playground/validator.v8) for more informations.
- `explode`: Specifies whether arrays should generate separate parameters for each array item or object property (limited to query parameters with *form* style). Accepted values are _1_, _t_, _T_, _TRUE_, _true_, _True_, _0_, _f_, _F_, _FALSE_. Invalid value are considered to be false.
Expand Down
4 changes: 2 additions & 2 deletions examples/market/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (

// Fruit represents a sweet, fresh fruit.
type Fruit struct {
Name string `json:"name" validate:"required"`
Name string `json:"name" validate:"required" example:"banana"`
Origin string `json:"origin" validate:"required" description:"Country of origin of the fruit" enum:"ecuador,france,senegal,china,spain"`
Price float64 `json:"price" validate:"required" description:"Price in euros"`
Price float64 `json:"price" validate:"required" description:"Price in euros" example:"5.13"`
AddedAt time.Time `json:"-" binding:"-" description:"Date of addition of the fruit to the market"`
}

Expand Down
34 changes: 34 additions & 0 deletions openapi/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,22 @@ func (g *Generator) newSchemaFromStructField(sf reflect.StructField, required bo
if t, ok := sf.Tag.Lookup(formatTag); ok {
schema.Format = t
}

// Set example value from tag to schema
if e := strings.TrimSpace(sf.Tag.Get("example")); e != "" {
if parsed, err := parseExampleValue(sf.Type, e); err != nil {
g.error(&FieldError{
Message: fmt.Sprintf("could not parse the example value %q of field %q: %s", e, fname, err),
Name: fname,
Type: sf.Type,
TypeName: g.typeName(sf.Type),
Parent: parent,
})
} else {
schema.Example = parsed
}
}

return sor
}

Expand Down Expand Up @@ -1161,3 +1177,21 @@ func fieldNameFromTag(sf reflect.StructField, tagName string) string {
}
return name
}

/// parseExampleValue is used to transform the string representation of the example value to the correct type.
func parseExampleValue(t reflect.Type, value string) (interface{}, error) {
switch t.Kind() {
case reflect.Bool:
return strconv.ParseBool(value)
case reflect.String:
return value, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.ParseInt(value, 10, t.Bits())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.ParseUint(value, 10, t.Bits())
case reflect.Float32, reflect.Float64:
return strconv.ParseFloat(value, t.Bits())
default:
return nil, fmt.Errorf("unsuported type: %s", t.String())
}
}
98 changes: 98 additions & 0 deletions openapi/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,40 @@ func TestNewSchemaFromStructErrors(t *testing.T) {
assert.Nil(t, sor)
}

// TestNewSchemaFromStructFieldExampleValues tests the
// case of setting example values.
func TestNewSchemaFromStructFieldExampleValues(t *testing.T) {
g := gen(t)

type T struct {
A string `example:"value"`
B int `example:"1"`
C float64 `example:"0.1"`
D bool `example:"true"`
}
typ := reflect.TypeOf(T{})

// Field A contains string example.
sor := g.newSchemaFromStructField(typ.Field(0), false, "A", typ)
assert.NotNil(t, sor)
assert.Equal(t, "value", sor.Example)

// Field B contains int example.
sor = g.newSchemaFromStructField(typ.Field(1), false, "B", typ)
assert.NotNil(t, sor)
assert.Equal(t, int64(1), sor.Example)

// Field C contains float example.
sor = g.newSchemaFromStructField(typ.Field(2), false, "C", typ)
assert.NotNil(t, sor)
assert.Equal(t, 0.1, sor.Example)

// Field D contains boolean example.
sor = g.newSchemaFromStructField(typ.Field(3), false, "D", typ)
assert.NotNil(t, sor)
assert.Equal(t, true, sor.Example)
}

// TestNewSchemaFromStructFieldErrors tests the errors
// case of generation of a schema from a struct field.
func TestNewSchemaFromStructFieldErrors(t *testing.T) {
Expand All @@ -235,6 +269,7 @@ func TestNewSchemaFromStructFieldErrors(t *testing.T) {
A string `validate:"required" default:"foobar"`
B int `default:"foobaz"`
C int `enum:"a,1,c"`
D bool `example:"not-a-bool-value"`
}
typ := reflect.TypeOf(T{})

Expand All @@ -260,6 +295,17 @@ func TestNewSchemaFromStructFieldErrors(t *testing.T) {
assert.Len(t, g.Errors(), 4)
assert.NotEmpty(t, g.Errors()[2].Error())
assert.NotEmpty(t, g.Errors()[3].Error())

// Field D has example value that cannot be parsed to bool.
sor = g.newSchemaFromStructField(typ.Field(3), false, "D", typ)
assert.NotNil(t, sor)
assert.Len(t, g.Errors(), 5)
assert.NotEmpty(t, g.Errors()[4].Error())
// check that Name & Type of the error are set correctly
fe, ok := g.Errors()[4].(*FieldError)
assert.True(t, ok)
assert.Equal(t, "D", fe.Name)
assert.Equal(t, reflect.Bool, fe.Type.Kind())
}

func diffJSON(a, b []byte) (bool, error) {
Expand Down Expand Up @@ -576,6 +622,58 @@ func TestSetServers(t *testing.T) {
assert.Equal(t, servers, g.API().Servers)
}

// TestGenerator_parseExampleValue tests the parsing of example values.
func TestGenerator_parseExampleValue(t *testing.T) {
var testCases = []struct {
testName string
typ reflect.Type
inputValue string
outputValue interface{}
}{
{
"mapping to string",
reflect.TypeOf("value"),
"value",
"value",
}, {
"mapping to int",
reflect.TypeOf(1),
"1",
int64(1),
}, {
"mapping to uint8",
reflect.TypeOf(uint8(1)),
"1",
uint64(1),
}, {
"mapping to number",
reflect.TypeOf(1.23),
"1.23",
1.23,
}, {
"mapping to boolean",
reflect.TypeOf(true),
"true",
true,
},
}

for _, tc := range testCases {
t.Run(tc.testName, func(t *testing.T) {
returned, err := parseExampleValue(tc.typ, tc.inputValue)
assert.Nil(t, err)
assert.Equal(t, tc.outputValue, returned)
})
}
}

// TestGenerator_parseExampleValueError tests that
// parseExampleValue raises error on unsupported type.
func TestGenerator_parseExampleValueError(t *testing.T) {
_, err := parseExampleValue(reflect.TypeOf(map[string]string{}), "whatever")
assert.Error(t, err, "parseExampleValue does not support type")
}

func gen(t *testing.T) *Generator {
g, err := NewGenerator(genConfig)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions openapi/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ type Schema struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`

// The following properties are taken directly from the
// JSON Schema definition and follow the same specifications
Expand Down

0 comments on commit 91f9fae

Please sign in to comment.