#2 - add swaggerset
This commit is contained in:
81
pkg/swaggerset/swagger.yaml
Normal file
81
pkg/swaggerset/swagger.yaml
Normal file
@@ -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
|
79
pkg/swaggerset/swaggerset.go
Normal file
79
pkg/swaggerset/swaggerset.go
Normal file
@@ -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
|
||||
}
|
36
pkg/swaggerset/swaggerset_test.go
Normal file
36
pkg/swaggerset/swaggerset_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
160
pkg/swaggerset/util.go
Normal file
160
pkg/swaggerset/util.go
Normal file
@@ -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, "")
|
||||
}
|
Reference in New Issue
Block a user