From 3f2a17082635bf2435dca0280675587f122ba3ce Mon Sep 17 00:00:00 2001 From: Jeremiah Z Date: Mon, 14 Jul 2025 11:00:52 -0600 Subject: [PATCH 1/5] fixed all examples to have a generated name, added WithNamedJsonString to allow adding json string examples with a specific name --- paths.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/paths.go b/paths.go index 3682d81..51b6a1e 100644 --- a/paths.go +++ b/paths.go @@ -179,8 +179,32 @@ func (r Response) WithJSONString(s string) Response { } // WithExample takes a struct and adds a json Content to the Response +// name is auto generated based on the example count func (r Response) WithExample(i any) Response { - return r.WithNamedExample("", i) + exampleCount := len(r.Content[Json].Examples) + 1 + return r.WithNamedExample("Example "+strconv.Itoa(exampleCount), i) +} + +// WithNamedJsonString takes a json string object and adds a json Content to the Response +// s is unmarshalled into a map to extract the key and value pairs +// JSONStringResp || resp.JSONString(s) +func (r Response) WithNamedJsonString(name string, s string) Response { + var m any + if s[0] == '[' && s[len(s)-1] == ']' { + m = make([]any, 0) + } else { + m = make(map[string]any) + } + err := json.Unmarshal([]byte(s), &m) + if err != nil { + // return a response with the error message + return Response{ + Status: r.Status, + Desc: err.Error(), + Content: Content{"invalid/json": {Examples: map[string]Example{"invalid": {Value: s}}}}, + } + } + return r.WithNamedExample(name, m) } func (r Response) WithNamedExample(name string, i any) Response { From 6d3fc1ad522cd51b6e4ee4f23cacf7e5aeb64436 Mon Sep 17 00:00:00 2001 From: Jeremiah Z Date: Mon, 14 Jul 2025 11:03:50 -0600 Subject: [PATCH 2/5] fixed tests --- paths_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/paths_test.go b/paths_test.go index 1366df5..08551a2 100644 --- a/paths_test.go +++ b/paths_test.go @@ -1,8 +1,9 @@ package openapi import ( - "github.com/hydronica/trial" "testing" + + "github.com/hydronica/trial" ) func TestAddParams(t *testing.T) { @@ -269,7 +270,7 @@ func TestAddResponse(t *testing.T) { Properties: map[string]Schema{"status": {Type: "string"}}, }, Examples: map[string]Example{ - "2c69c864087c4000": { + "Example 1": { Value: map[string]any{"status": "ok"}, }, }, @@ -284,7 +285,7 @@ func TestAddResponse(t *testing.T) { Properties: map[string]Schema{"Error": {Type: "string"}}, }, Examples: map[string]Example{ - "struct { Error string }": { + "Example 1": { Value: struct{ Error string }{Error: "invalid request"}, }, }, From cad7f34916aca45185f24aad27edaeca0e2a6617 Mon Sep 17 00:00:00 2001 From: "jeremiah.zink" Date: Tue, 15 Jul 2025 07:29:45 -0600 Subject: [PATCH 3/5] moved naming logic for examples into the add_example func, fixed a couple other small issues --- build_test.go | 11 +++-------- openapi.go | 2 +- paths.go | 33 +++++++++------------------------ paths_test.go | 2 +- 4 files changed, 14 insertions(+), 34 deletions(-) diff --git a/build_test.go b/build_test.go index 70d54fe..489a9b0 100644 --- a/build_test.go +++ b/build_test.go @@ -3,11 +3,12 @@ package openapi import ( _ "embed" "errors" + "testing" + "time" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hydronica/trial" - "testing" - "time" ) func TestBuildSchema(t *testing.T) { @@ -24,12 +25,6 @@ func TestBuildSchema(t *testing.T) { F3 int } - // test a time type - type TestT struct { - F1 time.Time `json:"time.time" format:"2006-01-02"` - // F2 Time `json:"openapi.time"` // custom time format can be used - } - type TestF struct { F1 int `json:"f1_int"` F2 bool `json:"f2_bool"` diff --git a/openapi.go b/openapi.go index 8d1bb30..c01b910 100644 --- a/openapi.go +++ b/openapi.go @@ -54,7 +54,7 @@ type Tag struct { } type ExternalDocs struct { - Desc string `json:"description,omitempty""` // A short description of the target documentation. CommonMark syntax MAY be used for rich text representation. + Desc string `json:"description,omitempty"` // A short description of the target documentation. CommonMark syntax MAY be used for rich text representation. URL string `json:"url,omitempty" required:"true"` // REQUIRED. The URL for the target documentation. Value MUST be in the format of a URL. } diff --git a/paths.go b/paths.go index 51b6a1e..aae10cb 100644 --- a/paths.go +++ b/paths.go @@ -160,29 +160,13 @@ type Response struct { // s is unmarshalled into a map to extract the key and value pairs // JSONStringResp || resp.JSONString(s) func (r Response) WithJSONString(s string) Response { - var m any - if s[0] == '[' && s[len(s)-1] == ']' { - m = make([]any, 0) - } else { - m = make(map[string]any) - } - err := json.Unmarshal([]byte(s), &m) - if err != nil { - // return a response with the error message - return Response{ - Status: r.Status, - Desc: err.Error(), - Content: Content{"invalid/json": {Examples: map[string]Example{"invalid": {Value: s}}}}, - } - } - return r.WithExample(m) + return r.WithNamedJsonString("", s) } // WithExample takes a struct and adds a json Content to the Response // name is auto generated based on the example count func (r Response) WithExample(i any) Response { - exampleCount := len(r.Content[Json].Examples) + 1 - return r.WithNamedExample("Example "+strconv.Itoa(exampleCount), i) + return r.WithNamedExample("", i) } // WithNamedJsonString takes a json string object and adds a json Content to the Response @@ -221,7 +205,7 @@ func (r Response) WithNamedExample(name string, i any) Response { // creating a schema based on the object i passed in. // The Example name will be the title of the Schema if not provided // and any description from added to the example as well. -func (m *Media) AddExample(exName string, i any) { +func (m *Media) AddExample(name string, i any) { if m.Examples == nil { m.Examples = make(map[string]Example) } @@ -229,8 +213,9 @@ func (m *Media) AddExample(exName string, i any) { if m.Schema.Title == "" { m.Schema = schema } - if exName == "" { - exName = schema.Title + if name == "" { + count := len(m.Examples) + 1 + name = "Example " + strconv.Itoa(count) } ex := Example{ Desc: schema.Desc, @@ -238,11 +223,11 @@ func (m *Media) AddExample(exName string, i any) { } // create unique name if key already exists - if _, found := m.Examples[exName]; found { - exName = exName + strconv.Itoa(len(m.Examples)) + if _, found := m.Examples[name]; found { + name = name + strconv.Itoa(len(m.Examples)) } - m.Examples[exName] = ex + m.Examples[name] = ex } // RequestBody describes a single request body. diff --git a/paths_test.go b/paths_test.go index 08551a2..a04e10c 100644 --- a/paths_test.go +++ b/paths_test.go @@ -294,7 +294,7 @@ func TestAddResponse(t *testing.T) { }, }) if !eq { - t.Logf(diff) + t.Log(diff) t.Fail() } From 771ddc1051419b098d6872448fd74c967db247a3 Mon Sep 17 00:00:00 2001 From: "jeremiah.zink" Date: Tue, 15 Jul 2025 09:24:05 -0600 Subject: [PATCH 4/5] added response --- paths.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/paths.go b/paths.go index aae10cb..f205c81 100644 --- a/paths.go +++ b/paths.go @@ -214,8 +214,7 @@ func (m *Media) AddExample(name string, i any) { m.Schema = schema } if name == "" { - count := len(m.Examples) + 1 - name = "Example " + strconv.Itoa(count) + name = "Example " } ex := Example{ Desc: schema.Desc, @@ -237,6 +236,10 @@ type RequestBody struct { Required bool `json:"required,omitempty"` // Determines if the request body is required in the request. Defaults to false. } +func (r RequestBody) WithNamedJsonString(name string, s string) RequestBody { + return r.WithNamedExample(name, s) +} + func (r RequestBody) WithJSONString(s string) RequestBody { var m any if s[0] == '[' && s[len(s)-1] == ']' { From bd0008b34a79f757293725e515eae9be08ff15b0 Mon Sep 17 00:00:00 2001 From: "jeremiah.zink" Date: Tue, 15 Jul 2025 09:58:35 -0600 Subject: [PATCH 5/5] added extra example showing additional examples, fixed tests --- paths.go | 4 ++-- paths_test.go | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/paths.go b/paths.go index f205c81..eacd261 100644 --- a/paths.go +++ b/paths.go @@ -214,7 +214,7 @@ func (m *Media) AddExample(name string, i any) { m.Schema = schema } if name == "" { - name = "Example " + name = "Example" } ex := Example{ Desc: schema.Desc, @@ -223,7 +223,7 @@ func (m *Media) AddExample(name string, i any) { // create unique name if key already exists if _, found := m.Examples[name]; found { - name = name + strconv.Itoa(len(m.Examples)) + name = name + " " + strconv.Itoa(len(m.Examples)) } m.Examples[name] = ex diff --git a/paths_test.go b/paths_test.go index a04e10c..1a4011a 100644 --- a/paths_test.go +++ b/paths_test.go @@ -251,8 +251,10 @@ func TestAddResponse(t *testing.T) { route.AddResponse(Response{ Status: 200, Desc: "resp desc", - }.WithJSONString(`{"status":"ok"}`)) - route.AddResponse(Response{Status: 400}.WithExample(struct{ Error string }{Error: "invalid request"})) + }.WithJSONString(`{"status":"ok"}`).WithJSONString(`{"status":"ok"}`)) + + route.AddResponse(Response{Status: 400}. + WithExample(struct{ Error string }{Error: "invalid request"})) eq, diff := trial.Equal(route, &Route{ path: "/test", @@ -270,9 +272,8 @@ func TestAddResponse(t *testing.T) { Properties: map[string]Schema{"status": {Type: "string"}}, }, Examples: map[string]Example{ - "Example 1": { - Value: map[string]any{"status": "ok"}, - }, + "Example": {Value: map[string]any{"status": "ok"}}, + "Example 1": {Value: map[string]any{"status": "ok"}}, }, }}, }, @@ -285,9 +286,7 @@ func TestAddResponse(t *testing.T) { Properties: map[string]Schema{"Error": {Type: "string"}}, }, Examples: map[string]Example{ - "Example 1": { - Value: struct{ Error string }{Error: "invalid request"}, - }, + "Example": {Value: struct{ Error string }{Error: "invalid request"}}, }, }}, },