From a2424443de85e2a92a0fcaad434074fb22660ec5 Mon Sep 17 00:00:00 2001 From: Igor Kova Date: Thu, 2 Jun 2022 13:16:05 +0200 Subject: [PATCH 1/4] feat: parse only specific extension tag --- cmd/swag/main.go | 7 +++++++ gen/gen.go | 4 ++++ parser.go | 18 ++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 0c689f626..f84a91afc 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -32,6 +32,7 @@ const ( overridesFileFlag = "overridesFile" parseGoListFlag = "parseGoList" quietFlag = "quiet" + parseExtensionFlag = "parseExtension" ) var initFlags = []cli.Flag{ @@ -123,6 +124,11 @@ var initFlags = []cli.Flag{ Value: true, Usage: "Parse dependency via 'go list'", }, + &cli.StringFlag{ + Name: parseExtensionFlag, + Value: "", + Usage: "Parse only those operations that match given extension", + }, } func initAction(ctx *cli.Context) error { @@ -146,6 +152,7 @@ func initAction(ctx *cli.Context) error { return gen.New().Build(&gen.Config{ SearchDir: ctx.String(searchDirFlag), Excludes: ctx.String(excludeFlag), + ParseExtension: ctx.String(parseExtensionFlag), MainAPIFile: ctx.String(generalInfoFlag), PropNamingStrategy: strategy, OutputDir: ctx.String(outputFlag), diff --git a/gen/gen.go b/gen/gen.go index ecdb9373d..3afbc2b6c 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -65,6 +65,9 @@ type Config struct { // excludes dirs and files in SearchDir,comma separated Excludes string + // outputs only those operations with "x-public: true" extension + ParseExtension string + // OutputDir represents the output directory for all the generated files OutputDir string @@ -149,6 +152,7 @@ func (g *Gen) Build(config *Config) error { p := swag.New(swag.SetMarkdownFileDirectory(config.MarkdownFilesDir), swag.SetDebugger(config.Debugger), swag.SetExcludedDirsAndFiles(config.Excludes), + swag.SetParseExtension(config.ParseExtension), swag.SetCodeExamplesDirectory(config.CodeExampleFilesDir), swag.SetStrict(config.Strict), swag.SetOverrides(overrides), diff --git a/parser.go b/parser.go index 815b2f8c6..79966a302 100644 --- a/parser.go +++ b/parser.go @@ -145,6 +145,9 @@ type Parser struct { // excludes excludes dirs and files in SearchDir excludes map[string]struct{} + // tells parser to include operations with "x-public: true" extension + parseExtension string + // debugging output goes here debug Debugger @@ -247,6 +250,13 @@ func SetExcludedDirsAndFiles(excludes string) func(*Parser) { } } +// SetParseExtension parses only those operations which match given extension +func SetParseExtension(parseExtension string) func(*Parser) { + return func(p *Parser) { + p.parseExtension = parseExtension + } +} + // SetStrict sets whether swag should error or warn when it detects cases which are most likely user errors. func SetStrict(strict bool) func(*Parser) { return func(p *Parser) { @@ -831,6 +841,14 @@ func processRouterOperation(parser *Parser, operation *Operation) error { pathItem = spec.PathItem{} } + // enable filtering of public endpoints + if len(parser.parseExtension) > 0 { + if val, ok := operation.Extensions[fmt.Sprintf("x-%s", parser.parseExtension)]; !ok || val == false { + parser.debug.Printf("skipping operation %s %s as it's not matching extension %s\n", routeProperties.HTTPMethod, routeProperties.Path, parser.parseExtension) + continue + } + } + op := refRouteMethodOp(&pathItem, routeProperties.HTTPMethod) // check if we already have an operation for this path and method From 143e0bb88292489836de406ea79bf5062e4c58c6 Mon Sep 17 00:00:00 2001 From: Igor Kova Date: Wed, 7 Dec 2022 14:02:20 +0100 Subject: [PATCH 2/4] chore: add tests --- gen/gen.go | 2 +- parser.go | 2 +- parser_test.go | 48 +++++++++++++++++++++++ testdata/parseExtension/parseExtension.go | 12 ++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 testdata/parseExtension/parseExtension.go diff --git a/gen/gen.go b/gen/gen.go index d74202623..ddd02f751 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -72,7 +72,7 @@ type Config struct { // excludes dirs and files in SearchDir,comma separated Excludes string - // outputs only those operations with "x-public: true" extension + // outputs only specific extension ParseExtension string // OutputDir represents the output directory for all the generated files diff --git a/parser.go b/parser.go index ed2294d08..dfb0ca82b 100644 --- a/parser.go +++ b/parser.go @@ -137,7 +137,7 @@ type Parser struct { // excludes excludes dirs and files in SearchDir excludes map[string]struct{} - // tells parser to include operations with "x-public: true" extension + // tells parser to include only specific extension parseExtension string // debugging output goes here diff --git a/parser_test.go b/parser_test.go index c7e5bacb1..0876b94e9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3878,3 +3878,51 @@ func TestParser_matchTags(t *testing.T) { }) } } + +func TestParser_parseExtension(t *testing.T) { + + src, err := os.ReadFile("testdata/parseExtension/parseExtension.go") + assert.NoError(t, err) + + f, err := goparser.ParseFile(token.NewFileSet(), "", src, goparser.ParseComments) + assert.NoError(t, err) + + tests := []struct { + name string + parser *Parser + expectedPaths map[string]bool + }{ + { + name: "when no flag is set, everything is exported", + parser: New(), + expectedPaths: map[string]bool{"/without-extension": true, "/with-another-extension": true, "/with-correct-extension": true}, + }, + { + name: "when nonexistent flag is set, nothing is exported", + parser: New(SetParseExtension("nonexistent-extension-filter")), + expectedPaths: map[string]bool{"/without-extension": false, "/with-another-extension": false, "/with-correct-extension": false}, + }, + { + name: "when correct flag is set, only that Path is exported", + parser: New(SetParseExtension("google-backend")), + expectedPaths: map[string]bool{"/without-extension": false, "/with-another-extension": false, "/with-correct-extension": true}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err = tt.parser.ParseRouterAPIInfo("", f) + assert.NoError(t, err) + for p, isExpected := range tt.expectedPaths { + _, ok := tt.parser.swagger.Paths.Paths[p] + assert.Equal(t, isExpected, ok) + } + + for p := range tt.parser.swagger.Paths.Paths { + _, isExpected := tt.expectedPaths[p] + assert.Equal(t, isExpected, true) + } + }) + + } +} diff --git a/testdata/parseExtension/parseExtension.go b/testdata/parseExtension/parseExtension.go new file mode 100644 index 000000000..9948c7a25 --- /dev/null +++ b/testdata/parseExtension/parseExtension.go @@ -0,0 +1,12 @@ +package main + +// @Router /without-extension [get] +func Fun() {} + +// @Router /with-another-extension [get] +// @x-another-extension {"address": "http://backend"} +func Fun2() {} + +// @Router /with-correct-extension [get] +// @x-google-backend {"address": "http://backend"} +func Fun3() {} From 588961a19ed739e3328573537f65e448f087271e Mon Sep 17 00:00:00 2001 From: Igor Kova Date: Sat, 10 Dec 2022 12:05:15 +0100 Subject: [PATCH 3/4] fix: Exclude operation before it's parsed Previous implementation had an issue where it was excluding operation after schema and other parts were parsed. That caused schema definition to be included in the output file, even though operation didn't match the extension. New implementation won't even start processing operation if extension isn't matching. Only potential issue/problem I see with this approach is that we are duplicating logic for comment line parsing (I basically c/p it from other places) but not sure how big of an issue that actually is as I noticed we are doing that at other places as well. --- parser.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/parser.go b/parser.go index dfb0ca82b..16f3752da 100644 --- a/parser.go +++ b/parser.go @@ -838,12 +838,29 @@ func (parser *Parser) matchTags(comments []*ast.Comment) (match bool) { return true } +func (parser *Parser) matchExtension(comments []*ast.Comment) (match bool) { + if len(parser.parseExtension) != 0 { + for _, comment := range comments { + commentLine := strings.TrimSpace(strings.TrimLeft(comment.Text, "/")) + fields := FieldsByAnySpace(commentLine, 2) + lowerAttribute := strings.ToLower(fields[0]) + + if lowerAttribute == fmt.Sprintf("@x-%s", strings.ToLower(parser.parseExtension)) { + return true + } + } + return false + } + return true +} + // ParseRouterAPIInfo parses router api info for given astFile. func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) error { for _, astDescription := range astFile.Decls { astDeclaration, ok := astDescription.(*ast.FuncDecl) if ok && astDeclaration.Doc != nil && astDeclaration.Doc.List != nil { - if parser.matchTags(astDeclaration.Doc.List) { + if parser.matchTags(astDeclaration.Doc.List) && + parser.matchExtension(astDeclaration.Doc.List) { // for per 'function' comment, create a new 'Operation' object operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir)) for _, comment := range astDeclaration.Doc.List { @@ -896,14 +913,6 @@ func processRouterOperation(parser *Parser, operation *Operation) error { pathItem = spec.PathItem{} } - // enable filtering of public endpoints - if len(parser.parseExtension) > 0 { - if val, ok := operation.Extensions[fmt.Sprintf("x-%s", parser.parseExtension)]; !ok || val == false { - parser.debug.Printf("skipping operation %s %s as it's not matching extension %s\n", routeProperties.HTTPMethod, routeProperties.Path, parser.parseExtension) - continue - } - } - op := refRouteMethodOp(&pathItem, routeProperties.HTTPMethod) // check if we already have an operation for this path and method From 4d331184475573c41edb4b9c2e72ec52ee9c5d09 Mon Sep 17 00:00:00 2001 From: Igor Kova Date: Sun, 11 Dec 2022 12:37:18 +0100 Subject: [PATCH 4/4] fix: detaching matchExtension from parser as per PR review suggestions --- parser.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parser.go b/parser.go index 16f3752da..261ba6c55 100644 --- a/parser.go +++ b/parser.go @@ -838,14 +838,14 @@ func (parser *Parser) matchTags(comments []*ast.Comment) (match bool) { return true } -func (parser *Parser) matchExtension(comments []*ast.Comment) (match bool) { - if len(parser.parseExtension) != 0 { +func matchExtension(extensionToMatch string, comments []*ast.Comment) (match bool) { + if len(extensionToMatch) != 0 { for _, comment := range comments { commentLine := strings.TrimSpace(strings.TrimLeft(comment.Text, "/")) fields := FieldsByAnySpace(commentLine, 2) lowerAttribute := strings.ToLower(fields[0]) - if lowerAttribute == fmt.Sprintf("@x-%s", strings.ToLower(parser.parseExtension)) { + if lowerAttribute == fmt.Sprintf("@x-%s", strings.ToLower(extensionToMatch)) { return true } } @@ -860,7 +860,7 @@ func (parser *Parser) ParseRouterAPIInfo(fileName string, astFile *ast.File) err astDeclaration, ok := astDescription.(*ast.FuncDecl) if ok && astDeclaration.Doc != nil && astDeclaration.Doc.List != nil { if parser.matchTags(astDeclaration.Doc.List) && - parser.matchExtension(astDeclaration.Doc.List) { + matchExtension(parser.parseExtension, astDeclaration.Doc.List) { // for per 'function' comment, create a new 'Operation' object operation := NewOperation(parser, SetCodeExampleFilesDirectory(parser.codeExampleFilesDir)) for _, comment := range astDeclaration.Doc.List {