Skip to content

Commit ce8b32c

Browse files
leosunmoubogdan
authored andcommitted
Add option to set template delimiters (#1499)
* Add template action delimiter cli flag * Add delims to generator config and template Also adds tests using the "quote" test as a base. This has to have a custom Instance name or it will clash with the "quotes" one and panic since it will have registered two "swagger" instances in the package test. * Add testdata for custom delim flags Based on the "quote" testdata. * Add delims to the spec, with tests. Make sure we don't add delims if they are empty. This shouldn't be possible, but might as well be safe. * Go mod tidy and sum update * Make the CLI experience a bit cleaner * Revert go.mod and sum * Update readme
1 parent 543e18b commit ce8b32c

File tree

10 files changed

+260
-31
lines changed

10 files changed

+260
-31
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ dist
22
testdata/simple*/docs
33
testdata/quotes/docs
44
testdata/quotes/quotes.so
5+
testdata/delims/docs
6+
testdata/delims/delims.so
57
example/basic/docs/*
68
example/celler/docs/*
79
cover.out

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ OPTIONS:
104104
--overridesFile value File to read global type overrides from. (default: ".swaggo")
105105
--parseGoList Parse dependency via 'go list' (default: true)
106106
--tags value, -t value A comma-separated list of tags to filter the APIs for which the documentation is generated.Special case if the tag is prefixed with the '!' character then the APIs with that tag will be excluded
107+
--templateDelims value, --td value Provide custom delimeters for Go template generation. The format is leftDelim,rightDelim. For example: "[[,]]"
107108
--collectionFormat value, --cf value Set default collection format (default: "csv")
108109
--help, -h show help (default: false)
109110
```
@@ -908,6 +909,18 @@ By default `swag` command generates Swagger specification in three different fil
908909
909910
If you would like to limit a set of file types which should be generated you can use `--outputTypes` (short `-ot`) flag. Default value is `go,json,yaml` - output types separated with comma. To limit output only to `go` and `yaml` files, you would write `go,yaml`. With complete command that would be `swag init --outputTypes go,yaml`.
910911
912+
### Change the default Go Template action delimiters
913+
[#980](https://github.com/swaggo/swag/issues/980)
914+
[#1177](https://github.com/swaggo/swag/issues/1177)
915+
916+
If your swagger annotations or struct fields contain "{{" or "}}", the template generation will most likely fail, as these are the default delimiters for [go templates](https://pkg.go.dev/text/template#Template.Delims).
917+
918+
To make the generation work properly, you can change the default delimiters with `-td`. For example:
919+
```console
920+
swag init -g http/api.go -td "[[,]]"
921+
```
922+
The new delimiter is a string with the format "`<left delimiter>`,`<right delimiter>`".
923+
911924
## About the Project
912925
This project was inspired by [yvasiyarov/swagger](https://github.com/yvasiyarov/swagger) but we simplified the usage and added support a variety of [web frameworks](#supported-web-frameworks). Gopher image source is [tenntenn/gopher-stickers](https://github.com/tenntenn/gopher-stickers). It has licenses [creative commons licensing](http://creativecommons.org/licenses/by/3.0/deed.en).
913926
## Contributors

cmd/swag/main.go

+21
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const (
3636
tagsFlag = "tags"
3737
parseExtensionFlag = "parseExtension"
3838
openAPIVersionFlag = "v3.1"
39+
templateDelimsFlag = "templateDelims"
3940
packageName = "packageName"
4041
collectionFormatFlag = "collectionFormat"
4142
)
@@ -149,6 +150,12 @@ var initFlags = []cli.Flag{
149150
Value: false,
150151
Usage: "Generate OpenAPI V3.1 spec",
151152
},
153+
&cli.StringFlag{
154+
Name: templateDelimsFlag,
155+
Aliases: []string{"td"},
156+
Value: "",
157+
Usage: "Provide custom delimeters for Go template generation. The format is leftDelim,rightDelim. For example: \"[[,]]\"",
158+
},
152159
&cli.StringFlag{
153160
Name: packageName,
154161
Value: "",
@@ -171,6 +178,18 @@ func initAction(ctx *cli.Context) error {
171178
return fmt.Errorf("not supported %s propertyStrategy", strategy)
172179
}
173180

181+
leftDelim, rightDelim := "{{", "}}"
182+
183+
if ctx.IsSet(templateDelimsFlag) {
184+
delims := strings.Split(ctx.String(templateDelimsFlag), ",")
185+
if len(delims) != 2 {
186+
return fmt.Errorf("exactly two template delimeters must be provided, comma separated")
187+
} else if delims[0] == delims[1] {
188+
return fmt.Errorf("template delimiters must be different")
189+
}
190+
leftDelim, rightDelim = strings.TrimSpace(delims[0]), strings.TrimSpace(delims[1])
191+
}
192+
174193
outputTypes := strings.Split(ctx.String(outputTypesFlag), ",")
175194
if len(outputTypes) == 0 {
176195
return fmt.Errorf("no output types specified")
@@ -205,6 +224,8 @@ func initAction(ctx *cli.Context) error {
205224
OverridesFile: ctx.String(overridesFileFlag),
206225
ParseGoList: ctx.Bool(parseGoListFlag),
207226
Tags: ctx.String(tagsFlag),
227+
LeftTemplateDelim: leftDelim,
228+
RightTemplateDelim: rightDelim,
208229
PackageName: ctx.String(packageName),
209230
Debugger: logger,
210231
GenerateOpenAPI3Doc: ctx.Bool(openAPIVersionFlag),

gen/gen.go

+46-28
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ type Config struct {
135135
// include only tags mentioned when searching, comma separated
136136
Tags string
137137

138+
// LeftTemplateDelim defines the left delimiter for the template generation
139+
LeftTemplateDelim string
140+
141+
// RightTemplateDelim defines the right delimiter for the template generation
142+
RightTemplateDelim string
143+
138144
// GenerateOpenAPI3Doc if true, OpenAPI V3.1 spec will be generated
139145
GenerateOpenAPI3Doc bool
140146

@@ -161,6 +167,14 @@ func (g *Gen) Build(config *Config) error {
161167
}
162168
}
163169

170+
if config.LeftTemplateDelim == "" {
171+
config.LeftTemplateDelim = "{{"
172+
}
173+
174+
if config.RightTemplateDelim == "" {
175+
config.RightTemplateDelim = "}}"
176+
}
177+
164178
var overrides map[string]string
165179

166180
if config.OverridesFile != "" {
@@ -403,7 +417,7 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *v2.Swagg
403417
generator, err := template.New("oas2.tmpl").Funcs(template.FuncMap{
404418
"printDoc": func(v string) string {
405419
// Add schemes
406-
v = "{\n \"schemes\": {{ marshal .Schemes }}," + v[1:]
420+
v = "{\n \"schemes\": " + config.LeftTemplateDelim + " marshal .Schemes " + config.RightTemplateDelim + "," + v[1:]
407421
// Sanitize backticks
408422
return strings.Replace(v, "`", "`+\"`\"+`", -1)
409423
},
@@ -422,16 +436,16 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *v2.Swagg
422436
Info: &v2.Info{
423437
VendorExtensible: swagger.Info.VendorExtensible,
424438
InfoProps: v2.InfoProps{
425-
Description: "{{escape .Description}}",
426-
Title: "{{.Title}}",
439+
Description: config.LeftTemplateDelim + "escape .Description" + config.RightTemplateDelim,
440+
Title: config.LeftTemplateDelim + ".Title" + config.RightTemplateDelim,
427441
TermsOfService: swagger.Info.TermsOfService,
428442
Contact: swagger.Info.Contact,
429443
License: swagger.Info.License,
430-
Version: "{{.Version}}",
444+
Version: config.LeftTemplateDelim + ".Version" + config.RightTemplateDelim,
431445
},
432446
},
433-
Host: "{{.Host}}",
434-
BasePath: "{{.BasePath}}",
447+
Host: config.LeftTemplateDelim + ".Host" + config.RightTemplateDelim,
448+
BasePath: config.LeftTemplateDelim + ".BasePath" + config.RightTemplateDelim,
435449
Paths: swagger.Paths,
436450
Definitions: swagger.Definitions,
437451
Parameters: swagger.Parameters,
@@ -452,29 +466,33 @@ func (g *Gen) writeGoDoc(packageName string, output io.Writer, swagger *v2.Swagg
452466
buffer := &bytes.Buffer{}
453467

454468
err = generator.Execute(buffer, struct {
455-
Timestamp time.Time
456-
Doc string
457-
Host string
458-
PackageName string
459-
BasePath string
460-
Title string
461-
Description string
462-
Version string
463-
InstanceName string
464-
Schemes []string
465-
GeneratedTime bool
469+
Timestamp time.Time
470+
Doc string
471+
Host string
472+
PackageName string
473+
BasePath string
474+
Title string
475+
Description string
476+
Version string
477+
InstanceName string
478+
Schemes []string
479+
GeneratedTime bool
480+
LeftTemplateDelim string
481+
RightTemplateDelim string
466482
}{
467-
Timestamp: time.Now(),
468-
GeneratedTime: config.GeneratedTime,
469-
Doc: string(buf),
470-
Host: swagger.Host,
471-
PackageName: packageName,
472-
BasePath: swagger.BasePath,
473-
Schemes: swagger.Schemes,
474-
Title: swagger.Info.Title,
475-
Description: swagger.Info.Description,
476-
Version: swagger.Info.Version,
477-
InstanceName: config.InstanceName,
483+
Timestamp: time.Now(),
484+
GeneratedTime: config.GeneratedTime,
485+
Doc: string(buf),
486+
Host: swagger.Host,
487+
PackageName: packageName,
488+
BasePath: swagger.BasePath,
489+
Schemes: swagger.Schemes,
490+
Title: swagger.Info.Title,
491+
Description: swagger.Info.Description,
492+
Version: swagger.Info.Version,
493+
InstanceName: config.InstanceName,
494+
LeftTemplateDelim: config.LeftTemplateDelim,
495+
RightTemplateDelim: config.RightTemplateDelim,
478496
})
479497
if err != nil {
480498
return err

gen/gen_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,67 @@ func TestGen_BuildDescriptionWithQuotes(t *testing.T) {
259259
assert.JSONEq(t, string(expectedJSON), jsonOutput)
260260
}
261261

262+
func TestGen_BuildDocCustomDelims(t *testing.T) {
263+
config := &Config{
264+
SearchDir: "../testdata/delims",
265+
MainAPIFile: "./main.go",
266+
OutputDir: "../testdata/delims/docs",
267+
OutputTypes: outputTypes,
268+
MarkdownFilesDir: "../testdata/delims",
269+
InstanceName: "CustomDelims",
270+
LeftTemplateDelim: "{%",
271+
RightTemplateDelim: "%}",
272+
}
273+
274+
require.NoError(t, New().Build(config))
275+
276+
expectedFiles := []string{
277+
filepath.Join(config.OutputDir, "CustomDelims_docs.go"),
278+
filepath.Join(config.OutputDir, "CustomDelims_swagger.json"),
279+
filepath.Join(config.OutputDir, "CustomDelims_swagger.yaml"),
280+
}
281+
for _, expectedFile := range expectedFiles {
282+
if _, err := os.Stat(expectedFile); os.IsNotExist(err) {
283+
require.NoError(t, err)
284+
}
285+
}
286+
287+
cmd := exec.Command("go", "build", "-buildmode=plugin", "github.com/swaggo/swag/testdata/delims")
288+
289+
cmd.Dir = config.SearchDir
290+
291+
output, err := cmd.CombinedOutput()
292+
if err != nil {
293+
require.NoError(t, err, string(output))
294+
}
295+
296+
p, err := plugin.Open(filepath.Join(config.SearchDir, "delims.so"))
297+
if err != nil {
298+
require.NoError(t, err)
299+
}
300+
301+
defer os.Remove("delims.so")
302+
303+
readDoc, err := p.Lookup("ReadDoc")
304+
if err != nil {
305+
require.NoError(t, err)
306+
}
307+
308+
jsonOutput := readDoc.(func() string)()
309+
310+
var jsonDoc interface{}
311+
if err := json.Unmarshal([]byte(jsonOutput), &jsonDoc); err != nil {
312+
require.NoError(t, err)
313+
}
314+
315+
expectedJSON, err := os.ReadFile(filepath.Join(config.SearchDir, "expected.json"))
316+
if err != nil {
317+
require.NoError(t, err)
318+
}
319+
320+
assert.JSONEq(t, string(expectedJSON), jsonOutput)
321+
}
322+
262323
func TestGen_jsonIndent(t *testing.T) {
263324
config := &Config{
264325
SearchDir: searchDir,

spec.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ type Spec struct {
1717
Description string
1818
InfoInstanceName string
1919
SwaggerTemplate string
20+
LeftDelim string
21+
RightDelim string
2022
}
2123

2224
// ReadDoc parses SwaggerTemplate into swagger document.
2325
func (i *Spec) ReadDoc() string {
2426
i.Description = strings.ReplaceAll(i.Description, "\n", "\\n")
2527

26-
tpl, err := template.New("swagger_info").Funcs(template.FuncMap{
28+
tpl := template.New("swagger_info").Funcs(template.FuncMap{
2729
"marshal": func(v interface{}) string {
2830
a, _ := json.Marshal(v)
2931

@@ -37,13 +39,19 @@ func (i *Spec) ReadDoc() string {
3739

3840
return strings.ReplaceAll(str, "\\\\\"", "\\\\\\\"")
3941
},
40-
}).Parse(i.SwaggerTemplate)
42+
})
43+
44+
if i.LeftDelim != "" && i.RightDelim != "" {
45+
tpl = tpl.Delims(i.LeftDelim, i.RightDelim)
46+
}
47+
48+
parsed, err := tpl.Parse(i.SwaggerTemplate)
4149
if err != nil {
4250
return i.SwaggerTemplate
4351
}
4452

4553
var doc bytes.Buffer
46-
if err = tpl.Execute(&doc, i); err != nil {
54+
if err = parsed.Execute(&doc, i); err != nil {
4755
return i.SwaggerTemplate
4856
}
4957

spec_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ func TestSpec_ReadDoc(t *testing.T) {
6363
Description string
6464
InfoInstanceName string
6565
SwaggerTemplate string
66+
LeftDelim string
67+
RightDelim string
6668
}
6769

6870
tests := []struct {
@@ -132,6 +134,37 @@ func TestSpec_ReadDoc(t *testing.T) {
132134
},
133135
want: "{{ .Schemesa }}",
134136
},
137+
{
138+
name: "TestReadDocCustomDelims",
139+
fields: fields{
140+
Version: "1.0",
141+
Host: "localhost:8080",
142+
BasePath: "/",
143+
InfoInstanceName: "TestInstanceName",
144+
SwaggerTemplate: `{
145+
"swagger": "2.0",
146+
"info": {
147+
"description": "{%escape .Description%}",
148+
"title": "{%.Title%}",
149+
"version": "{%.Version%}"
150+
},
151+
"host": "{%.Host%}",
152+
"basePath": "{%.BasePath%}",
153+
}`,
154+
LeftDelim: "{%",
155+
RightDelim: "%}",
156+
},
157+
want: "{" +
158+
"\n\t\t\t\"swagger\": \"2.0\"," +
159+
"\n\t\t\t\"info\": {" +
160+
"\n\t\t\t\t\"description\": \"\",\n\t\t\t\t\"" +
161+
"title\": \"\"," +
162+
"\n\t\t\t\t\"version\": \"1.0\"" +
163+
"\n\t\t\t}," +
164+
"\n\t\t\t\"host\": \"localhost:8080\"," +
165+
"\n\t\t\t\"basePath\": \"/\"," +
166+
"\n\t\t}",
167+
},
135168
}
136169

137170
for _, tt := range tests {
@@ -145,6 +178,8 @@ func TestSpec_ReadDoc(t *testing.T) {
145178
Description: tt.fields.Description,
146179
InfoInstanceName: tt.fields.InfoInstanceName,
147180
SwaggerTemplate: tt.fields.SwaggerTemplate,
181+
LeftDelim: tt.fields.LeftDelim,
182+
RightDelim: tt.fields.RightDelim,
148183
}
149184

150185
assert.Equal(t, tt.want, doc.ReadDoc())

testdata/delims/api/api.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package api
2+
3+
// MyFunc godoc
4+
// @Description My Function
5+
// @Success 200 {object} MyStruct
6+
// @Router /myfunc [get]
7+
func MyFunc() {}
8+
9+
type MyStruct struct {
10+
URLTemplate string `json:"urltemplate" example:"http://example.org/{{ path }}" swaggertype:"string"`
11+
}

0 commit comments

Comments
 (0)