Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(net/goai): add enhanced response status interface #3896

Merged
merged 29 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
95d401a
feat: add openapi operation override hooker
UncleChair Oct 28, 2024
7db6e28
feat: add openapi operation override unit test
UncleChair Oct 28, 2024
5257ce3
Merge branch 'master' into goai/hook
UncleChair Nov 7, 2024
4d1563f
Merge branch 'master' into goai/hook
UncleChair Nov 11, 2024
9cd9ae4
Merge branch 'master' into goai/hook
UncleChair Nov 12, 2024
31d3f37
Revert "feat: add openapi operation override hooker"
UncleChair Nov 12, 2024
ae1824f
Revert "feat: add openapi operation override unit test"
UncleChair Nov 12, 2024
50e73bf
feat: add operation enhancement interface
UncleChair Nov 12, 2024
39ad6fd
Merge branch 'master' into goai/hook
UncleChair Nov 13, 2024
ea9fa21
feat: remove operation enhancement interface
UncleChair Nov 13, 2024
42b2705
feat: rename response enhancement interface
UncleChair Nov 13, 2024
361735c
fix: change interface and function name
UncleChair Nov 13, 2024
9049034
fix: change test cases
UncleChair Nov 13, 2024
fee488f
Merge branch 'master' into goai/hook
UncleChair Nov 13, 2024
53c71c6
Merge branch 'master' into goai/hook
UncleChair Nov 13, 2024
56c0b62
feat: add example generation from data
VoidGun Nov 14, 2024
b82f5a2
feat: add enhanced response processing
VoidGun Nov 14, 2024
7145006
feat: update old unit test
VoidGun Nov 14, 2024
023e3b0
Merge branch 'master' into goai/hook
UncleChair Nov 14, 2024
e5332cf
Merge branch 'master' into goai/hook
UncleChair Nov 14, 2024
119ba4c
Merge branch 'master' into goai/hook
UncleChair Nov 14, 2024
d60e7b9
Merge branch 'master' into goai/hook
UncleChair Nov 15, 2024
cd6fb15
Merge branch 'master' into goai/hook
UncleChair Nov 16, 2024
b503f11
Merge branch 'master' into goai/hook
UncleChair Nov 17, 2024
9be16c6
Merge branch 'master' into goai/hook
UncleChair Nov 18, 2024
9a01a83
Merge branch 'master' into goai/hook
UncleChair Nov 20, 2024
75aedb0
Merge branch 'master' into goai/hook
UncleChair Nov 21, 2024
8e71090
Merge branch 'master' into goai/hook
UncleChair Nov 21, 2024
cb0f722
Merge branch 'master' into goai/hook
UncleChair Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions net/goai/goai_example.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ func (e *Examples) applyExamplesFile(path string) error {
if err != nil {
return err
}
err = e.applyExamplesData(data)
if err != nil {
return err
}
return nil
}

