From 09c5c817e1ed36989cb21fa30cf84ad93946bb33 Mon Sep 17 00:00:00 2001 From: Brendan Le Glaunec Date: Fri, 2 May 2025 12:25:57 +0200 Subject: [PATCH 1/3] feat: support multiline doc strings --- cmd/oapi-gen/gen.go | 39 +++++++++++++++++++++++++------- cmd/oapi-gen/testdata/all.go | 2 +- cmd/oapi-gen/testdata/not-all.go | 2 +- cmd/oapi-gen/testdata/test.go | 3 ++- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/cmd/oapi-gen/gen.go b/cmd/oapi-gen/gen.go index 1761d23..ff14e1a 100644 --- a/cmd/oapi-gen/gen.go +++ b/cmd/oapi-gen/gen.go @@ -8,11 +8,12 @@ import ( "go/format" "go/parser" "go/token" - "html/template" + "html" "io/fs" "slices" "sort" "strings" + "text/template" "github.com/fatih/structtag" ) @@ -51,7 +52,9 @@ func (g *Generator) Generate(path string) ([]byte, error) { return nil, nil } - tmpl, err := template.New("info").Parse(genTemplate) + tmpl, err := template.New("info").Funcs(template.FuncMap{ + "escapeGoString": escapeGoString, + }).Parse(genTemplate) if err != nil { return nil, fmt.Errorf("createing template: %w", err) } @@ -245,13 +248,33 @@ func fieldName(field *ast.Field, tag string) (string, bool) { } func docToString(cg *ast.CommentGroup) string { - s := cg.Text() - if s == "" { - return "" + lines := make([]string, 0, len(cg.List)) + for _, c := range cg.List { + text := strings.TrimPrefix(c.Text, "//") + text = strings.TrimPrefix(text, "/*") + text = strings.TrimSuffix(text, "*/") + text = strings.TrimSpace(text) + + // Skip directive lines + if strings.HasPrefix(text, "openapi:") { + continue + } + + // Skip empty lines. + if text == "" { + continue + } + + lines = append(lines, text) } + return html.EscapeString(strings.Join(lines, "\n")) +} - s = strings.Join(strings.Split(s, "\n"), " ") - return strings.TrimSpace(s) +func escapeGoString(s string) string { + s = strings.ReplaceAll(s, `\`, `\\`) + s = strings.ReplaceAll(s, `"`, `\"`) + s = strings.ReplaceAll(s, "\n", `\n`) + return s } const genTemplate = `// Code generated by oapi-gen. DO NOT EDIT. @@ -264,7 +287,7 @@ package {{ .Pkg }} func ({{ .Name }}) Docs() map[string]string { return map[string]string { {{- range $k, $v := .Docs }} - "{{ $k }}": "{{ $v }}", + "{{ $k }}": "{{ escapeGoString $v }}", {{- end }} } } diff --git a/cmd/oapi-gen/testdata/all.go b/cmd/oapi-gen/testdata/all.go index 5975caf..fb9be03 100644 --- a/cmd/oapi-gen/testdata/all.go +++ b/cmd/oapi-gen/testdata/all.go @@ -6,7 +6,7 @@ package testdata func (TestObject) Docs() map[string]string { return map[string]string{ "B": "B is another example field.", - "a": "A is an example field with "quotes".", + "a": "A is an example field with "quotes"\nand a newline.", } } diff --git a/cmd/oapi-gen/testdata/not-all.go b/cmd/oapi-gen/testdata/not-all.go index e95e1c5..431e22e 100644 --- a/cmd/oapi-gen/testdata/not-all.go +++ b/cmd/oapi-gen/testdata/not-all.go @@ -6,7 +6,7 @@ package testdata func (TestObject) Docs() map[string]string { return map[string]string{ "B": "B is another example field.", - "a": "A is an example field with "quotes".", + "a": "A is an example field with "quotes"\nand a newline.", } } diff --git a/cmd/oapi-gen/testdata/test.go b/cmd/oapi-gen/testdata/test.go index ff2ece2..b531fea 100644 --- a/cmd/oapi-gen/testdata/test.go +++ b/cmd/oapi-gen/testdata/test.go @@ -4,7 +4,8 @@ package testdata // //openapi:gen type TestObject struct { - // A is an example field with "quotes". + // A is an example field with "quotes" + // and a newline. A string `json:"a"` // B is another example field. From cddc82294ce70aa9f189d06607acd35136f44104 Mon Sep 17 00:00:00 2001 From: Brendan Le Glaunec Date: Mon, 5 May 2025 09:33:28 +0200 Subject: [PATCH 2/3] fix: apply PR suggestions --- cmd/oapi-gen/gen.go | 40 +++++++++------------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/cmd/oapi-gen/gen.go b/cmd/oapi-gen/gen.go index ff14e1a..4910516 100644 --- a/cmd/oapi-gen/gen.go +++ b/cmd/oapi-gen/gen.go @@ -8,12 +8,11 @@ import ( "go/format" "go/parser" "go/token" - "html" + "html/template" "io/fs" "slices" "sort" "strings" - "text/template" "github.com/fatih/structtag" ) @@ -52,9 +51,7 @@ func (g *Generator) Generate(path string) ([]byte, error) { return nil, nil } - tmpl, err := template.New("info").Funcs(template.FuncMap{ - "escapeGoString": escapeGoString, - }).Parse(genTemplate) + tmpl, err := template.New("info").Parse(genTemplate) if err != nil { return nil, fmt.Errorf("createing template: %w", err) } @@ -248,33 +245,14 @@ func fieldName(field *ast.Field, tag string) (string, bool) { } func docToString(cg *ast.CommentGroup) string { - lines := make([]string, 0, len(cg.List)) - for _, c := range cg.List { - text := strings.TrimPrefix(c.Text, "//") - text = strings.TrimPrefix(text, "/*") - text = strings.TrimSuffix(text, "*/") - text = strings.TrimSpace(text) - - // Skip directive lines - if strings.HasPrefix(text, "openapi:") { - continue - } - - // Skip empty lines. - if text == "" { - continue - } - - lines = append(lines, text) + s := cg.Text() + if s == "" { + return "" } - return html.EscapeString(strings.Join(lines, "\n")) -} -func escapeGoString(s string) string { - s = strings.ReplaceAll(s, `\`, `\\`) - s = strings.ReplaceAll(s, `"`, `\"`) - s = strings.ReplaceAll(s, "\n", `\n`) - return s + s = strings.TrimSuffix(s, "\n") + s = strings.Join(strings.Split(s, "\n"), "\\n") // Escape newlines. + return strings.TrimSpace(s) } const genTemplate = `// Code generated by oapi-gen. DO NOT EDIT. @@ -287,7 +265,7 @@ package {{ .Pkg }} func ({{ .Name }}) Docs() map[string]string { return map[string]string { {{- range $k, $v := .Docs }} - "{{ $k }}": "{{ escapeGoString $v }}", + "{{ $k }}": "{{ $v }}", {{- end }} } } From d133799b9090ae9eda85dc229c066de6c6faeb5a Mon Sep 17 00:00:00 2001 From: Brendan Le Glaunec Date: Mon, 5 May 2025 11:27:54 +0200 Subject: [PATCH 3/3] feat: add example of multiline docs in gen tests --- gen_test.go | 2 +- testdata/spec-pkgseg0.json | 2 +- testdata/spec.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gen_test.go b/gen_test.go index 0f6c5b3..212c30e 100644 --- a/gen_test.go +++ b/gen_test.go @@ -185,7 +185,7 @@ type TestObject struct { func (TestObject) Docs() map[string]string { return map[string]string{ - "test1": "Some test docs", + "test1": "Test1 is an documented field with "quotes"\nand a newline.", } } diff --git a/testdata/spec-pkgseg0.json b/testdata/spec-pkgseg0.json index 38bdd75..4e47416 100644 --- a/testdata/spec-pkgseg0.json +++ b/testdata/spec-pkgseg0.json @@ -26,7 +26,7 @@ }, "properties": { "test1": { - "description": "Some test docs", + "description": "Test1 is an documented field with \u0026#34;quotes\u0026#34;\nand a newline.", "type": "string" }, "test2": { diff --git a/testdata/spec.json b/testdata/spec.json index cc21d2c..f1e3212 100644 --- a/testdata/spec.json +++ b/testdata/spec.json @@ -26,7 +26,7 @@ }, "properties": { "test1": { - "description": "Some test docs", + "description": "Test1 is an documented field with \u0026#34;quotes\u0026#34;\nand a newline.", "type": "string" }, "test2": {