From 806a3993793929cb5d0a1ef4b2f5344a5f12175d Mon Sep 17 00:00:00 2001 From: Ian Fox Date: Fri, 26 Nov 2021 16:14:27 +0100 Subject: [PATCH 1/5] feat: support global type overrides --- README.md | 28 +++++++ cmd/swag/main.go | 7 ++ gen/gen.go | 27 ++++++- go.mod | 7 +- go.sum | 29 +++++++ parser.go | 25 ++++++ parser_test.go | 30 ++++++++ testdata/global_override/api/api.go | 29 +++++++ .../data/applicationresponse.go | 14 ++++ testdata/global_override/expected.json | 76 +++++++++++++++++++ testdata/global_override/main.go | 26 +++++++ .../global_override/othertypes/application.go | 5 ++ testdata/global_override/types/application.go | 17 +++++ types.go | 5 ++ 14 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 testdata/global_override/api/api.go create mode 100644 testdata/global_override/data/applicationresponse.go create mode 100644 testdata/global_override/expected.json create mode 100644 testdata/global_override/main.go create mode 100644 testdata/global_override/othertypes/application.go create mode 100644 testdata/global_override/types/application.go diff --git a/README.md b/README.md index cad48effd..903587ec9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie - [Example value of struct](#example-value-of-struct) - [Description of struct](#description-of-struct) - [Use swaggertype tag to supported custom type](#use-swaggertype-tag-to-supported-custom-type) + - [Use global overrides to support a custom type](#use-global-overrides-to-support-a-custom-type) - [Use swaggerignore tag to exclude a field](#use-swaggerignore-tag-to-exclude-a-field) - [Add extension info to struct field](#add-extension-info-to-struct-field) - [Rename model to display](#rename-model-to-display) @@ -683,6 +684,33 @@ generated swagger doc as follows: ``` +### Use global overrides to support a custom type + +If you are using generated files, the [`swaggertype`](#use-swaggertype-tag-to-supported-custom-type) tag may not be possible. + +By passing a mapping to swag with `--overridesFile` you can tell swag to use one type in place of another wherever it appears. + +Go code: +```go +type MyStruct struct { + ID sql.NullInt64 `json:"id"` +} +``` + +`overrides.yaml`: +```yaml +"database/sql.NullInt64": "int" +``` + +(Note that the full paths to any named types must be provided to prevent problems when multiple packages define a type with the same name) + +Rendered (after running swag with `--overridesFile overrides.yaml`): +```go +"types.MyStruct": { + "id": "integer" +} +``` + ### Use swaggerignore tag to exclude a field diff --git a/cmd/swag/main.go b/cmd/swag/main.go index bc29bd404..1d2331dc5 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -26,6 +26,7 @@ const ( generatedTimeFlag = "generatedTime" parseDepthFlag = "parseDepth" instanceNameFlag = "instanceName" + overridesFileFlag = "overridesFile" ) var initFlags = []cli.Flag{ @@ -96,6 +97,11 @@ var initFlags = []cli.Flag{ Value: "", Usage: "This parameter can be used to name different swagger document instances. It is optional.", }, + &cli.StringFlag{ + Name: overridesFileFlag, + Value: "", + Usage: "File to read global type overrides from.", + }, } func initAction(c *cli.Context) error { @@ -121,6 +127,7 @@ func initAction(c *cli.Context) error { CodeExampleFilesDir: c.String(codeExampleFilesFlag), ParseDepth: c.Int(parseDepthFlag), InstanceName: c.String(instanceNameFlag), + OverridesFile: c.String(overridesFileFlag), }) } diff --git a/gen/gen.go b/gen/gen.go index cbcb9403a..99b4f05ec 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -78,6 +78,9 @@ type Config struct { // GeneratedTime whether swag should generate the timestamp at the top of docs.go GeneratedTime bool + + // OverridesFile defines global type overrides. + OverridesFile string } // Build builds swagger json file for given searchDir and mainAPIFile. Returns json @@ -93,11 +96,33 @@ func (g *Gen) Build(config *Config) error { } } + overrides := make(map[string]string) + if config.OverridesFile != "" { + f, err := os.Open(config.OverridesFile) + if err != nil { + return fmt.Errorf("file %s cannot be opened: %s", config.OverridesFile, err) + } + + contents, err := io.ReadAll(f) + if err != nil { + return fmt.Errorf("file %s cannot be read: %s", config.OverridesFile, err) + } + + err = yaml.Unmarshal(contents, &overrides) + if err != nil { + return fmt.Errorf("could not parse overrides file: %s", err) + } + + log.Printf("Using overrides from %s...", config.OverridesFile) + } + log.Println("Generate swagger docs....") p := swag.New(swag.SetMarkdownFileDirectory(config.MarkdownFilesDir), swag.SetExcludedDirsAndFiles(config.Excludes), swag.SetCodeExamplesDirectory(config.CodeExampleFilesDir), - swag.SetStrict(config.Strict)) + swag.SetStrict(config.Strict), + swag.SetOverrides(overrides), + ) p.PropNamingStrategy = config.PropNamingStrategy p.ParseVendor = config.ParseVendor p.ParseDependency = config.ParseDependency diff --git a/go.mod b/go.mod index 5075efb70..93da08f98 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,14 @@ require ( github.com/KyleBanks/depth v1.2.1 github.com/agiledragon/gomonkey/v2 v2.3.1 github.com/ghodss/yaml v1.0.0 - github.com/go-openapi/spec v0.20.3 + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 github.com/otiai10/copy v1.7.0 github.com/stretchr/testify v1.7.0 + github.com/swaggo/cli v1.22.2 // indirect github.com/urfave/cli/v2 v2.3.0 - golang.org/x/tools v0.1.0 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect + golang.org/x/tools v0.1.7 ) go 1.13 diff --git a/go.sum b/go.sum index f62bc9373..07be13628 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -20,11 +22,17 @@ github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUe github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ= github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -50,6 +58,8 @@ github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -61,14 +71,18 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/swaggo/cli v1.22.2 h1:HcOuWl50wxecZWnAA3eIrf2XcOki3XeRK7HljCzP9Vg= +github.com/swaggo/cli v1.22.2/go.mod h1:mod7cSpILRjdhkgSKDd1HJFDMN4hopy6uH5pkXELHkM= github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -76,24 +90,39 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/parser.go b/parser.go index 411e13c5e..34264806a 100644 --- a/parser.go +++ b/parser.go @@ -119,6 +119,9 @@ type Parser struct { // fieldParserFactory create FieldParser fieldParserFactory FieldParserFactory + + // Overrides allows global replacements of types + Overrides map[string]string } // FieldParserFactory create FieldParser @@ -169,6 +172,7 @@ func New(options ...func(*Parser)) *Parser { toBeRenamedSchemas: make(map[string]string), excludes: make(map[string]bool), fieldParserFactory: newTagBaseFieldParser, + Overrides: make(map[string]string), } for _, option := range options { @@ -226,6 +230,15 @@ func SetFieldParserFactory(factory FieldParserFactory) func(parser *Parser) { } } +// SetOverrides allows the use of user-defined global type overrides. +func SetOverrides(overrides map[string]string) func(parser *Parser) { + return func(p *Parser) { + for k, v := range overrides { + p.Overrides[k] = v + } + } +} + // ParseAPI parses general api info for given searchDir and mainAPIFile. func (parser *Parser) ParseAPI(searchDir string, mainAPIFile string, parseDepth int) error { return parser.ParseAPIMultiSearchDir([]string{searchDir}, mainAPIFile, parseDepth) @@ -778,6 +791,18 @@ func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) ( return nil, fmt.Errorf("cannot find type definition: %s", typeName) } + if override, ok := parser.Overrides[typeSpecDef.FullPath()]; ok { + parser.debug.Printf("Override detected for %s, using %s instead", typeSpecDef.FullPath(), override) + separator := strings.LastIndex(override, ".") + if separator == -1 { + // treat as a swaggertype tag + parts := strings.Split(override, ",") + return BuildCustomSchema(parts) + } + + typeSpecDef = parser.packages.findTypeSpec(override[0:separator], override[separator+1:]) + } + schema, ok := parser.parsedSchemas[typeSpecDef] if !ok { var err error diff --git a/parser_test.go b/parser_test.go index d9fd55667..d3d63c8ea 100644 --- a/parser_test.go +++ b/parser_test.go @@ -63,6 +63,17 @@ func TestNew(t *testing.T) { }) } +func TestSetOverrides(t *testing.T) { + t.Parallel() + + overrides := map[string]string{ + "foo": "bar", + } + + p := New(SetOverrides(overrides)) + assert.Equal(t, overrides, p.Overrides) +} + func TestParser_ParseDefinition(t *testing.T) { p := New() @@ -2032,6 +2043,25 @@ func TestParseImportAliases(t *testing.T) { assert.Equal(t, string(expected), string(b)) } +func TestParseTypeOverrides(t *testing.T) { + t.Parallel() + + searchDir := "testdata/global_override" + p := New(SetOverrides(map[string]string{ + "github.com/swaggo/swag/testdata/global_override/types.Application": "string", + "github.com/swaggo/swag/testdata/global_override/types.Application2": "github.com/swaggo/swag/testdata/global_override/othertypes.Application", + })) + err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) + assert.NoError(t, err) + + expected, err := ioutil.ReadFile(filepath.Join(searchDir, "expected.json")) + assert.NoError(t, err) + + b, _ := json.MarshalIndent(p.swagger, "", " ") + //windows will fail: \r\n \n + assert.Equal(t, string(expected), string(b)) +} + func TestParseNested(t *testing.T) { t.Parallel() diff --git a/testdata/global_override/api/api.go b/testdata/global_override/api/api.go new file mode 100644 index 000000000..c619dfd1b --- /dev/null +++ b/testdata/global_override/api/api.go @@ -0,0 +1,29 @@ +package api + +import ( + "log" + "net/http" + + "github.com/swaggo/swag/testdata/alias_type/types" + "github.com/swaggo/swag/testdata/global_override/data" +) + +// @Summary Get application +// @Description test get application +// @ID get-application +// @Accept json +// @Produce json +// @Success 200 {object} data.ApplicationResponse "ok" +// @Router /testapi/application [get] +func GetApplication(w http.ResponseWriter, r *http.Request) { + var foo = data.ApplicationResponse{ + Application: types.Application{ + Name: "name", + }, + ApplicationArray: []types.Application{ + {Name: "name"}, + }, + } + log.Println(foo) + //write your code +} diff --git a/testdata/global_override/data/applicationresponse.go b/testdata/global_override/data/applicationresponse.go new file mode 100644 index 000000000..6dc51f765 --- /dev/null +++ b/testdata/global_override/data/applicationresponse.go @@ -0,0 +1,14 @@ +package data + +import ( + typesapplication "github.com/swaggo/swag/testdata/global_override/types" +) + +type ApplicationResponse struct { + typesapplication.TypeToEmbed + + Application typesapplication.Application `json:"application"` + Application2 typesapplication.Application2 `json:"application2"` + ApplicationArray []typesapplication.Application `json:"application_array"` + ApplicationTime typesapplication.DateOnly `json:"application_time"` +} diff --git a/testdata/global_override/expected.json b/testdata/global_override/expected.json new file mode 100644 index 000000000..74d373eb8 --- /dev/null +++ b/testdata/global_override/expected.json @@ -0,0 +1,76 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server.", + "title": "Swagger Example API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "paths": { + "/testapi/application": { + "get": { + "description": "test get application", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Get application", + "operationId": "get-application", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/data.ApplicationResponse" + } + } + } + } + } + }, + "definitions": { + "data.ApplicationResponse": { + "type": "object", + "properties": { + "application": { + "type": "string" + }, + "application2": { + "$ref": "#/definitions/othertypes.Application" + }, + "application_array": { + "type": "array", + "items": { + "type": "string" + } + }, + "application_time": { + "type": "string" + }, + "embedded": { + "type": "string" + } + } + }, + "othertypes.Application": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + } + } +} \ No newline at end of file diff --git a/testdata/global_override/main.go b/testdata/global_override/main.go new file mode 100644 index 000000000..c77a6c6ca --- /dev/null +++ b/testdata/global_override/main.go @@ -0,0 +1,26 @@ +package global_override + +import ( + "net/http" + + "github.com/swaggo/swag/testdata/global_override/api" +) + +// @title Swagger Example API +// @version 1.0 +// @description This is a sample server Petstore server. +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.url http://www.swagger.io/support +// @contact.email support@swagger.io + +// @license.name Apache 2.0 +// @license.url http://www.apache.org/licenses/LICENSE-2.0.html + +// @host petstore.swagger.io +// @BasePath /v2 +func main() { + http.HandleFunc("/testapi/application", api.GetApplication) + http.ListenAndServe(":8080", nil) +} diff --git a/testdata/global_override/othertypes/application.go b/testdata/global_override/othertypes/application.go new file mode 100644 index 000000000..24b374211 --- /dev/null +++ b/testdata/global_override/othertypes/application.go @@ -0,0 +1,5 @@ +package othertypes + +type Application struct { + ID int +} diff --git a/testdata/global_override/types/application.go b/testdata/global_override/types/application.go new file mode 100644 index 000000000..e9572e0b3 --- /dev/null +++ b/testdata/global_override/types/application.go @@ -0,0 +1,17 @@ +package types + +import "time" + +type Application struct { + Name string +} + +type Application2 struct { + Name string +} + +type DateOnly time.Time + +type TypeToEmbed struct { + Embedded string +} diff --git a/types.go b/types.go index f65a62e07..8a4f8a21c 100644 --- a/types.go +++ b/types.go @@ -35,6 +35,11 @@ func (t *TypeSpecDef) FullName() string { return fullTypeName(t.File.Name.Name, t.TypeSpec.Name.Name) } +// FullPath of the typeSpec. +func (t *TypeSpecDef) FullPath() string { + return t.PkgPath + "." + t.Name() +} + // AstFileInfo information of an ast.File. type AstFileInfo struct { // File ast.File From 0e1d68d94b5e2e6f30236543f48738c00f334349 Mon Sep 17 00:00:00 2001 From: Ian Fox Date: Mon, 29 Nov 2021 10:51:19 +0100 Subject: [PATCH 2/5] feat: allow skipping fields with global overrides --- cmd/swag/main.go | 2 +- gen/gen.go | 61 ++++++++++++++----- gen/gen_test.go | 58 ++++++++++++++++++ parser.go | 15 ++++- parser_test.go | 1 + .../data/applicationresponse.go | 1 + testdata/global_override/types/application.go | 4 ++ 7 files changed, 122 insertions(+), 20 deletions(-) diff --git a/cmd/swag/main.go b/cmd/swag/main.go index 1d2331dc5..229894d04 100644 --- a/cmd/swag/main.go +++ b/cmd/swag/main.go @@ -99,7 +99,7 @@ var initFlags = []cli.Flag{ }, &cli.StringFlag{ Name: overridesFileFlag, - Value: "", + Value: gen.DefaultOverridesFile, Usage: "File to read global type overrides from.", }, } diff --git a/gen/gen.go b/gen/gen.go index 99b4f05ec..6e8b073d3 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -1,6 +1,7 @@ package gen import ( + "bufio" "bytes" "encoding/json" "fmt" @@ -9,6 +10,7 @@ import ( "log" "os" "path/filepath" + "regexp" "strings" "text/template" "time" @@ -18,6 +20,9 @@ import ( "github.com/swaggo/swag" ) +// DefaultOverridesFile is the location swaggo will look for type overrides. +const DefaultOverridesFile = ".swaggo" + // Gen presents a generate tool for swag. type Gen struct { jsonIndent func(data interface{}) ([]byte, error) @@ -96,24 +101,20 @@ func (g *Gen) Build(config *Config) error { } } - overrides := make(map[string]string) + var overrides map[string]string if config.OverridesFile != "" { - f, err := os.Open(config.OverridesFile) - if err != nil { - return fmt.Errorf("file %s cannot be opened: %s", config.OverridesFile, err) - } - - contents, err := io.ReadAll(f) - if err != nil { - return fmt.Errorf("file %s cannot be read: %s", config.OverridesFile, err) + overridesFile, err := os.Open(config.OverridesFile) + if err == nil { + log.Printf("Using overrides from %s", config.OverridesFile) + + overrides, err = parseOverrides(overridesFile) + if err != nil { + return err + } + } else if !(config.OverridesFile == DefaultOverridesFile && os.IsNotExist(err)) { + // Don't bother reporting if the default file is missing; assume there are no overrides + return fmt.Errorf("could not open overrides file: %w", err) } - - err = yaml.Unmarshal(contents, &overrides) - if err != nil { - return fmt.Errorf("could not parse overrides file: %s", err) - } - - log.Printf("Using overrides from %s...", config.OverridesFile) } log.Println("Generate swagger docs....") @@ -204,6 +205,34 @@ func (g *Gen) formatSource(src []byte) []byte { return code } +// Read the swaggo overrides +func parseOverrides(r io.Reader) (map[string]string, error) { + overrides := make(map[string]string) + scanner := bufio.NewScanner(r) + overridesReplace := regexp.MustCompile(`replace\s+(\S+)\s+(\S+)`) + overridesSkip := regexp.MustCompile(`skip\s+(\S+)`) + overridesComment := regexp.MustCompile(`^//.*`) + nonWhitespace := regexp.MustCompile(`\S`) + + for scanner.Scan() { + line := scanner.Text() + + if parts := overridesReplace.FindStringSubmatch(line); parts != nil { + overrides[parts[1]] = parts[2] // parts[0] is the full line + } else if parts := overridesSkip.FindStringSubmatch(line); parts != nil { + overrides[parts[1]] = "" // parts[0] is the full line + } else if nonWhitespace.MatchString(line) && !overridesComment.MatchString(line) { + return nil, fmt.Errorf("could not parse override: '%s'", line) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading overrides file: %w", err) + } + + return overrides, nil +} + func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *spec.Swagger, config *Config) error { generator, err := template.New("swagger_info").Funcs(template.FuncMap{ "printDoc": func(v string) string { diff --git a/gen/gen_test.go b/gen/gen_test.go index f9d623dba..dd9267cdc 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -3,6 +3,7 @@ package gen import ( "encoding/json" "errors" + "fmt" "io/ioutil" "os" "os/exec" @@ -463,3 +464,60 @@ func TestGen_duplicateRoute(t *testing.T) { err = New().Build(config) assert.EqualError(t, err, "route GET /testapi/endpoint is declared multiple times") } + +func TestGen_parseOverrides(t *testing.T) { + testCases := []struct { + Name string + Data string + Expected map[string]string + ExpectedError error + }{ + { + Name: "replace", + Data: `replace github.com/foo/bar baz`, + Expected: map[string]string{ + "github.com/foo/bar": "baz", + }, + }, + { + Name: "skip", + Data: `skip github.com/foo/bar`, + Expected: map[string]string{ + "github.com/foo/bar": "", + }, + }, + { + Name: "comment", + Data: `// this is a comment + replace foo bar`, + Expected: map[string]string{ + "foo": "bar", + }, + }, + { + Name: "ignore whitespace", + Data: ` + + replace foo bar`, + Expected: map[string]string{ + "foo": "bar", + }, + }, + { + Name: "unknown directive", + Data: `foo`, + ExpectedError: fmt.Errorf("could not parse override: 'foo'"), + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + t.Parallel() + + overrides, err := parseOverrides(strings.NewReader(tc.Data)) + assert.Equal(t, tc.Expected, overrides) + assert.Equal(t, tc.ExpectedError, err) + }) + } +} diff --git a/parser.go b/parser.go index 34264806a..281a195bd 100644 --- a/parser.go +++ b/parser.go @@ -49,6 +49,9 @@ var ( // ErrFailedConvertPrimitiveType Failed to convert for swag to interpretable type. ErrFailedConvertPrimitiveType = errors.New("swag property: failed convert primitive type") + + // ErrSkippedField .swaggo specifies field should be skipped + ErrSkippedField = errors.New("field is skipped by global overrides") ) var allMethod = map[string]struct{}{ @@ -120,7 +123,7 @@ type Parser struct { // fieldParserFactory create FieldParser fieldParserFactory FieldParserFactory - // Overrides allows global replacements of types + // Overrides allows global replacements of types. A blank replacement will be skipped. Overrides map[string]string } @@ -792,7 +795,13 @@ func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) ( } if override, ok := parser.Overrides[typeSpecDef.FullPath()]; ok { - parser.debug.Printf("Override detected for %s, using %s instead", typeSpecDef.FullPath(), override) + if override == "" { + parser.debug.Printf("Override detected for %s: ignoring", typeSpecDef.FullPath()) + return nil, ErrSkippedField + } + + parser.debug.Printf("Override detected for %s: using %s instead", typeSpecDef.FullPath(), override) + separator := strings.LastIndex(override, ".") if separator == -1 { // treat as a swaggertype tag @@ -1015,7 +1024,7 @@ func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec. for _, field := range fields.List { fieldProps, requiredFromAnon, err := parser.parseStructField(file, field) if err != nil { - if err == ErrFuncTypeField { + if err == ErrFuncTypeField || err == ErrSkippedField { continue } diff --git a/parser_test.go b/parser_test.go index d3d63c8ea..1506acb19 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2050,6 +2050,7 @@ func TestParseTypeOverrides(t *testing.T) { p := New(SetOverrides(map[string]string{ "github.com/swaggo/swag/testdata/global_override/types.Application": "string", "github.com/swaggo/swag/testdata/global_override/types.Application2": "github.com/swaggo/swag/testdata/global_override/othertypes.Application", + "github.com/swaggo/swag/testdata/global_override/types.ShouldSkip": "", })) err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth) assert.NoError(t, err) diff --git a/testdata/global_override/data/applicationresponse.go b/testdata/global_override/data/applicationresponse.go index 6dc51f765..097e80364 100644 --- a/testdata/global_override/data/applicationresponse.go +++ b/testdata/global_override/data/applicationresponse.go @@ -11,4 +11,5 @@ type ApplicationResponse struct { Application2 typesapplication.Application2 `json:"application2"` ApplicationArray []typesapplication.Application `json:"application_array"` ApplicationTime typesapplication.DateOnly `json:"application_time"` + ShouldSkip typesapplication.ShouldSkip `json:"should_skip"` } diff --git a/testdata/global_override/types/application.go b/testdata/global_override/types/application.go index e9572e0b3..0a47b75bb 100644 --- a/testdata/global_override/types/application.go +++ b/testdata/global_override/types/application.go @@ -15,3 +15,7 @@ type DateOnly time.Time type TypeToEmbed struct { Embedded string } + +type ShouldSkip struct { + Name string +} From a5609c5d4080f71afdefb2f5f6604b94ac06065a Mon Sep 17 00:00:00 2001 From: Ian Fox Date: Mon, 29 Nov 2021 15:22:06 +0100 Subject: [PATCH 3/5] fix: avoid panic on calling Name() for empty typespec --- types.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/types.go b/types.go index 8a4f8a21c..82ddbbebb 100644 --- a/types.go +++ b/types.go @@ -27,7 +27,11 @@ type TypeSpecDef struct { // Name the name of the typeSpec. func (t *TypeSpecDef) Name() string { - return t.TypeSpec.Name.Name + if t.TypeSpec != nil { + return t.TypeSpec.Name.Name + } + + return "" } // FullName full name of the typeSpec. From 73c04866a79ba84b45bc5ecdcc23b637ae165d7a Mon Sep 17 00:00:00 2001 From: Ian Fox Date: Tue, 30 Nov 2021 10:17:07 +0100 Subject: [PATCH 4/5] address review comments --- README.md | 19 +++++++++++++------ gen/gen.go | 44 ++++++++++++++++++++++++++++++-------------- gen/gen_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 903587ec9..d0e7e31b9 100644 --- a/README.md +++ b/README.md @@ -686,25 +686,32 @@ generated swagger doc as follows: ### Use global overrides to support a custom type -If you are using generated files, the [`swaggertype`](#use-swaggertype-tag-to-supported-custom-type) tag may not be possible. +If you are using generated files, the [`swaggertype`](#use-swaggertype-tag-to-supported-custom-type) or `swaggerignore` tags may not be possible. -By passing a mapping to swag with `--overridesFile` you can tell swag to use one type in place of another wherever it appears. +By passing a mapping to swag with `--overridesFile` you can tell swag to use one type in place of another wherever it appears. By default, if a `.swaggo` file is present in the current directory it will be used. Go code: ```go type MyStruct struct { ID sql.NullInt64 `json:"id"` + Name sql.NullString `json:"name"` } ``` -`overrides.yaml`: -```yaml -"database/sql.NullInt64": "int" +`.swaggo`: ``` +// Replace all NullInt64 with int +replace database/sql.NullInt64 int + +// Don't include any fields of type database/sql.NullString in the swagger docs +skip database/sql.NullString +``` + +Possible directives are comments (beginning with `//`), `replace path/to/a.type path/to/b.type`, and `skip path/to/a.type`. (Note that the full paths to any named types must be provided to prevent problems when multiple packages define a type with the same name) -Rendered (after running swag with `--overridesFile overrides.yaml`): +Rendered: ```go "types.MyStruct": { "id": "integer" diff --git a/gen/gen.go b/gen/gen.go index 6e8b073d3..cdda38e60 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -10,7 +10,6 @@ import ( "log" "os" "path/filepath" - "regexp" "strings" "text/template" "time" @@ -104,16 +103,18 @@ func (g *Gen) Build(config *Config) error { var overrides map[string]string if config.OverridesFile != "" { overridesFile, err := os.Open(config.OverridesFile) - if err == nil { + if err != nil { + // Don't bother reporting if the default file is missing; assume there are no overrides + if !(config.OverridesFile == DefaultOverridesFile && os.IsNotExist(err)) { + return fmt.Errorf("could not open overrides file: %w", err) + } + } else { log.Printf("Using overrides from %s", config.OverridesFile) overrides, err = parseOverrides(overridesFile) if err != nil { return err } - } else if !(config.OverridesFile == DefaultOverridesFile && os.IsNotExist(err)) { - // Don't bother reporting if the default file is missing; assume there are no overrides - return fmt.Errorf("could not open overrides file: %w", err) } } @@ -209,19 +210,34 @@ func (g *Gen) formatSource(src []byte) []byte { func parseOverrides(r io.Reader) (map[string]string, error) { overrides := make(map[string]string) scanner := bufio.NewScanner(r) - overridesReplace := regexp.MustCompile(`replace\s+(\S+)\s+(\S+)`) - overridesSkip := regexp.MustCompile(`skip\s+(\S+)`) - overridesComment := regexp.MustCompile(`^//.*`) - nonWhitespace := regexp.MustCompile(`\S`) for scanner.Scan() { line := scanner.Text() - if parts := overridesReplace.FindStringSubmatch(line); parts != nil { - overrides[parts[1]] = parts[2] // parts[0] is the full line - } else if parts := overridesSkip.FindStringSubmatch(line); parts != nil { - overrides[parts[1]] = "" // parts[0] is the full line - } else if nonWhitespace.MatchString(line) && !overridesComment.MatchString(line) { + // Skip comments + if len(line) > 1 && line[0:2] == "//" { + continue + } + + parts := strings.Fields(line) + + switch len(parts) { + case 0: + // only whitespace + continue + case 2: + // either a skip or malformed + if parts[0] != "skip" { + return nil, fmt.Errorf("could not parse override: '%s'", line) + } + overrides[parts[1]] = "" + case 3: + // either a replace or malformed + if parts[0] != "replace" { + return nil, fmt.Errorf("could not parse override: '%s'", line) + } + overrides[parts[1]] = parts[2] + default: return nil, fmt.Errorf("could not parse override: '%s'", line) } } diff --git a/gen/gen_test.go b/gen/gen_test.go index dd9267cdc..5c6a52992 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -12,6 +12,7 @@ import ( "strings" "testing" + "github.com/agiledragon/gomonkey/v2" "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -521,3 +522,51 @@ func TestGen_parseOverrides(t *testing.T) { }) } } + +func TestGen_TypeOverridesFile(t *testing.T) { + customPath := "/foo/bar/baz" + + t.Run("Default file is missing", func(t *testing.T) { + patches := gomonkey.ApplyFunc(os.Open, func(path string) (*os.File, error) { + assert.Equal(t, DefaultOverridesFile, path) + return nil, os.ErrNotExist + }) + defer patches.Reset() + + err := New().Build(&Config{}) + assert.NoError(t, err) + }) + + t.Run("Default file is present", func(t *testing.T) { + patches := gomonkey.ApplyFunc(os.Open, func(path string) (*os.File, error) { + assert.Equal(t, DefaultOverridesFile, path) + return &os.File{}, nil + }) + defer patches.Reset() + + err := New().Build(&Config{}) + assert.NoError(t, err) + }) + + t.Run("Different file is missing", func(t *testing.T) { + patches := gomonkey.ApplyFunc(os.Open, func(path string) (*os.File, error) { + assert.Equal(t, DefaultOverridesFile, path) + return nil, os.ErrNotExist + }) + defer patches.Reset() + + err := New().Build(&Config{OverridesFile: customPath}) + assert.EqualError(t, err, "could not open overrides file: %w") + }) + + t.Run("Different file is present", func(t *testing.T) { + patches := gomonkey.ApplyFunc(os.Open, func(path string) (*os.File, error) { + assert.Equal(t, customPath, path) + return &os.File{}, nil + }) + defer patches.Reset() + + err := New().Build(&Config{OverridesFile: customPath}) + assert.NoError(t, err) + }) +} From b0fd3aa900ed4de3d59d96a260c73d03fbacca63 Mon Sep 17 00:00:00 2001 From: Ian Fox Date: Tue, 30 Nov 2021 12:59:33 +0100 Subject: [PATCH 5/5] fix: tests for loading custom overrides file --- gen/gen.go | 4 +++- gen/gen_test.go | 64 +++++++++++++++++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/gen/gen.go b/gen/gen.go index cdda38e60..0fbb25951 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -19,6 +19,8 @@ import ( "github.com/swaggo/swag" ) +var open = os.Open + // DefaultOverridesFile is the location swaggo will look for type overrides. const DefaultOverridesFile = ".swaggo" @@ -102,7 +104,7 @@ func (g *Gen) Build(config *Config) error { var overrides map[string]string if config.OverridesFile != "" { - overridesFile, err := os.Open(config.OverridesFile) + overridesFile, err := open(config.OverridesFile) if err != nil { // Don't bother reporting if the default file is missing; assume there are no overrides if !(config.OverridesFile == DefaultOverridesFile && os.IsNotExist(err)) { diff --git a/gen/gen_test.go b/gen/gen_test.go index 5c6a52992..7b2653052 100644 --- a/gen/gen_test.go +++ b/gen/gen_test.go @@ -12,7 +12,6 @@ import ( "strings" "testing" - "github.com/agiledragon/gomonkey/v2" "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -526,47 +525,70 @@ func TestGen_parseOverrides(t *testing.T) { func TestGen_TypeOverridesFile(t *testing.T) { customPath := "/foo/bar/baz" + tmp, err := ioutil.TempFile("", "") + require.NoError(t, err) + defer os.Remove(tmp.Name()) + + config := &Config{ + SearchDir: searchDir, + MainAPIFile: "./main.go", + OutputDir: "../testdata/simple/docs", + PropNamingStrategy: "", + } + t.Run("Default file is missing", func(t *testing.T) { - patches := gomonkey.ApplyFunc(os.Open, func(path string) (*os.File, error) { + open = func(path string) (*os.File, error) { assert.Equal(t, DefaultOverridesFile, path) return nil, os.ErrNotExist - }) - defer patches.Reset() + } + defer func() { + open = os.Open + }() - err := New().Build(&Config{}) + config.OverridesFile = DefaultOverridesFile + err := New().Build(config) assert.NoError(t, err) }) t.Run("Default file is present", func(t *testing.T) { - patches := gomonkey.ApplyFunc(os.Open, func(path string) (*os.File, error) { + open = func(path string) (*os.File, error) { assert.Equal(t, DefaultOverridesFile, path) - return &os.File{}, nil - }) - defer patches.Reset() + return tmp, nil + } + defer func() { + open = os.Open + }() - err := New().Build(&Config{}) + config.OverridesFile = DefaultOverridesFile + err := New().Build(config) assert.NoError(t, err) }) t.Run("Different file is missing", func(t *testing.T) { - patches := gomonkey.ApplyFunc(os.Open, func(path string) (*os.File, error) { - assert.Equal(t, DefaultOverridesFile, path) + open = func(path string) (*os.File, error) { + assert.Equal(t, customPath, path) return nil, os.ErrNotExist - }) - defer patches.Reset() + } + defer func() { + open = os.Open + }() - err := New().Build(&Config{OverridesFile: customPath}) - assert.EqualError(t, err, "could not open overrides file: %w") + config.OverridesFile = customPath + err := New().Build(config) + assert.EqualError(t, err, "could not open overrides file: file does not exist") }) t.Run("Different file is present", func(t *testing.T) { - patches := gomonkey.ApplyFunc(os.Open, func(path string) (*os.File, error) { + open = func(path string) (*os.File, error) { assert.Equal(t, customPath, path) - return &os.File{}, nil - }) - defer patches.Reset() + return tmp, nil + } + defer func() { + open = os.Open + }() - err := New().Build(&Config{OverridesFile: customPath}) + config.OverridesFile = customPath + err := New().Build(config) assert.NoError(t, err) }) }