Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ type Foo struct {
Field Name | Type | Description
---|:---:|---
<a name="validate"></a>validate | `string` | Determines the validation for the parameter. Possible values are: `required,optional`.
<a name="json"></a>json | `string` | JSON tag options. The `omitempty` option will mark the field as not required.
<a name="parameterDefault"></a>default | * | Declares the value of the parameter that the server will use if none is provided, for example a "count" to control the number of results per page might default to 100 if not supplied by the client in the request. (Note: "default" has no meaning for required parameters.) See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. Unlike JSON Schema this value MUST conform to the defined [`type`](#parameterType) for this parameter.
<a name="parameterMaximum"></a>maximum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2.
<a name="parameterMinimum"></a>minimum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3.
Expand Down
16 changes: 16 additions & 0 deletions field_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var _ FieldParser = (*tagBaseFieldParser)(nil)
const (
requiredLabel = "required"
optionalLabel = "optional"
omitEmptyLabel = "omitempty"
omitZeroLabel = "omitzero"
swaggerTypeTag = "swaggertype"
swaggerIgnoreTag = "swaggerignore"
)
Expand Down Expand Up @@ -541,6 +543,20 @@ func (ps *tagBaseFieldParser) IsRequired() (bool, error) {
}
}

jsonTag := ps.tag.Get(jsonTag)
if jsonTag != "" {
for _, val := range strings.Split(jsonTag, ",") {
if val == omitEmptyLabel || val == omitZeroLabel {
return false, nil
}
}
}

// Pointer types are inherently optional.
if _, ok := ps.field.Type.(*ast.StarExpr); ok {
return false, nil
}

return ps.p.RequiredByDefault, nil
}

Expand Down
55 changes: 55 additions & 0 deletions field_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,61 @@ func TestDefaultFieldParser(t *testing.T) {
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)

got, err = newTagBaseFieldParser(
&Parser{
RequiredByDefault: true,
},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test,omitempty"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)

got, err = newTagBaseFieldParser(
&Parser{
RequiredByDefault: true,
},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test,omitzero"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)
})

t.Run("Pointer type is optional", func(t *testing.T) {
t.Parallel()

got, err := newTagBaseFieldParser(
&Parser{
RequiredByDefault: true,
},
&ast.Field{
Type: &ast.StarExpr{X: &ast.Ident{Name: "string"}},
Tag: &ast.BasicLit{
Value: `json:"test"`,
},
},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)

// Explicit required tag should override pointer optionality
got, err = newTagBaseFieldParser(
&Parser{
RequiredByDefault: true,
},
&ast.Field{
Type: &ast.StarExpr{X: &ast.Ident{Name: "string"}},
Tag: &ast.BasicLit{
Value: `json:"test" validate:"required"`,
},
},
).IsRequired()
assert.NoError(t, err)
assert.True(t, got)
})

t.Run("Extensions tag", func(t *testing.T) {
Expand Down
59 changes: 59 additions & 0 deletions field_parser_v3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,65 @@ func TestDefaultFieldParserV3(t *testing.T) {
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)

got, err = newTagBaseFieldParserV3(
&Parser{
RequiredByDefault: true,
},
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test,omitempty"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)

got, err = newTagBaseFieldParserV3(
&Parser{
RequiredByDefault: true,
},
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.Field{Tag: &ast.BasicLit{
Value: `json:"test,omitzero"`,
}},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)
})

t.Run("Pointer type is optional", func(t *testing.T) {
t.Parallel()

got, err := newTagBaseFieldParserV3(
&Parser{
RequiredByDefault: true,
},
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.Field{
Type: &ast.StarExpr{X: &ast.Ident{Name: "string"}},
Tag: &ast.BasicLit{
Value: `json:"test"`,
},
},
).IsRequired()
assert.NoError(t, err)
assert.False(t, got)

// Explicit required tag should override pointer optionality
got, err = newTagBaseFieldParserV3(
&Parser{
RequiredByDefault: true,
},
&ast.File{Name: &ast.Ident{Name: "test"}},
&ast.Field{
Type: &ast.StarExpr{X: &ast.Ident{Name: "string"}},
Tag: &ast.BasicLit{
Value: `json:"test" validate:"required"`,
},
},
).IsRequired()
assert.NoError(t, err)
assert.True(t, got)
})

t.Run("Skipped tag", func(t *testing.T) {
Expand Down
31 changes: 25 additions & 6 deletions field_parserv3.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,13 +475,18 @@ func (ps *tagBaseFieldParserV3) ShouldSkip() bool {
return true
}

// json:"tag,hoge"
name := ps.JsonName()
if name == "" {
return true
}
// `json:"-"` explicitly excludes the field. Absence of a json tag falls
// through so FieldName() can pick up form/header tags or use the
// configured naming strategy on the Go field name.
return ps.isJsonIgnored()
}

return false
func (ps *tagBaseFieldParserV3) isJsonIgnored() bool {
if ps.field.Tag == nil {
return false
}
name := strings.TrimSpace(strings.Split(ps.tag.Get(jsonTag), ",")[0])
return name == "-"
}

func (ps *tagBaseFieldParserV3) FieldName() (string, error) {
Expand Down Expand Up @@ -562,5 +567,19 @@ func (ps *tagBaseFieldParserV3) IsRequired() (bool, error) {
}
}

jsonTag := ps.tag.Get(jsonTag)
if jsonTag != "" {
for _, val := range strings.Split(jsonTag, ",") {
if val == omitEmptyLabel || val == omitZeroLabel {
return false, nil
}
}
}

// Pointer types are inherently optional.
if _, ok := ps.field.Type.(*ast.StarExpr); ok {
return false, nil
}

return ps.p.RequiredByDefault, nil
}
5 changes: 2 additions & 3 deletions gen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,9 +540,8 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *v2.Swagg
func (g *Gen) writeGoDocV3(packageName string, output io.Writer, openAPI *v3.OpenAPI, config *Config) error {
generator, err := template.New("oas3.tmpl").Funcs(template.FuncMap{
"printDoc": func(v string) string {
// Add schemes
v = "{\n \"schemes\": " + config.LeftTemplateDelim + " marshal .Schemes " + config.RightTemplateDelim + "," + v[1:]
// Sanitize backticks
// schemes is a Swagger 2.0 field with no place in OpenAPI 3.1,
// so — unlike the v2 writer — don't inject it here.
return strings.Replace(v, "`", "`+\"`\"+`", -1)
},
}).ParseFS(tmpl, "src/*.tmpl")
Expand Down
3 changes: 1 addition & 2 deletions schemav3.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ func BuildCustomSchemaV3(types []string) (*spec.RefOrSpec[spec.Schema], error) {
return nil, err
}

// TODO: check if this is correct
result := spec.NewSchemaSpec()
result.Spec.Type = &spec.SingleOrArray[string]{ARRAY}
result.Spec.AdditionalProperties = spec.NewBoolOrSchema(true, schema)
result.Spec.Items = spec.NewBoolOrSchema(false, schema)

return result, nil
case OBJECT:
Expand Down