Skip to content

Commit 91f9fae

Browse files
authored
Merge pull request #43 from nikicc/support-example-tag
feat: support example in schema object
2 parents e161069 + 2ff2d64 commit 91f9fae

File tree

6 files changed

+139
-2
lines changed

6 files changed

+139
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@
1515

1616
# tests
1717
coverage.txt
18+
19+
# GoLand
20+
.idea

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ You can use additional tags. Some will be interpreted by *tonic*, others will be
190190
- `description`: Add a description of the field in the spec.
191191
- `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.
192192
- `enum`: A coma separated list of acceptable values for the parameter.
193+
- `example`: An example value to be used in OpenAPI specification.
193194
- `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.
194195
- `validate`: Field validation rules. Read the [documentation](https://godoc.org/gopkg.in/go-playground/validator.v8) for more informations.
195196
- `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.

examples/market/market.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99

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

openapi/generator.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,22 @@ func (g *Generator) newSchemaFromStructField(sf reflect.StructField, required bo
758758
if t, ok := sf.Tag.Lookup(formatTag); ok {
759759
schema.Format = t
760760
}
761+
762+
// Set example value from tag to schema
763+
if e := strings.TrimSpace(sf.Tag.Get("example")); e != "" {
764+
if parsed, err := parseExampleValue(sf.Type, e); err != nil {
765+
g.error(&FieldError{
766+
Message: fmt.Sprintf("could not parse the example value %q of field %q: %s", e, fname, err),
767+
Name: fname,
768+
Type: sf.Type,
769+
TypeName: g.typeName(sf.Type),
770+
Parent: parent,
771+
})
772+
} else {
773+
schema.Example = parsed
774+
}
775+
}
776+
761777
return sor
762778
}
763779

@@ -1161,3 +1177,21 @@ func fieldNameFromTag(sf reflect.StructField, tagName string) string {
11611177
}
11621178
return name
11631179
}
1180+
1181+
/// parseExampleValue is used to transform the string representation of the example value to the correct type.
1182+
func parseExampleValue(t reflect.Type, value string) (interface{}, error) {
1183+
switch t.Kind() {
1184+
case reflect.Bool:
1185+
return strconv.ParseBool(value)
1186+
case reflect.String:
1187+
return value, nil
1188+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
1189+
return strconv.ParseInt(value, 10, t.Bits())
1190+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
1191+
return strconv.ParseUint(value, 10, t.Bits())
1192+
case reflect.Float32, reflect.Float64:
1193+
return strconv.ParseFloat(value, t.Bits())
1194+
default:
1195+
return nil, fmt.Errorf("unsuported type: %s", t.String())
1196+
}
1197+
}

openapi/generator_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,40 @@ func TestNewSchemaFromStructErrors(t *testing.T) {
226226
assert.Nil(t, sor)
227227
}
228228

229+
// TestNewSchemaFromStructFieldExampleValues tests the
230+
// case of setting example values.
231+
func TestNewSchemaFromStructFieldExampleValues(t *testing.T) {
232+
g := gen(t)
233+
234+
type T struct {
235+
A string `example:"value"`
236+
B int `example:"1"`
237+
C float64 `example:"0.1"`
238+
D bool `example:"true"`
239+
}
240+
typ := reflect.TypeOf(T{})
241+
242+
// Field A contains string example.
243+
sor := g.newSchemaFromStructField(typ.Field(0), false, "A", typ)
244+
assert.NotNil(t, sor)
245+
assert.Equal(t, "value", sor.Example)
246+
247+
// Field B contains int example.
248+
sor = g.newSchemaFromStructField(typ.Field(1), false, "B", typ)
249+
assert.NotNil(t, sor)
250+
assert.Equal(t, int64(1), sor.Example)
251+
252+
// Field C contains float example.
253+
sor = g.newSchemaFromStructField(typ.Field(2), false, "C", typ)
254+
assert.NotNil(t, sor)
255+
assert.Equal(t, 0.1, sor.Example)
256+
257+
// Field D contains boolean example.
258+
sor = g.newSchemaFromStructField(typ.Field(3), false, "D", typ)
259+
assert.NotNil(t, sor)
260+
assert.Equal(t, true, sor.Example)
261+
}
262+
229263
// TestNewSchemaFromStructFieldErrors tests the errors
230264
// case of generation of a schema from a struct field.
231265
func TestNewSchemaFromStructFieldErrors(t *testing.T) {
@@ -235,6 +269,7 @@ func TestNewSchemaFromStructFieldErrors(t *testing.T) {
235269
A string `validate:"required" default:"foobar"`
236270
B int `default:"foobaz"`
237271
C int `enum:"a,1,c"`
272+
D bool `example:"not-a-bool-value"`
238273
}
239274
typ := reflect.TypeOf(T{})
240275

@@ -260,6 +295,17 @@ func TestNewSchemaFromStructFieldErrors(t *testing.T) {
260295
assert.Len(t, g.Errors(), 4)
261296
assert.NotEmpty(t, g.Errors()[2].Error())
262297
assert.NotEmpty(t, g.Errors()[3].Error())
298+
299+
// Field D has example value that cannot be parsed to bool.
300+
sor = g.newSchemaFromStructField(typ.Field(3), false, "D", typ)
301+
assert.NotNil(t, sor)
302+
assert.Len(t, g.Errors(), 5)
303+
assert.NotEmpty(t, g.Errors()[4].Error())
304+
// check that Name & Type of the error are set correctly
305+
fe, ok := g.Errors()[4].(*FieldError)
306+
assert.True(t, ok)
307+
assert.Equal(t, "D", fe.Name)
308+
assert.Equal(t, reflect.Bool, fe.Type.Kind())
263309
}
264310

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

625+
// TestGenerator_parseExampleValue tests the parsing of example values.
626+
func TestGenerator_parseExampleValue(t *testing.T) {
627+
var testCases = []struct {
628+
testName string
629+
typ reflect.Type
630+
inputValue string
631+
outputValue interface{}
632+
}{
633+
{
634+
"mapping to string",
635+
reflect.TypeOf("value"),
636+
"value",
637+
"value",
638+
}, {
639+
"mapping to int",
640+
reflect.TypeOf(1),
641+
"1",
642+
int64(1),
643+
}, {
644+
"mapping to uint8",
645+
reflect.TypeOf(uint8(1)),
646+
"1",
647+
uint64(1),
648+
}, {
649+
"mapping to number",
650+
reflect.TypeOf(1.23),
651+
"1.23",
652+
1.23,
653+
}, {
654+
"mapping to boolean",
655+
reflect.TypeOf(true),
656+
"true",
657+
true,
658+
},
659+
}
660+
661+
for _, tc := range testCases {
662+
t.Run(tc.testName, func(t *testing.T) {
663+
returned, err := parseExampleValue(tc.typ, tc.inputValue)
664+
assert.Nil(t, err)
665+
assert.Equal(t, tc.outputValue, returned)
666+
})
667+
}
668+
}
669+
670+
// TestGenerator_parseExampleValueError tests that
671+
// parseExampleValue raises error on unsupported type.
672+
func TestGenerator_parseExampleValueError(t *testing.T) {
673+
_, err := parseExampleValue(reflect.TypeOf(map[string]string{}), "whatever")
674+
assert.Error(t, err, "parseExampleValue does not support type")
675+
}
676+
579677
func gen(t *testing.T) *Generator {
580678
g, err := NewGenerator(genConfig)
581679
if err != nil {

openapi/spec.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ type Schema struct {
156156
Description string `json:"description,omitempty" yaml:"description,omitempty"`
157157
Format string `json:"format,omitempty" yaml:"format,omitempty"`
158158
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
159+
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
159160

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

0 commit comments

Comments
 (0)