From a780e45b5ddbd82ef9f99b5109696adf0972f0b3 Mon Sep 17 00:00:00 2001 From: Andrey Lukin Date: Thu, 16 Jun 2022 13:51:40 +0400 Subject: [PATCH] feat: default required option for struct fields (#1181) --- README.md | 3 ++- cmd/swag/main.go | 40 +++++++++++++++++++++++----------------- field_parser.go | 13 ++++++++++--- field_parser_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ gen/gen.go | 4 ++++ parser.go | 3 +++ 6 files changed, 83 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 84239d117..065c93a76 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ OPTIONS: --codeExampleFiles value, --cef value Parse folder containing code example files to use for the x-codeSamples extension, disabled by default --parseInternal Parse go files in internal packages, disabled by default (default: false) --generatedTime Generate timestamp at the top of docs.go, disabled by default (default: false) + --requiredByDefault Set validation required for all fields by default (default: false) --parseDepth value Dependency parse depth (default: 100) --instanceName value This parameter can be used to name different swagger document instances. It is optional. --overridesFile value File to read global type overrides from. (default: ".swaggo") @@ -488,7 +489,7 @@ type Foo struct { Field Name | Type | Description ---|:---:|--- -validate | `string` | Determines the validation for the parameter. Possible values are: `required`. +validate | `string` | Determines the validation for the parameter. Possible values are: `required,optional`. 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. maximum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.2. minimum | `number` | See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.1.3. diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 0c689f626..b6805c786 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -15,23 +15,24 @@ import ( ) const ( - searchDirFlag = "dir" - excludeFlag = "exclude" - generalInfoFlag = "generalInfo" - propertyStrategyFlag = "propertyStrategy" - outputFlag = "output" - outputTypesFlag = "outputTypes" - parseVendorFlag = "parseVendor" - parseDependencyFlag = "parseDependency" - markdownFilesFlag = "markdownFiles" - codeExampleFilesFlag = "codeExampleFiles" - parseInternalFlag = "parseInternal" - generatedTimeFlag = "generatedTime" - parseDepthFlag = "parseDepth" - instanceNameFlag = "instanceName" - overridesFileFlag = "overridesFile" - parseGoListFlag = "parseGoList" - quietFlag = "quiet" + searchDirFlag = "dir" + excludeFlag = "exclude" + generalInfoFlag = "generalInfo" + propertyStrategyFlag = "propertyStrategy" + outputFlag = "output" + outputTypesFlag = "outputTypes" + parseVendorFlag = "parseVendor" + parseDependencyFlag = "parseDependency" + markdownFilesFlag = "markdownFiles" + codeExampleFilesFlag = "codeExampleFiles" + parseInternalFlag = "parseInternal" + generatedTimeFlag = "generatedTime" + requiredByDefaultFlag = "requiredByDefault" + parseDepthFlag = "parseDepth" + instanceNameFlag = "instanceName" + overridesFileFlag = "overridesFile" + parseGoListFlag = "parseGoList" + quietFlag = "quiet" ) var initFlags = []cli.Flag{ @@ -108,6 +109,10 @@ var initFlags = []cli.Flag{ Value: 100, Usage: "Dependency parse depth", }, + &cli.BoolFlag{ + Name: requiredByDefaultFlag, + Usage: "Set validation required for all fields by default", + }, &cli.StringFlag{ Name: instanceNameFlag, Value: "", @@ -155,6 +160,7 @@ func initAction(ctx *cli.Context) error { MarkdownFilesDir: ctx.String(markdownFilesFlag), ParseInternal: ctx.Bool(parseInternalFlag), GeneratedTime: ctx.Bool(generatedTimeFlag), + RequiredByDefault: ctx.Bool(requiredByDefaultFlag), CodeExampleFilesDir: ctx.String(codeExampleFilesFlag), ParseDepth: ctx.Int(parseDepthFlag), InstanceName: ctx.String(instanceNameFlag), diff --git a/field_parser.go b/field_parser.go index bd4fa4218..ce1ef4bf5 100644 --- a/field_parser.go +++ b/field_parser.go @@ -18,6 +18,7 @@ var _ FieldParser = &tagBaseFieldParser{p: nil, field: nil, tag: ""} const ( requiredLabel = "required" + optionalLabel = "optional" swaggerTypeTag = "swaggertype" swaggerIgnoreTag = "swaggerignore" ) @@ -472,8 +473,11 @@ func (ps *tagBaseFieldParser) IsRequired() (bool, error) { bindingTag := ps.tag.Get(bindingTag) if bindingTag != "" { for _, val := range strings.Split(bindingTag, ",") { - if val == requiredLabel { + switch val { + case requiredLabel: return true, nil + case optionalLabel: + return false, nil } } } @@ -481,13 +485,16 @@ func (ps *tagBaseFieldParser) IsRequired() (bool, error) { validateTag := ps.tag.Get(validateTag) if validateTag != "" { for _, val := range strings.Split(validateTag, ",") { - if val == requiredLabel { + switch val { + case requiredLabel: return true, nil + case optionalLabel: + return false, nil } } } - return false, nil + return ps.p.RequiredByDefault, nil } func parseValidTags(validTag string, sf *structField) { diff --git a/field_parser_test.go b/field_parser_test.go index aa89d6ac8..a7487c03d 100644 --- a/field_parser_test.go +++ b/field_parser_test.go @@ -82,6 +82,47 @@ func TestDefaultFieldParser(t *testing.T) { assert.Equal(t, true, got) }) + t.Run("Default required tag", func(t *testing.T) { + t.Parallel() + + got, err := newTagBaseFieldParser( + &Parser{ + RequiredByDefault: true, + }, + &ast.Field{Tag: &ast.BasicLit{ + Value: `json:"test"`, + }}, + ).IsRequired() + assert.NoError(t, err) + assert.True(t, got) + }) + + t.Run("Optional tag", func(t *testing.T) { + t.Parallel() + + got, err := newTagBaseFieldParser( + &Parser{ + RequiredByDefault: true, + }, + &ast.Field{Tag: &ast.BasicLit{ + Value: `json:"test" binding:"optional"`, + }}, + ).IsRequired() + assert.NoError(t, err) + assert.False(t, got) + + got, err = newTagBaseFieldParser( + &Parser{ + RequiredByDefault: true, + }, + &ast.Field{Tag: &ast.BasicLit{ + Value: `json:"test" validate:"optional"`, + }}, + ).IsRequired() + assert.NoError(t, err) + assert.False(t, got) + }) + t.Run("Extensions tag", func(t *testing.T) { t.Parallel() diff --git a/gen/gen.go b/gen/gen.go index ecdb9373d..b85290b4f 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -105,6 +105,9 @@ type Config struct { // GeneratedTime whether swag should generate the timestamp at the top of docs.go GeneratedTime bool + // RequiredByDefault set validation required for all fields by default + RequiredByDefault bool + // OverridesFile defines global type overrides. OverridesFile string @@ -159,6 +162,7 @@ func (g *Gen) Build(config *Config) error { p.ParseVendor = config.ParseVendor p.ParseDependency = config.ParseDependency p.ParseInternal = config.ParseInternal + p.RequiredByDefault = config.RequiredByDefault if err := p.ParseAPIMultiSearchDir(searchDirs, config.MainAPIFile, config.ParseDepth); err != nil { return err diff --git a/parser.go b/parser.go index fc4c23099..e284b594b 100644 --- a/parser.go +++ b/parser.go @@ -130,6 +130,9 @@ type Parser struct { // Strict whether swag should error or warn when it detects cases which are most likely user errors Strict bool + // RequiredByDefault set validation required for all fields by default + RequiredByDefault bool + // structStack stores full names of the structures that were already parsed or are being parsed now structStack []*TypeSpecDef