func (e *Examples) applyExamplesData(data interface{}) error {
if empty.IsNil(e) || empty.IsNil(data) {
return nil
}

switch v := data.(type) {
case map[string]interface{}:
Expand Down
6 changes: 3 additions & 3 deletions net/goai/goai_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,12 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
// =================================================================================================================
// Other Responses.
// =================================================================================================================
if enhancedResponse, ok := outputObject.Interface().(ResponseStatusDef); ok {
for statusCode, data := range enhancedResponse.ResponseStatusMap() {
if enhancedResponse, ok := outputObject.Interface().(IEnhanceResponseStatus); ok {
for statusCode, data := range enhancedResponse.EnhanceResponseStatus() {
if statusCode < 100 || statusCode >= 600 {
return gerror.Newf("Invalid HTTP status code: %d", statusCode)
}
if data == nil {
if data.Response == nil {
continue
}
status := gconv.String(statusCode)
Expand Down
19 changes: 14 additions & 5 deletions net/goai/goai_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)

// StatusCode is http status for response.
type StatusCode = int
// EnhancedStatusCode is http status for response.
type EnhancedStatusCode = int

// ResponseStatusDef is used to enhance the documentation of the response.
// EnhancedStatusType is the structure for certain response status.
// Currently, it only supports `Response` and `Examples`.
// `Response` is the response structure
// `Examples` is the examples for the response, map[string]interface{}, []interface{} are supported.
type EnhancedStatusType struct {
Response any
Examples any
}

// IEnhanceResponseStatus is used to enhance the documentation of the response.
// Normal response structure could implement this interface to provide more information.
type ResponseStatusDef interface {
ResponseStatusMap() map[StatusCode]any
type IEnhanceResponseStatus interface {
EnhanceResponseStatus() map[EnhancedStatusCode]EnhancedStatusType
}

// Response is specified by OpenAPI/Swagger 3.0 standard.
Expand Down
17 changes: 16 additions & 1 deletion net/goai/goai_response_ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ type Responses map[string]ResponseRef

// object could be someObject.Interface()
// There may be some difference between someObject.Type() and reflect.TypeOf(object).
func (oai *OpenApiV3) getResponseFromObject(object interface{}, isDefault bool) (*Response, error) {
func (oai *OpenApiV3) getResponseFromObject(data interface{}, isDefault bool) (*Response, error) {
var object interface{}
enhancedResponse, isEnhanced := data.(EnhancedStatusType)
if isEnhanced {
object = enhancedResponse.Response
} else {
object = data
}
// Add object schema to oai
if err := oai.addSchema(object); err != nil {
return nil, err
Expand Down Expand Up @@ -86,6 +93,14 @@ func (oai *OpenApiV3) getResponseFromObject(object interface{}, isDefault bool)
}
}

// Override examples from enhanced response.
if isEnhanced {
err := examples.applyExamplesData(enhancedResponse.Examples)
if err != nil {
return nil, err
}
}

// Generate response schema from input.
schemaRef, err := oai.getResponseSchemaRef(refInput)
if err != nil {
Expand Down
101 changes: 63 additions & 38 deletions net/goai/goai_z_unit_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/net/goai"
Expand Down Expand Up @@ -119,71 +120,97 @@ func Test_Issue3135(t *testing.T) {
})
}

type Issue3747CommonRes struct {
type Issue3889CommonRes struct {
g.Meta `mime:"application/json"`
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}

type Issue3747Req struct {
type Issue3889Req struct {
g.Meta `path:"/default" method:"post"`
Name string
}
type Issue3747Res struct {
g.Meta `status:"201" resEg:"testdata/Issue3747JsonFile/201.json"`
type Issue3889Res struct {
g.Meta `status:"201" resEg:"testdata/Issue3889JsonFile/201.json"`
Info string `json:"info" eg:"Created!"`
}

// Example case
type Issue3747Res401 struct {
g.Meta `resEg:"testdata/Issue3747JsonFile/401.json"`
}
type Issue3889Res401 struct{}

// Override case 1
type Issue3747Res402 struct {
type Issue3889Res402 struct {
g.Meta `mime:"application/json"`
}

// Override case 2
type Issue3747Res403 struct {
type Issue3889Res403 struct {
Code int `json:"code"`
Message string `json:"message"`
}

// Common response case
type Issue3747Res404 struct{}

func (r Issue3747Res) ResponseStatusMap() map[goai.StatusCode]any {
return map[goai.StatusCode]any{
401: Issue3747Res401{},
402: Issue3747Res402{},
403: Issue3747Res403{},
404: Issue3747Res404{},
405: struct{}{},
407: interface{}(nil),
406: nil,
type Issue3889Res404 struct{}

var Issue3889ErrorRes = map[int][]gcode.Code{
401: {
gcode.New(1, "Aha, 401 - 1", nil),
gcode.New(2, "Aha, 401 - 2", nil),
},
}

func (r Issue3889Res) EnhanceResponseStatus() map[goai.EnhancedStatusCode]goai.EnhancedStatusType {
Codes401 := Issue3889ErrorRes[401]
// iterate Codes401 to generate Examples
var Examples401 []interface{}
for _, code := range Codes401 {
example := Issue3889CommonRes{
Code: code.Code(),
Message: code.Message(),
Data: nil,
}
Examples401 = append(Examples401, example)
}
return map[goai.EnhancedStatusCode]goai.EnhancedStatusType{
401: {
Response: Issue3889Res401{},
Examples: Examples401,
},
402: {
Response: Issue3889Res402{},
},
403: {
Response: Issue3889Res403{},
},
404: {
Response: Issue3889Res404{},
},
500: {
Response: struct{}{},
},
501: {},
}
}

type Issue3747 struct{}
type Issue3889 struct{}

func (Issue3747) Default(ctx context.Context, req *Issue3747Req) (res *Issue3747Res, err error) {
res = &Issue3747Res{}
func (Issue3889) Default(ctx context.Context, req *Issue3889Req) (res *Issue3889Res, err error) {
res = &Issue3889Res{}
return
}

// https://github.com/gogf/gf/issues/3747
func Test_Issue3747(t *testing.T) {
// https://github.com/gogf/gf/issues/3889
func Test_Issue3889(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := g.Server(guid.S())
openapi := s.GetOpenApi()
openapi.Config.CommonResponse = Issue3747CommonRes{}
openapi.Config.CommonResponse = Issue3889CommonRes{}
openapi.Config.CommonResponseDataField = `Data`
s.Use(ghttp.MiddlewareHandlerResponse)
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(Issue3747),
new(Issue3889),
)
})
s.SetLogger(nil)
Expand All @@ -205,29 +232,27 @@ func Test_Issue3747(t *testing.T) {
t.AssertNE(j.Get(`paths./default.post.responses.402`).String(), "")
t.AssertNE(j.Get(`paths./default.post.responses.403`).String(), "")
t.AssertNE(j.Get(`paths./default.post.responses.404`).String(), "")
t.AssertNE(j.Get(`paths./default.post.responses.405`).String(), "")
t.Assert(j.Get(`paths./default.post.responses.406`).String(), "")
t.Assert(j.Get(`paths./default.post.responses.407`).String(), "")

t.AssertNE(j.Get(`paths./default.post.responses.500`).String(), "")
t.Assert(j.Get(`paths./default.post.responses.501`).String(), "")
// Check content
commonResponseSchema := `{"properties":{"code":{"format":"int","type":"integer"},"data":{"properties":{},"type":"object"},"message":{"format":"string","type":"string"}},"type":"object"}`
Status201ExamplesContent := `{"code 1":{"value":{"code":1,"data":"Good","message":"Aha, 201 - 1"}},"code 2":{"value":{"code":2,"data":"Not Bad","message":"Aha, 201 - 2"}}}`
Status401ExamplesContent := `{"example 1":{"value":{"code":1,"data":null,"message":"Aha, 401 - 1"}},"example 2":{"value":{"code":2,"data":null,"message":"Aha, 401 - 2"}}}`
Status402SchemaContent := `{"$ref":"#/components/schemas/github.com.gogf.gf.v2.net.goai_test.Issue3747Res402"}`
Issue3747Res403Ref := `{"$ref":"#/components/schemas/github.com.gogf.gf.v2.net.goai_test.Issue3747Res403"}`
Status402SchemaContent := `{"$ref":"#/components/schemas/github.com.gogf.gf.v2.net.goai_test.Issue3889Res402"}`
Issue3889Res403Ref := `{"$ref":"#/components/schemas/github.com.gogf.gf.v2.net.goai_test.Issue3889Res403"}`

t.Assert(j.Get(`paths./default.post.responses.201.content.application/json.examples`).String(), Status201ExamplesContent)
t.Assert(j.Get(`paths./default.post.responses.401.content.application/json.examples`).String(), Status401ExamplesContent)
t.Assert(j.Get(`paths./default.post.responses.402.content.application/json.schema`).String(), Status402SchemaContent)
t.Assert(j.Get(`paths./default.post.responses.403.content.application/json.schema`).String(), Issue3747Res403Ref)
t.Assert(j.Get(`paths./default.post.responses.403.content.application/json.schema`).String(), Issue3889Res403Ref)
t.Assert(j.Get(`paths./default.post.responses.404.content.application/json.schema`).String(), commonResponseSchema)
t.Assert(j.Get(`paths./default.post.responses.405.content.application/json.schema`).String(), commonResponseSchema)
t.Assert(j.Get(`paths./default.post.responses.500.content.application/json.schema`).String(), commonResponseSchema)

api := s.GetOpenApi()
reqPath := "github.com.gogf.gf.v2.net.goai_test.Issue3747Res403"
reqPath := "github.com.gogf.gf.v2.net.goai_test.Issue3889Res403"
schema := api.Components.Schemas.Get(reqPath).Value

Issue3747Res403Schema := `{"properties":{"code":{"format":"int","type":"integer"},"message":{"format":"string","type":"string"}},"type":"object"}`
t.Assert(schema, Issue3747Res403Schema)
Issue3889Res403Schema := `{"properties":{"code":{"format":"int","type":"integer"},"message":{"format":"string","type":"string"}},"type":"object"}`
t.Assert(schema, Issue3889Res403Schema)
})
}
12 changes: 0 additions & 12 deletions net/goai/testdata/Issue3747JsonFile/401.json

This file was deleted.

Loading