From fb1ae47d6fb32e548b3a074891c6994bbc670326 Mon Sep 17 00:00:00 2001 From: Gorbunov Kirill Andreevich Date: Fri, 29 Nov 2024 19:58:18 +0300 Subject: [PATCH 1/3] #2 - add swaggerset --- go.mod | 9 ++ go.sum | 28 +++++- pkg/swaggerset/swagger.yaml | 81 +++++++++++++++ pkg/swaggerset/swaggerset.go | 79 +++++++++++++++ pkg/swaggerset/swaggerset_test.go | 36 +++++++ pkg/swaggerset/util.go | 160 ++++++++++++++++++++++++++++++ 6 files changed, 391 insertions(+), 2 deletions(-) create mode 100644 pkg/swaggerset/swagger.yaml create mode 100644 pkg/swaggerset/swaggerset.go create mode 100644 pkg/swaggerset/swaggerset_test.go create mode 100644 pkg/swaggerset/util.go diff --git a/go.mod b/go.mod index d284214..6aee667 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module go.unistack.org/servicechecker go 1.23.3 require ( + github.com/getkin/kin-openapi v0.128.0 github.com/go-co-op/gocron/v2 v2.12.3 github.com/google/gnostic v0.7.0 github.com/google/uuid v1.6.0 + github.com/ompluscator/dynamic-struct v1.4.0 github.com/stretchr/testify v1.9.0 go.unistack.org/micro-client-grpc/v3 v3.11.10 go.unistack.org/micro-client-http/v3 v3.9.14 @@ -22,8 +24,15 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect + github.com/invopop/yaml v0.3.1 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/valyala/fastrand v1.1.0 // indirect diff --git a/go.sum b/go.sum index 34360c3..9188338 100644 --- a/go.sum +++ b/go.sum @@ -660,6 +660,8 @@ github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/getkin/kin-openapi v0.128.0 h1:jqq3D9vC9pPq1dGcOCv7yOp1DaEe7c/T1vzcLbITSp4= +github.com/getkin/kin-openapi v0.128.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-co-op/gocron/v2 v2.12.3 h1:3JkKjkFoAPp/i0YE+sonlF5gi+xnBChwYh75nX16MaE= github.com/go-co-op/gocron/v2 v2.12.3/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w= @@ -673,8 +675,14 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -790,8 +798,12 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +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= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -805,8 +817,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -814,12 +827,20 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/ompluscator/dynamic-struct v1.4.0 h1:I/Si9LZtItSwiTMe7vosEuIu2TKdOvWbE3R/lokpN4Q= +github.com/ompluscator/dynamic-struct v1.4.0/go.mod h1:ADQ1+6Ox1D+ntuNwTHyl1NvpAqY2lBXPSPbcO4CJdeA= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -840,8 +861,9 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= @@ -865,6 +887,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8= github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= diff --git a/pkg/swaggerset/swagger.yaml b/pkg/swaggerset/swagger.yaml new file mode 100644 index 0000000..56dade6 --- /dev/null +++ b/pkg/swaggerset/swagger.yaml @@ -0,0 +1,81 @@ +openapi: 3.0.3 +info: + title: platform/services/domain/service-proto + description: Domain Service + version: 3.6.0 +paths: + /domain-service/v1/push_mail/enabled: + get: + tags: + - DomainService + description: Получение статуса подключения PUSH (глобального) + operationId: IsPushTokenEnabled + parameters: + - name: Phone + in: header + schema: + type: string + - name: app_name + in: query + schema: + type: string + - name: device_id.value + in: query + description: The string value. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/IsPushTokenEnabledRsp' + post: + tags: + - DomainService + description: Сохранение статуса подключения PUSH (глобального) + operationId: SetPushTokenEnabled + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SetPushTokenEnabledReq' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SetPushTokenEnabledRsp' +components: + schemas: + IsPushTokenEnabledRsp: + type: object + properties: + result: + type: boolean + setPushTokenEnabledRsp: + $ref: '#/components/schemas/SetPushTokenEnabledRsp' + SetPushTokenEnabledReq: + type: object + properties: + app_name: + type: string + enabled: + type: boolean + device_id: + $ref: '#/components/schemas/StringValue' + StringValue: + type: object + properties: + value: + type: string + description: The string value. + description: Wrapper message for `string`. The JSON representation for `StringValue` is JSON string. + SetPushTokenEnabledRsp: + type: object + properties: + result: + type: boolean \ No newline at end of file diff --git a/pkg/swaggerset/swaggerset.go b/pkg/swaggerset/swaggerset.go new file mode 100644 index 0000000..167fc0e --- /dev/null +++ b/pkg/swaggerset/swaggerset.go @@ -0,0 +1,79 @@ +package swaggerset + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/getkin/kin-openapi/openapi3" +) + +var errNotFound = errors.New("file descriptor not found") + +type SwaggerSet struct { + mu sync.Mutex + files map[string]*openapi3.T +} + +func NewSwaggerSet() *SwaggerSet { + return &SwaggerSet{ + mu: sync.Mutex{}, + files: make(map[string]*openapi3.T, 0), + } +} + +func (p *SwaggerSet) GetMessage(addr, svc, mth, typereq string) (*messages, error) { + if svc == "" || mth == "" || addr == "" || typereq == "" { + return nil, errors.New("addr or service name is empty") + } + + messages := newMessages() + + p.mu.Lock() + doc := p.files[addr+"|"+svc] + p.mu.Unlock() + + pathItem := doc.Paths.Value(mth) + if pathItem.Get != nil { + reqParam, reqBody, rsp := handleOperation("GET", pathItem.Get) + messages.Msgs = append(messages.Msgs, message{ + Type: "GET", + RequestParam: reqParam, + RequestBody: reqBody, + Response: rsp, + }) + } + if pathItem.Post != nil { + reqParam, reqBody, rsp := handleOperation("POST", pathItem.Post) + messages.Msgs = append(messages.Msgs, message{ + Type: "POST", + RequestParam: reqParam, + RequestBody: reqBody, + Response: rsp, + }) + } + + return messages, nil +} + +func (p *SwaggerSet) AddSwaggerset(addr, svc string, data []byte) error { + ctx := context.Background() + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + + doc, err := loader.LoadFromData(data) + if err != nil { + return fmt.Errorf("failed to load data from buf: %w", err) + } + + if err = doc.Validate(ctx); err != nil { + return fmt.Errorf("failed to validate data from swagger: %w", err) + } + + p.mu.Lock() + p.files[addr+"|"+svc] = doc + p.mu.Unlock() + return nil +} diff --git a/pkg/swaggerset/swaggerset_test.go b/pkg/swaggerset/swaggerset_test.go new file mode 100644 index 0000000..ec80c78 --- /dev/null +++ b/pkg/swaggerset/swaggerset_test.go @@ -0,0 +1,36 @@ +package swaggerset + +import ( + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSwaggerSet_1(t *testing.T) { + s := NewSwaggerSet() + data, err := os.ReadFile("swagger.yaml") + assert.Nil(t, err) + + err = s.AddSwaggerset("localhost:8080", "service", data) + assert.Nil(t, err) + + msgs, err := s.GetMessage( + "localhost:8080", + "service", + "/domain-service/v1/push_mail/enabled", + "POST") + assert.Nil(t, err) + assert.NotNil(t, msgs.Msgs) + for _, msg := range msgs.Msgs { + reqParam, err := json.Marshal(msg.RequestParam) + assert.Nil(t, err) + reqBody, err := json.Marshal(msg.RequestBody) + assert.Nil(t, err) + rsp, err := json.Marshal(msg.Response) + assert.Nil(t, err) + fmt.Printf("type: %s, reqParam: %s, reqBody: %s, rsp: %s \n", msg.Type, reqParam, reqBody, rsp) + } +} diff --git a/pkg/swaggerset/util.go b/pkg/swaggerset/util.go new file mode 100644 index 0000000..cb2049c --- /dev/null +++ b/pkg/swaggerset/util.go @@ -0,0 +1,160 @@ +package swaggerset + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + dynamicstruct "github.com/ompluscator/dynamic-struct" +) + +type messages struct { + Msgs []message +} + +func newMessages() *messages { + return &messages{ + Msgs: make([]message, 0), + } +} + +type message struct { + Type string + RequestParam interface{} + RequestBody interface{} + Response interface{} +} + +// Обработка операции (GET или POST) +func handleOperation(method string, operation *openapi3.Operation) (reqParam, reqBody, rsp interface{}) { + fmt.Printf(" Method: %s\n", method) + + // Обработка параметров (GET) + if len(operation.Parameters) > 0 { + paramsStruct := dynamicstruct.NewStruct() + for _, paramRef := range operation.Parameters { + param := paramRef.Value + fieldName := capitalize(param.Name) + goType := getGoType(param.Schema) + + // В зависимости от того, где параметр находится (header, query, path, etc.), добавляем соответствующий тег + switch param.In { + case "query": + paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s" query:"%s"`, param.Name, param.Name)) + case "header": + paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s" header:"%s"`, param.Name, param.Name)) + default: + paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s"`, param.Name)) + } + } + requestStruct := paramsStruct.Build().New() + fmt.Printf(" Request (Parameters): %+v\n", requestStruct) + buf, _ := json.Marshal(requestStruct) + fmt.Printf(" Request (Parameters) JSON: %s\n", buf) + reqParam = requestStruct + } + + // Обработка тела запроса (POST) + if operation.RequestBody != nil { + for _, content := range operation.RequestBody.Value.Content { + bodyFields := buildDynamicStruct(content.Schema) + bodyStruct := reflect.StructOf(bodyFields) + bodyInstance := reflect.New(bodyStruct).Interface() + fmt.Printf(" Request (Body): %+v\n", bodyInstance) + buf, _ := json.Marshal(bodyInstance) + fmt.Printf(" Request (Body) JSON: %s\n", buf) + reqBody = bodyInstance + } + } + + // Обработка ответов + for status, response := range operation.Responses.Map() { + fmt.Printf(" Response Code: %s\n", status) + for _, content := range response.Value.Content { + responseFields := buildDynamicStruct(content.Schema) + responseStruct := reflect.StructOf(responseFields) + responseInstance := reflect.New(responseStruct).Interface() + fmt.Printf(" Response: %+v\n", responseInstance) + buf, _ := json.Marshal(responseInstance) + fmt.Printf(" Response JSON: %s\n", buf) + rsp = responseInstance + } + } + + return +} + +// Рекурсивное создание структуры из схемы с учетом $ref +func buildDynamicStruct(schema *openapi3.SchemaRef) []reflect.StructField { + var builder []reflect.StructField + fmt.Println("ref: ", schema.Ref) + + if len(schema.Value.Properties) == 0 { + return builder + } + + for name, prop := range schema.Value.Properties { + fieldName := capitalize(name) + if prop.Ref != "" || prop.Value.Type.Is("object") { + subBuilder := buildDynamicStruct(prop) + + sfield := reflect.StructField{ + Name: fieldName, + Type: reflect.StructOf(subBuilder), + Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, name)), + } + + builder = append(builder, sfield) + } else { + sfield := reflect.StructField{ + Name: fieldName, + Type: reflect.TypeOf(getGoType(prop)), + Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, name)), + } + + builder = append(builder, sfield) + } + } + + return builder +} + +// Преобразование типа OpenAPI в тип Go +func getGoType(schema *openapi3.SchemaRef) interface{} { + switch { + case schema.Value.Type.Is("string"): + return "" + case schema.Value.Type.Is("integer"): + return 0 + case schema.Value.Type.Is("boolean"): + return false + case schema.Value.Type.Is("array"): + return []interface{}{} + case schema.Value.Type.Is("object"): + return buildDynamicStruct(schema) + default: + return nil + } +} + +func capitalize(fieldName string) string { + // Заменяем точки на подчеркивания для унификации + fieldName = strings.ReplaceAll(fieldName, ".", "_") + + // Разделяем строку по подчеркиваниям + parts := strings.Split(fieldName, "_") + if len(parts) == 1 { + return strings.ToUpper(fieldName[:1]) + fieldName[1:] + } + + // Обрабатываем каждый фрагмент + for i := 0; i < len(parts); i++ { + // Капитализируем первые буквы всех частей, кроме первой + parts[i] = strings.Title(parts[i]) + } + + // Собираем строку обратно, соединяя части без подчеркиваний + return strings.Join(parts, "") +} -- 2.45.2 From e207117ba4391f1c71ff5f71dbd2bf65bdd2f1ef Mon Sep 17 00:00:00 2001 From: Gorbunov Kirill Andreevich Date: Sat, 30 Nov 2024 19:30:46 +0300 Subject: [PATCH 2/3] #2 - add swaggerset --- cmd/servicechecker/main.go | 20 ++++++++--- pkg/swaggerset/swaggerset.go | 31 ++++++----------- pkg/swaggerset/swaggerset_test.go | 5 +-- pkg/swaggerset/util.go | 57 ++++++++++++------------------- 4 files changed, 49 insertions(+), 64 deletions(-) diff --git a/cmd/servicechecker/main.go b/cmd/servicechecker/main.go index fc40ce5..6455cbd 100644 --- a/cmd/servicechecker/main.go +++ b/cmd/servicechecker/main.go @@ -7,7 +7,6 @@ import ( "os/signal" "time" - openapi_v3 "github.com/google/gnostic/openapiv3" "github.com/google/uuid" grpccli "go.unistack.org/micro-client-grpc/v3" httpcli "go.unistack.org/micro-client-http/v3" @@ -31,6 +30,7 @@ import ( "go.unistack.org/servicechecker/pkg/httpconn" "go.unistack.org/servicechecker/pkg/protoset" "go.unistack.org/servicechecker/pkg/scheduler" + "go.unistack.org/servicechecker/pkg/swaggerset" ) var ( @@ -213,8 +213,10 @@ func newHTTPTask(ctx context.Context, l logger.Logger, m meter.Meter, check stri var treq client.Request var opts []client.CallOption var labels []string + swaggerSet := swaggerset.NewSwaggerSet() if task.HTTP.OpenAPI != "" { + var svc string openapiBuf, err := os.ReadFile(task.HTTP.OpenAPI) if err != nil { @@ -222,12 +224,11 @@ func newHTTPTask(ctx context.Context, l logger.Logger, m meter.Meter, check stri return nil, nil, err } - doc, err := openapi_v3.ParseDocument(openapiBuf) + err = swaggerSet.AddSwaggerset(task.HTTP.Addr, svc, openapiBuf) if err != nil { - l.Error(ctx, "failed to unmarshal openapi file", err) + l.Error(ctx, "failed to add openApi spec", err) return nil, nil, err } - _ = doc errmap := make(map[string]interface{}, 1) errmap["default"] = &codecpb.Frame{} @@ -239,8 +240,17 @@ func newHTTPTask(ctx context.Context, l logger.Logger, m meter.Meter, check stri // client.WithContentType("application/json"), } + msg, err := swaggerSet.GetMessage(task.HTTP.Addr, svc, task.HTTP.Method, "POST") + if err != nil { + l.Error(ctx, "failed to get message from swagger spec", err) + return nil, nil, err + } + req = &codecpb.Frame{Data: []byte(task.HTTP.Data)} - rsp = &codecpb.Frame{} + if task.HTTP.Data == "" { + req = msg.Request + } + rsp = msg.Response treq = c.NewRequest(task.Name, task.Name, req) diff --git a/pkg/swaggerset/swaggerset.go b/pkg/swaggerset/swaggerset.go index 167fc0e..d3553ca 100644 --- a/pkg/swaggerset/swaggerset.go +++ b/pkg/swaggerset/swaggerset.go @@ -23,38 +23,27 @@ func NewSwaggerSet() *SwaggerSet { } } -func (p *SwaggerSet) GetMessage(addr, svc, mth, typereq string) (*messages, error) { +func (p *SwaggerSet) GetMessage(addr, svc, mth string, typereq string) (*Message, error) { if svc == "" || mth == "" || addr == "" || typereq == "" { return nil, errors.New("addr or service name is empty") } - messages := newMessages() - p.mu.Lock() doc := p.files[addr+"|"+svc] p.mu.Unlock() pathItem := doc.Paths.Value(mth) - if pathItem.Get != nil { - reqParam, reqBody, rsp := handleOperation("GET", pathItem.Get) - messages.Msgs = append(messages.Msgs, message{ - Type: "GET", - RequestParam: reqParam, - RequestBody: reqBody, - Response: rsp, - }) - } - if pathItem.Post != nil { - reqParam, reqBody, rsp := handleOperation("POST", pathItem.Post) - messages.Msgs = append(messages.Msgs, message{ - Type: "POST", - RequestParam: reqParam, - RequestBody: reqBody, - Response: rsp, - }) + reqParam, reqBody, rsp := handleOperation(typereq, pathItem.Get) + msg := &Message{ + Type: typereq, + Request: httpRequest{ + Header: reqParam, + Body: reqBody, + }, + Response: rsp, } - return messages, nil + return msg, nil } func (p *SwaggerSet) AddSwaggerset(addr, svc string, data []byte) error { diff --git a/pkg/swaggerset/swaggerset_test.go b/pkg/swaggerset/swaggerset_test.go index ec80c78..cd97ced 100644 --- a/pkg/swaggerset/swaggerset_test.go +++ b/pkg/swaggerset/swaggerset_test.go @@ -21,7 +21,7 @@ func TestSwaggerSet_1(t *testing.T) { "localhost:8080", "service", "/domain-service/v1/push_mail/enabled", - "POST") + []string{"POST", "GET"}) assert.Nil(t, err) assert.NotNil(t, msgs.Msgs) for _, msg := range msgs.Msgs { @@ -31,6 +31,7 @@ func TestSwaggerSet_1(t *testing.T) { assert.Nil(t, err) rsp, err := json.Marshal(msg.Response) assert.Nil(t, err) - fmt.Printf("type: %s, reqParam: %s, reqBody: %s, rsp: %s \n", msg.Type, reqParam, reqBody, rsp) + fmt.Printf("JSON: type: %s, reqParam: %s, reqBody: %s, rsp: %s \n", msg.Type, reqParam, reqBody, rsp) + fmt.Printf("Struct: type: %s, reqParam: %+v, reqBody: %+v, rsp: %+v \n", msg.Type, msg.RequestParam, msg.RequestBody, msg.Response) } } diff --git a/pkg/swaggerset/util.go b/pkg/swaggerset/util.go index cb2049c..2953f94 100644 --- a/pkg/swaggerset/util.go +++ b/pkg/swaggerset/util.go @@ -1,7 +1,6 @@ package swaggerset import ( - "encoding/json" "fmt" "reflect" "strings" @@ -10,24 +9,17 @@ import ( dynamicstruct "github.com/ompluscator/dynamic-struct" ) -type messages struct { - Msgs []message +type Message struct { + Type string + Request httpRequest + Response interface{} } -func newMessages() *messages { - return &messages{ - Msgs: make([]message, 0), - } +type httpRequest struct { + Header interface{} + Body interface{} } -type message struct { - Type string - RequestParam interface{} - RequestBody interface{} - Response interface{} -} - -// Обработка операции (GET или POST) func handleOperation(method string, operation *openapi3.Operation) (reqParam, reqBody, rsp interface{}) { fmt.Printf(" Method: %s\n", method) @@ -37,23 +29,21 @@ func handleOperation(method string, operation *openapi3.Operation) (reqParam, re for _, paramRef := range operation.Parameters { param := paramRef.Value fieldName := capitalize(param.Name) + jsonName := strings.ToLower(param.Name[:1]) + param.Name[1:] goType := getGoType(param.Schema) // В зависимости от того, где параметр находится (header, query, path, etc.), добавляем соответствующий тег switch param.In { case "query": - paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s" query:"%s"`, param.Name, param.Name)) + paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s" query:"%s"`, jsonName, jsonName)) case "header": - paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s" header:"%s"`, param.Name, param.Name)) + paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s" header:"%s"`, jsonName, jsonName)) default: - paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s"`, param.Name)) + paramsStruct = paramsStruct.AddField(fieldName, goType, fmt.Sprintf(`json:"%s"`, jsonName)) } } - requestStruct := paramsStruct.Build().New() - fmt.Printf(" Request (Parameters): %+v\n", requestStruct) - buf, _ := json.Marshal(requestStruct) - fmt.Printf(" Request (Parameters) JSON: %s\n", buf) - reqParam = requestStruct + // Получили структуру запроса для методов, где есть параметры в header, query, path, etc., добавили теги + reqParam = paramsStruct.Build().New() } // Обработка тела запроса (POST) @@ -62,9 +52,7 @@ func handleOperation(method string, operation *openapi3.Operation) (reqParam, re bodyFields := buildDynamicStruct(content.Schema) bodyStruct := reflect.StructOf(bodyFields) bodyInstance := reflect.New(bodyStruct).Interface() - fmt.Printf(" Request (Body): %+v\n", bodyInstance) - buf, _ := json.Marshal(bodyInstance) - fmt.Printf(" Request (Body) JSON: %s\n", buf) + // Получили тело запроса reqBody = bodyInstance } } @@ -76,9 +64,7 @@ func handleOperation(method string, operation *openapi3.Operation) (reqParam, re responseFields := buildDynamicStruct(content.Schema) responseStruct := reflect.StructOf(responseFields) responseInstance := reflect.New(responseStruct).Interface() - fmt.Printf(" Response: %+v\n", responseInstance) - buf, _ := json.Marshal(responseInstance) - fmt.Printf(" Response JSON: %s\n", buf) + // Получили структуру ответа rsp = responseInstance } } @@ -88,11 +74,10 @@ func handleOperation(method string, operation *openapi3.Operation) (reqParam, re // Рекурсивное создание структуры из схемы с учетом $ref func buildDynamicStruct(schema *openapi3.SchemaRef) []reflect.StructField { - var builder []reflect.StructField - fmt.Println("ref: ", schema.Ref) + var sfields []reflect.StructField if len(schema.Value.Properties) == 0 { - return builder + return sfields } for name, prop := range schema.Value.Properties { @@ -106,7 +91,7 @@ func buildDynamicStruct(schema *openapi3.SchemaRef) []reflect.StructField { Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, name)), } - builder = append(builder, sfield) + sfields = append(sfields, sfield) } else { sfield := reflect.StructField{ Name: fieldName, @@ -114,11 +99,11 @@ func buildDynamicStruct(schema *openapi3.SchemaRef) []reflect.StructField { Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, name)), } - builder = append(builder, sfield) + sfields = append(sfields, sfield) } } - return builder + return sfields } // Преобразование типа OpenAPI в тип Go @@ -152,7 +137,7 @@ func capitalize(fieldName string) string { // Обрабатываем каждый фрагмент for i := 0; i < len(parts); i++ { // Капитализируем первые буквы всех частей, кроме первой - parts[i] = strings.Title(parts[i]) + parts[i] = strings.Title(parts[i]) //cases.Title(language.English).String(parts[i]) } // Собираем строку обратно, соединяя части без подчеркиваний -- 2.45.2 From 8b6c5049b11eb31ffb5007effcb6d622a9985289 Mon Sep 17 00:00:00 2001 From: Gorbunov Kirill Andreevich Date: Wed, 4 Dec 2024 15:36:55 +0300 Subject: [PATCH 3/3] #2 - change to micro-proto/openapi --- cmd/servicechecker/main.go | 2 +- go.mod | 3 +- go.sum | 2 + pkg/swaggerset/swaggerset.go | 56 +++++++++------ pkg/swaggerset/swaggerset_test.go | 22 +++--- pkg/swaggerset/util.go | 110 +++++++++++++++++++++--------- 6 files changed, 126 insertions(+), 69 deletions(-) diff --git a/cmd/servicechecker/main.go b/cmd/servicechecker/main.go index 6455cbd..1a30316 100644 --- a/cmd/servicechecker/main.go +++ b/cmd/servicechecker/main.go @@ -240,7 +240,7 @@ func newHTTPTask(ctx context.Context, l logger.Logger, m meter.Meter, check stri // client.WithContentType("application/json"), } - msg, err := swaggerSet.GetMessage(task.HTTP.Addr, svc, task.HTTP.Method, "POST") + msg, err := swaggerSet.GetMessage(task.HTTP.Addr, svc, task.HTTP.Method) if err != nil { l.Error(ctx, "failed to get message from swagger spec", err) return nil, nil, err diff --git a/go.mod b/go.mod index 6aee667..aee1f3d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.23.3 require ( github.com/getkin/kin-openapi v0.128.0 github.com/go-co-op/gocron/v2 v2.12.3 - github.com/google/gnostic v0.7.0 github.com/google/uuid v1.6.0 github.com/ompluscator/dynamic-struct v1.4.0 github.com/stretchr/testify v1.9.0 @@ -17,6 +16,7 @@ require ( go.unistack.org/micro-codec-yaml/v3 v3.10.2 go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9 go.unistack.org/micro-proto/v3 v3.4.1 + go.unistack.org/micro-proto/v4 v4.1.0 go.unistack.org/micro-server-http/v3 v3.11.34 go.unistack.org/micro/v3 v3.10.100 google.golang.org/protobuf v1.35.2 @@ -26,6 +26,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/gnostic v0.7.0 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect diff --git a/go.sum b/go.sum index 9188338..f7f07a3 100644 --- a/go.sum +++ b/go.sum @@ -936,6 +936,8 @@ go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9 h1:ZXCS0eFiSdvcFYxpxV2Q77g go.unistack.org/micro-meter-victoriametrics/v3 v3.8.9/go.mod h1:xODJQ0Nu/F8k34D/z2ITL91OskI/C674XCkugAxmc3Q= go.unistack.org/micro-proto/v3 v3.4.1 h1:UTjLSRz2YZuaHk9iSlVqqsA50JQNAEK2ZFboGqtEa9Q= go.unistack.org/micro-proto/v3 v3.4.1/go.mod h1:okx/cnOhzuCX0ggl/vToatbCupi0O44diiiLLsZ93Zo= +go.unistack.org/micro-proto/v4 v4.1.0 h1:qPwL2n/oqh9RE3RTTDgt28XK3QzV597VugQPaw9lKUk= +go.unistack.org/micro-proto/v4 v4.1.0/go.mod h1:ArmK7o+uFvxSY3dbJhKBBX4Pm1rhWdLEFf3LxBrMtec= go.unistack.org/micro-server-http/v3 v3.11.34 h1:Cu44/IWyTBa0I1LIt8TZu/z130uDs2t/OZJwV5H5NX0= go.unistack.org/micro-server-http/v3 v3.11.34/go.mod h1:3hlTQj3e6HQziJa2Coz/BwvVbt3oRejOevyAPUCuBEk= go.unistack.org/micro/v3 v3.10.94/go.mod h1:erMgt3Bl7vQQ0e9UpQyR5NlLiZ9pKeEJ9+1tfYFaqUg= diff --git a/pkg/swaggerset/swaggerset.go b/pkg/swaggerset/swaggerset.go index d3553ca..d3f9752 100644 --- a/pkg/swaggerset/swaggerset.go +++ b/pkg/swaggerset/swaggerset.go @@ -1,30 +1,29 @@ package swaggerset import ( - "context" "errors" - "fmt" + "net/http" "sync" - "github.com/getkin/kin-openapi/openapi3" + openapi "go.unistack.org/micro-proto/v4/openapiv3" ) var errNotFound = errors.New("file descriptor not found") type SwaggerSet struct { mu sync.Mutex - files map[string]*openapi3.T + files map[string]*openapi.Document } func NewSwaggerSet() *SwaggerSet { return &SwaggerSet{ mu: sync.Mutex{}, - files: make(map[string]*openapi3.T, 0), + files: make(map[string]*openapi.Document, 0), } } -func (p *SwaggerSet) GetMessage(addr, svc, mth string, typereq string) (*Message, error) { - if svc == "" || mth == "" || addr == "" || typereq == "" { +func (p *SwaggerSet) GetMessage(addr, svc, mth string) (*Message, error) { + if svc == "" || mth == "" || addr == "" { return nil, errors.New("addr or service name is empty") } @@ -32,10 +31,34 @@ func (p *SwaggerSet) GetMessage(addr, svc, mth string, typereq string) (*Message doc := p.files[addr+"|"+svc] p.mu.Unlock() - pathItem := doc.Paths.Value(mth) - reqParam, reqBody, rsp := handleOperation(typereq, pathItem.Get) + var reqParam, reqBody, rsp interface{} + var typeReq string + for _, path := range doc.Paths.GetPath() { + if path.GetName() == mth { + if path.GetValue().GetGet() != nil { + typeReq = http.MethodGet + reqParam, reqBody, rsp = handleOperation(path.GetValue().GetGet(), doc) + } + if path.GetValue().GetPost() != nil { + typeReq = http.MethodPost + reqParam, reqBody, rsp = handleOperation(path.GetValue().GetPost(), doc) + } + if path.GetValue().GetDelete() != nil { + typeReq = http.MethodDelete + reqParam, reqBody, rsp = handleOperation(path.GetValue().GetDelete(), doc) + } + if path.GetValue().GetPatch() != nil { + typeReq = http.MethodPatch + reqParam, reqBody, rsp = handleOperation(path.GetValue().GetPatch(), doc) + } + if path.GetValue().GetPut() != nil { + typeReq = http.MethodPut + reqParam, reqBody, rsp = handleOperation(path.GetValue().GetPut(), doc) + } + } + } msg := &Message{ - Type: typereq, + Type: typeReq, Request: httpRequest{ Header: reqParam, Body: reqBody, @@ -47,18 +70,9 @@ func (p *SwaggerSet) GetMessage(addr, svc, mth string, typereq string) (*Message } func (p *SwaggerSet) AddSwaggerset(addr, svc string, data []byte) error { - ctx := context.Background() - - loader := openapi3.NewLoader() - loader.IsExternalRefsAllowed = true - - doc, err := loader.LoadFromData(data) + doc, err := openapi.ParseDocument(data) if err != nil { - return fmt.Errorf("failed to load data from buf: %w", err) - } - - if err = doc.Validate(ctx); err != nil { - return fmt.Errorf("failed to validate data from swagger: %w", err) + return err } p.mu.Lock() diff --git a/pkg/swaggerset/swaggerset_test.go b/pkg/swaggerset/swaggerset_test.go index cd97ced..513a39c 100644 --- a/pkg/swaggerset/swaggerset_test.go +++ b/pkg/swaggerset/swaggerset_test.go @@ -17,21 +17,17 @@ func TestSwaggerSet_1(t *testing.T) { err = s.AddSwaggerset("localhost:8080", "service", data) assert.Nil(t, err) - msgs, err := s.GetMessage( + msg, err := s.GetMessage( "localhost:8080", "service", "/domain-service/v1/push_mail/enabled", - []string{"POST", "GET"}) + "GET") assert.Nil(t, err) - assert.NotNil(t, msgs.Msgs) - for _, msg := range msgs.Msgs { - reqParam, err := json.Marshal(msg.RequestParam) - assert.Nil(t, err) - reqBody, err := json.Marshal(msg.RequestBody) - assert.Nil(t, err) - rsp, err := json.Marshal(msg.Response) - assert.Nil(t, err) - fmt.Printf("JSON: type: %s, reqParam: %s, reqBody: %s, rsp: %s \n", msg.Type, reqParam, reqBody, rsp) - fmt.Printf("Struct: type: %s, reqParam: %+v, reqBody: %+v, rsp: %+v \n", msg.Type, msg.RequestParam, msg.RequestBody, msg.Response) - } + assert.NotNil(t, msg) + req, err := json.Marshal(msg.Request) + assert.Nil(t, err) + rsp, err := json.Marshal(msg.Response) + assert.Nil(t, err) + fmt.Printf("JSON: type: %s, req: %s, rsp: %s \n", msg.Type, req, rsp) + fmt.Printf("Struct: type: %s, req: %+v, rsp: %+v \n", msg.Type, msg.Request, msg.Response) } diff --git a/pkg/swaggerset/util.go b/pkg/swaggerset/util.go index 2953f94..6291e66 100644 --- a/pkg/swaggerset/util.go +++ b/pkg/swaggerset/util.go @@ -5,8 +5,8 @@ import ( "reflect" "strings" - "github.com/getkin/kin-openapi/openapi3" dynamicstruct "github.com/ompluscator/dynamic-struct" + openapi "go.unistack.org/micro-proto/v4/openapiv3" ) type Message struct { @@ -20,17 +20,16 @@ type httpRequest struct { Body interface{} } -func handleOperation(method string, operation *openapi3.Operation) (reqParam, reqBody, rsp interface{}) { - fmt.Printf(" Method: %s\n", method) - +func handleOperation(operation *openapi.Operation, doc *openapi.Document) (reqParam, reqBody, rsp interface{}) { // Обработка параметров (GET) if len(operation.Parameters) > 0 { paramsStruct := dynamicstruct.NewStruct() for _, paramRef := range operation.Parameters { - param := paramRef.Value + var param *openapi.Parameter + param = paramRef.GetParameter() fieldName := capitalize(param.Name) jsonName := strings.ToLower(param.Name[:1]) + param.Name[1:] - goType := getGoType(param.Schema) + goType := getGoType(doc, param.Schema) // В зависимости от того, где параметр находится (header, query, path, etc.), добавляем соответствующий тег switch param.In { @@ -47,9 +46,17 @@ func handleOperation(method string, operation *openapi3.Operation) (reqParam, re } // Обработка тела запроса (POST) - if operation.RequestBody != nil { - for _, content := range operation.RequestBody.Value.Content { - bodyFields := buildDynamicStruct(content.Schema) + if operation.GetRequestBody() != nil { + if operation.GetRequestBody().GetRequestBody() != nil { + bodyFields := buildDynamicStruct(doc, operation.GetRequestBody().GetRequestBody().GetContent().GetAdditionalProperties()[0].GetValue().GetSchema()) + bodyStruct := reflect.StructOf(bodyFields) + bodyInstance := reflect.New(bodyStruct).Interface() + // Получили тело запроса + reqBody = bodyInstance + } + if operation.GetRequestBody().GetReference() != nil { + schemOrRef := findReference(doc, operation.GetRequestBody().GetReference().GetXRef()) + bodyFields := buildDynamicStruct(doc, schemOrRef) bodyStruct := reflect.StructOf(bodyFields) bodyInstance := reflect.New(bodyStruct).Interface() // Получили тело запроса @@ -58,10 +65,19 @@ func handleOperation(method string, operation *openapi3.Operation) (reqParam, re } // Обработка ответов - for status, response := range operation.Responses.Map() { - fmt.Printf(" Response Code: %s\n", status) - for _, content := range response.Value.Content { - responseFields := buildDynamicStruct(content.Schema) + for _, rspOrRef := range operation.Responses.ResponseOrReference { + if rspOrRef.GetValue().GetResponse() != nil { + for _, prop := range rspOrRef.Value.GetResponse().GetContent().GetAdditionalProperties() { + responseFields := buildDynamicStruct(doc, prop.GetValue().GetSchema()) + responseStruct := reflect.StructOf(responseFields) + responseInstance := reflect.New(responseStruct).Interface() + // Получили структуру ответа + rsp = responseInstance + } + } + if rspOrRef.GetValue().GetReference() != nil { + schemaOrRef := findReference(doc, rspOrRef.GetValue().GetReference().GetXRef()) + responseFields := buildDynamicStruct(doc, schemaOrRef) responseStruct := reflect.StructOf(responseFields) responseInstance := reflect.New(responseStruct).Interface() // Получили структуру ответа @@ -73,30 +89,42 @@ func handleOperation(method string, operation *openapi3.Operation) (reqParam, re } // Рекурсивное создание структуры из схемы с учетом $ref -func buildDynamicStruct(schema *openapi3.SchemaRef) []reflect.StructField { +func buildDynamicStruct(doc *openapi.Document, schemaOrRef *openapi.SchemaOrReference) []reflect.StructField { var sfields []reflect.StructField - - if len(schema.Value.Properties) == 0 { - return sfields + var schema *openapi.Schema + if schemaOrRef.GetSchema() != nil { + schema = schemaOrRef.GetSchema() + } + if schemaOrRef.GetReference() != nil { + name := strings.Split(schemaOrRef.GetReference().GetXRef(), "#/components/schemas/")[1] + fieldName := capitalize(name) + subBuilder := buildDynamicStruct(doc, findReference(doc, schemaOrRef.GetReference().GetXRef())) + sfield := reflect.StructField{ + Name: fieldName, + Type: reflect.StructOf(subBuilder), + Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, name)), + } + sfields = append(sfields, sfield) } - for name, prop := range schema.Value.Properties { - fieldName := capitalize(name) - if prop.Ref != "" || prop.Value.Type.Is("object") { - subBuilder := buildDynamicStruct(prop) + for _, prop := range schema.GetProperties().GetAdditionalProperties() { + fieldName := capitalize(prop.GetName()) + if prop.GetValue().GetReference() != nil { + subBuilder := buildDynamicStruct(doc, prop.GetValue()) sfield := reflect.StructField{ Name: fieldName, Type: reflect.StructOf(subBuilder), - Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, name)), + Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, prop.GetName())), } sfields = append(sfields, sfield) - } else { + } + if prop.GetValue().GetSchema() != nil { sfield := reflect.StructField{ Name: fieldName, - Type: reflect.TypeOf(getGoType(prop)), - Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, name)), + Type: reflect.TypeOf(getGoType(doc, prop.GetValue())), + Tag: reflect.StructTag(fmt.Sprintf(`json:"%s"`, prop.GetName())), } sfields = append(sfields, sfield) @@ -106,25 +134,41 @@ func buildDynamicStruct(schema *openapi3.SchemaRef) []reflect.StructField { return sfields } +func findReference(doc *openapi.Document, ref string) *openapi.SchemaOrReference { + var result *openapi.SchemaOrReference + ref = strings.Split(ref, "#/components/schemas/")[1] + + for _, prop := range doc.Components.Schemas.GetAdditionalProperties() { + if prop.Name == ref { + result = prop.Value + } + } + + return result +} + // Преобразование типа OpenAPI в тип Go -func getGoType(schema *openapi3.SchemaRef) interface{} { - switch { - case schema.Value.Type.Is("string"): +func getGoType(doc *openapi.Document, schema *openapi.SchemaOrReference) interface{} { + switch schema.GetSchema().Type { + case "string": return "" - case schema.Value.Type.Is("integer"): + case "integer": return 0 - case schema.Value.Type.Is("boolean"): + case "boolean": return false - case schema.Value.Type.Is("array"): + case "array": return []interface{}{} - case schema.Value.Type.Is("object"): - return buildDynamicStruct(schema) + case "object": + return buildDynamicStruct(doc, schema) default: return nil } } func capitalize(fieldName string) string { + if fieldName == "" { + return fieldName + } // Заменяем точки на подчеркивания для унификации fieldName = strings.ReplaceAll(fieldName, ".", "_") -- 2.45.2