Skip to content

Commit

Permalink
feat: support providing examples for pointer fields (#56)
Browse files Browse the repository at this point in the history
Co-authored-by: William Poussier <[email protected]>
  • Loading branch information
nikicc and wI2L authored May 7, 2021
1 parent 6c2edb3 commit d0b235e
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 39 deletions.
32 changes: 30 additions & 2 deletions openapi/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1235,11 +1235,39 @@ func parseExampleValue(t reflect.Type, value string) (interface{}, error) {
case reflect.String:
return value, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.ParseInt(value, 10, t.Bits())
i, err := strconv.ParseInt(value, 10, t.Bits())
if err != nil {
return nil, err
}
switch t.Bits() {
case 8:
return int8(i), nil
case 16:
return int16(i), nil
case 32:
return int32(i), nil
default:
}
return i, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.ParseUint(value, 10, t.Bits())
u, err := strconv.ParseUint(value, 10, t.Bits())
if err != nil {
return nil, err
}
switch t.Bits() {
case 8:
return uint8(u), nil
case 16:
return uint16(u), nil
case 32:
return uint32(u), nil
default:
}
return u, nil
case reflect.Float32, reflect.Float64:
return strconv.ParseFloat(value, t.Bits())
case reflect.Ptr:
return parseExampleValue(t.Elem(), value)
default:
return nil, fmt.Errorf("unsuported type: %s", t.String())
}
Expand Down
225 changes: 194 additions & 31 deletions openapi/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package openapi
import (
"encoding/json"
"io/ioutil"
"math"
"reflect"
"testing"
"time"
Expand Down Expand Up @@ -129,6 +130,20 @@ func TestSchemaFromPrimitiveType(t *testing.T) {
assert.True(t, schema.Nullable)
}

// TestSchemaFromInterface tests that a schema
// can be created for an interface{} value that
// represent *any* type.
func TestSchemaFromInterface(t *testing.T) {
g := gen(t)

schema := g.newSchemaFromType(tofEmptyInterface)
assert.NotNil(t, schema)
assert.Empty(t, schema.Type)
assert.Empty(t, schema.Format)
assert.True(t, schema.Nullable)
assert.NotEmpty(t, schema.Description)
}

// TestSchemaFromUnsupportedType tests that a schema
// cannot be created given an unsupported input type.
func TestSchemaFromUnsupportedType(t *testing.T) {
Expand Down Expand Up @@ -232,10 +247,16 @@ 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"`
A string `example:"value"`
APtr *string `example:"value"`
B int `example:"1"`
BPtr *int `example:"1"`
C float64 `example:"0.1"`
CPtr *float64 `example:"0.1"`
D bool `example:"true"`
DPtr *bool `example:"true"`
EPtr **bool `example:"false"`
FPtr ***uint16 `example:"128"`
}
typ := reflect.TypeOf(T{})

Expand All @@ -244,20 +265,50 @@ func TestNewSchemaFromStructFieldExampleValues(t *testing.T) {
assert.NotNil(t, sor)
assert.Equal(t, "value", sor.Example)

// Field APtr contains pointer to string example.
sor = g.newSchemaFromStructField(typ.Field(1), false, "APtr", 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)
sor = g.newSchemaFromStructField(typ.Field(2), false, "B", typ)
assert.NotNil(t, sor)
assert.Equal(t, int64(1), sor.Example)

// Field BPtr contains pointer to int example.
sor = g.newSchemaFromStructField(typ.Field(3), false, "BPtr", 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)
sor = g.newSchemaFromStructField(typ.Field(4), false, "C", typ)
assert.NotNil(t, sor)
assert.Equal(t, 0.1, sor.Example)

// Field CPtr contains pointer to float example.
sor = g.newSchemaFromStructField(typ.Field(5), false, "CPtr", 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)
sor = g.newSchemaFromStructField(typ.Field(6), false, "D", typ)
assert.NotNil(t, sor)
assert.Equal(t, true, sor.Example)

// Field DPtr contains pointer to boolean example.
sor = g.newSchemaFromStructField(typ.Field(7), false, "DPtr", typ)
assert.NotNil(t, sor)
assert.Equal(t, true, sor.Example)

// Field EPtr contains a double-pointer to boolean example.
sor = g.newSchemaFromStructField(typ.Field(8), false, "EPtr", typ)
assert.NotNil(t, sor)
assert.Equal(t, false, sor.Example)

// Field FPtr contains a triple-pointer to uint16 value example.
sor = g.newSchemaFromStructField(typ.Field(9), false, "FPtr", typ)
assert.NotNil(t, sor)
assert.Equal(t, uint16(128), sor.Example)
}

// TestNewSchemaFromStructFieldErrors tests the errors
Expand Down Expand Up @@ -365,23 +416,23 @@ func TestAddOperation(t *testing.T) {
Description: "XYZ",
Deprecated: true,
Responses: []*OperationResponse{
&OperationResponse{
{
Code: "400",
Description: "Bad Request",
Model: CustomError{},
},
&OperationResponse{
{
Code: "5XX",
Description: "Server Errors",
},
},
Headers: []*ResponseHeader{
&ResponseHeader{
{
Name: "X-Test-Header",
Description: "Test header",
Model: Header,
},
&ResponseHeader{
{
Name: "X-Test-Header-Alt",
Description: "Test header alt",
},
Expand Down Expand Up @@ -659,16 +710,22 @@ func TestSetServers(t *testing.T) {
g := gen(t)

servers := []*Server{
&Server{URL: "https://dev.api.foo.bar/v1", Description: "Development server"},
&Server{URL: "https://prod.api.foo.bar/{basePath}", Description: "Production server", Variables: map[string]*ServerVariable{
"basePath": &ServerVariable{
Description: "Version of the API",
Enum: []string{
"v1", "v2", "beta",
{
URL: "https://dev.api.foo.bar/v1",
Description: "Development server",
},
{
URL: "https://prod.api.foo.bar/{basePath}",
Description: "Production server",
Variables: map[string]*ServerVariable{
"basePath": {
Description: "Version of the API",
Enum: []string{
"v1", "v2", "beta",
},
Default: "v2",
},
Default: "v2",
},
}},
}},
}
g.SetServers(servers)

Expand All @@ -689,27 +746,133 @@ func TestGenerator_parseExampleValue(t *testing.T) {
reflect.TypeOf("value"),
"value",
"value",
}, {
"mapping to int",
reflect.TypeOf(1),
"1",
int64(1),
}, {
},
{
"mapping pointer to string",
reflect.PtrTo(reflect.TypeOf("value")),
"value",
"value",
},
{
"mapping to int8",
reflect.TypeOf(int8(math.MaxInt8)),
"127",
int8(math.MaxInt8),
},
{
"mapping pointer to int8",
reflect.PtrTo(reflect.TypeOf(int8(math.MaxInt8))),
"127",
int8(math.MaxInt8),
},
{
"mapping to int16",
reflect.TypeOf(int16(math.MaxInt16)),
"32767",
int16(math.MaxInt16),
},
{
"mapping pointer to int16",
reflect.PtrTo(reflect.TypeOf(int16(math.MaxInt16))),
"32767",
int16(math.MaxInt16),
},
{
"mapping to int32",
reflect.TypeOf(int32(math.MaxInt32)),
"2147483647",
int32(math.MaxInt32),
},
{
"mapping pointer to int32",
reflect.PtrTo(reflect.TypeOf(int32(math.MaxInt32))),
"2147483647",
int32(math.MaxInt32),
},
{
"mapping to int64",
reflect.TypeOf(int64(math.MaxInt64)),
"9223372036854775807",
int64(math.MaxInt64),
},
{
"mapping pointer to int64",
reflect.PtrTo(reflect.TypeOf(int64(math.MaxInt64))),
"9223372036854775807",
int64(math.MaxInt64),
},
{
"mapping to uint8",
reflect.TypeOf(uint8(1)),
"1",
uint64(1),
}, {
reflect.TypeOf(uint8(math.MaxUint8)),
"255",
uint8(math.MaxUint8),
},
{
"mapping pointer to uint8",
reflect.PtrTo(reflect.TypeOf(uint8(math.MaxUint8))),
"255",
uint8(math.MaxUint8),
},
{
"mapping to uint16",
reflect.TypeOf(uint16(math.MaxUint16)),
"65535",
uint16(math.MaxUint16),
},
{
"mapping pointer to uint16",
reflect.PtrTo(reflect.TypeOf(uint16(math.MaxUint16))),
"65535",
uint16(math.MaxUint16),
},
{
"mapping to uint32",
reflect.TypeOf(uint32(math.MaxUint32)),
"4294967295",
uint32(math.MaxUint32),
},
{
"mapping pointer to uint32",
reflect.PtrTo(reflect.TypeOf(uint32(math.MaxUint32))),
"4294967295",
uint32(math.MaxUint32),
},
{
"mapping to uint64",
reflect.TypeOf(uint64(math.MaxUint64)),
"18446744073709551615",
uint64(math.MaxUint64),
},
{
"mapping pointer to uint64",
reflect.PtrTo(reflect.TypeOf(uint64(math.MaxUint64))),
"18446744073709551615",
uint64(math.MaxUint64),
},
{
"mapping to number",
reflect.TypeOf(1.23),
"1.23",
1.23,
}, {
},
{
"mapping pointer to number",
reflect.PtrTo(reflect.TypeOf(1.23)),
"1.23",
1.23,
},
{
"mapping to boolean",
reflect.TypeOf(true),
"true",
true,
},
{
"mapping pointer to boolean",
reflect.PtrTo(reflect.TypeOf(true)),
"true",
true,
},
}

for _, tc := range testCases {
Expand Down
10 changes: 5 additions & 5 deletions openapi/sort_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
// sort functions.
func TestSortParams(t *testing.T) {
params := []*ParameterOrRef{
&ParameterOrRef{Parameter: &Parameter{Name: "Paz", In: "path"}},
&ParameterOrRef{Parameter: &Parameter{Name: "Baz", In: "header"}},
&ParameterOrRef{Parameter: &Parameter{Name: "Bar", In: "query"}},
&ParameterOrRef{Parameter: &Parameter{Name: "Zap", In: "path"}},
&ParameterOrRef{Parameter: &Parameter{Name: "Foo", In: "query"}},
{Parameter: &Parameter{Name: "Paz", In: "path"}},
{Parameter: &Parameter{Name: "Baz", In: "header"}},
{Parameter: &Parameter{Name: "Bar", In: "query"}},
{Parameter: &Parameter{Name: "Zap", In: "path"}},
{Parameter: &Parameter{Name: "Foo", In: "query"}},
}
// Sort by location, then by name in ascending order.
paramsOrderedBy(byLocation, byName).Sort(params)
Expand Down
2 changes: 1 addition & 1 deletion openapi/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func DataTypeFromType(t reflect.Type) DataType {
}
// Dereference any pointer.
if t.Kind() == reflect.Ptr {
t = t.Elem()
return DataTypeFromType(t.Elem())
}
// Switch over Golang types.
switch t {
Expand Down

0 comments on commit d0b235e

Please sign in to comment.