#2 - add swaggerset
This commit is contained in:
parent
82824cdfcc
commit
95acea00d2
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,3 +25,4 @@ config.yaml
|
||||
servicechecker
|
||||
*.protoset
|
||||
authn.yaml
|
||||
.idea
|
@ -29,11 +29,8 @@ import (
|
||||
"go.unistack.org/servicechecker/pkg/config"
|
||||
"go.unistack.org/servicechecker/pkg/grpcconn"
|
||||
"go.unistack.org/servicechecker/pkg/httpconn"
|
||||
"go.unistack.org/servicechecker/pkg/protoset"
|
||||
"go.unistack.org/servicechecker/pkg/scheduler"
|
||||
"google.golang.org/protobuf/reflect/protodesc"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
"google.golang.org/protobuf/types/dynamicpb"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -62,14 +59,12 @@ func main() {
|
||||
l.Fatal(ctx, "failed to init meter", err)
|
||||
}
|
||||
|
||||
meters["default"] = m
|
||||
f, err := os.Open("config.yaml")
|
||||
if err != nil {
|
||||
l.Fatal(ctx, "failed to open config", err)
|
||||
}
|
||||
defer f.Close()
|
||||
meters[uuid.Nil.String()] = m
|
||||
|
||||
cfg := &config.Config{}
|
||||
if err = cfg.Parse(f); err != nil {
|
||||
|
||||
l.Info(ctx, "try to load config")
|
||||
if err := config.Load(config.Filesytem, "config.yaml", cfg); err != nil {
|
||||
l.Fatal(ctx, "failed to open config", err)
|
||||
}
|
||||
|
||||
@ -133,7 +128,7 @@ func main() {
|
||||
client.Codec("application/grpc+json", jsonpbcodec.NewCodec()),
|
||||
client.ContentType("application/grpc"),
|
||||
client.Retries(0),
|
||||
// client.TLSConfig(&tls.Config{InsecureSkipVerify: true}),
|
||||
// client.TLSConfig(&tls.Config{InsecureSkipVerify: true}),
|
||||
)
|
||||
if err = gcli.Init(); err != nil {
|
||||
l.Fatal(ctx, "failed to init grpc client", err)
|
||||
@ -146,14 +141,14 @@ func main() {
|
||||
client.Codec("application/json", jsonpbcodec.NewCodec()),
|
||||
client.ContentType("application/json"),
|
||||
client.Retries(0),
|
||||
// client.TLSConfig(&tls.Config{InsecureSkipVerify: true}),
|
||||
// client.TLSConfig(&tls.Config{InsecureSkipVerify: true}),
|
||||
)
|
||||
if err = hcli.Init(); err != nil {
|
||||
l.Fatal(ctx, "failed to init http client", err)
|
||||
}
|
||||
clients["http"] = hcli
|
||||
|
||||
for _, check := range cfg.Checks {
|
||||
for _, check := range cfg.App.Checks {
|
||||
l.Info(ctx, fmt.Sprintf("check %#+v", check))
|
||||
if !check.Active {
|
||||
continue
|
||||
@ -161,7 +156,7 @@ func main() {
|
||||
|
||||
var mtr meter.Meter
|
||||
if !cfg.App.MultiUser {
|
||||
mtr = meters["default"]
|
||||
mtr = meters[uuid.Nil.String()]
|
||||
} else {
|
||||
if v, ok := meters[check.User]; ok && v != nil {
|
||||
mtr = v
|
||||
@ -185,6 +180,10 @@ func main() {
|
||||
fn, args, err = newGRPCTask(ctx, l, mtr, check.Name, task)
|
||||
case task.HTTP != nil:
|
||||
fn, args, err = newHTTPTask(ctx, l, mtr, check.Name, task)
|
||||
case task.GraphQL != nil:
|
||||
fn, args, err = newGraphQLTask(ctx, l, mtr, check.Name, task)
|
||||
default:
|
||||
err = fmt.Errorf("unknown task type")
|
||||
}
|
||||
if err != nil {
|
||||
l.Error(ctx, "failed to create task", err)
|
||||
@ -305,51 +304,28 @@ func newGRPCTask(ctx context.Context, l logger.Logger, m meter.Meter, check stri
|
||||
var rsp interface{}
|
||||
var treq client.Request
|
||||
var labels []string
|
||||
p := protoset.NewProtoSet()
|
||||
|
||||
if task.GRPC.Protoset != "" {
|
||||
pkg, svc, mth := grpcconn.ServiceMethod(task.GRPC.Endpoint)
|
||||
|
||||
protosetBuf, err := os.ReadFile(task.GRPC.Protoset)
|
||||
if err != nil {
|
||||
l.Error(ctx, "failed to unmarshal protoset file", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fdset := &descriptorpb.FileDescriptorSet{}
|
||||
if err = protocodec.NewCodec().Unmarshal(protosetBuf, fdset); err != nil {
|
||||
l.Error(ctx, "failed to unmarshal protoset file", err)
|
||||
if err = p.AddProtoset(task.GRPC.Addr, svc, protosetBuf); err != nil {
|
||||
l.Error(ctx, "failed add protoset", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pfileoptions := protodesc.FileOptions{AllowUnresolvable: true}
|
||||
pfiles, err := pfileoptions.NewFiles(fdset)
|
||||
req, rsp, err = p.GetMessage(task.GRPC.Addr, pkg, svc, mth)
|
||||
if err != nil {
|
||||
l.Error(ctx, "failed to use protoset file", err)
|
||||
l.Error(ctx, "failed get req, rsp from protoset", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkg, svc, mth := grpcconn.ServiceMethod(task.GRPC.Endpoint)
|
||||
pdesc, err := pfiles.FindDescriptorByName(protoreflect.FullName(pkg + "." + svc))
|
||||
if err != nil {
|
||||
l.Error(ctx, "failed to find service "+pkg+"."+svc)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sdesc, ok := pdesc.(protoreflect.ServiceDescriptor)
|
||||
if !ok {
|
||||
err = fmt.Errorf("failed to find service " + pkg + "." + svc)
|
||||
l.Error(ctx, "unable to find service in protoset", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
mdesc := sdesc.Methods().ByName(protoreflect.Name(mth))
|
||||
if mdesc == nil {
|
||||
err = fmt.Errorf("unknown method " + mth)
|
||||
l.Error(ctx, "failed to find method", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req = dynamicpb.NewMessageType(mdesc.Input()).New()
|
||||
rsp = dynamicpb.NewMessageType(mdesc.Output()).New()
|
||||
|
||||
if err = jsonpbcodec.NewCodec().Unmarshal([]byte(task.GRPC.Data), req); err != nil {
|
||||
l.Error(ctx, "failed to unmarshal", err)
|
||||
return nil, nil, err
|
||||
@ -401,3 +377,96 @@ func newGRPCTask(ctx context.Context, l logger.Logger, m meter.Meter, check stri
|
||||
|
||||
return fn, nil, nil
|
||||
}
|
||||
|
||||
func newGraphQLTask(ctx context.Context, l logger.Logger, m meter.Meter, check string, task *config.TaskConfig) (any, []any, error) {
|
||||
/*
|
||||
var err error
|
||||
|
||||
c, ok := clients["http"]
|
||||
if !ok {
|
||||
err = fmt.Errorf("unknown client http")
|
||||
l.Error(ctx, "failed to get client", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var req interface{}
|
||||
var rsp interface{}
|
||||
var treq client.Request
|
||||
var opts []client.CallOption
|
||||
var labels []string
|
||||
|
||||
if task.HTTP.OpenAPI != "" {
|
||||
|
||||
openapiBuf, err := os.ReadFile(task.HTTP.OpenAPI)
|
||||
if err != nil {
|
||||
l.Error(ctx, "failed to unmarshal openapi file", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
doc, err := openapi_v3.ParseDocument(openapiBuf)
|
||||
if err != nil {
|
||||
l.Error(ctx, "failed to unmarshal openapi file", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
_ = doc
|
||||
|
||||
errmap := make(map[string]interface{}, 1)
|
||||
errmap["default"] = &codecpb.Frame{}
|
||||
opts = []client.CallOption{
|
||||
httpcli.ErrorMap(errmap),
|
||||
httpcli.Method(task.HTTP.Method),
|
||||
httpcli.Path(task.HTTP.Endpoint),
|
||||
|
||||
// client.WithContentType("application/json"),
|
||||
}
|
||||
|
||||
req = &codecpb.Frame{Data: []byte(task.HTTP.Data)}
|
||||
rsp = &codecpb.Frame{}
|
||||
|
||||
treq = c.NewRequest(task.Name, task.Name, req)
|
||||
|
||||
labels = []string{"check", check, "task", task.Name, "service", task.Name, "endpoint", task.Name}
|
||||
}
|
||||
|
||||
fn := func() {
|
||||
var cerr error
|
||||
|
||||
metadata := make(map[string]string, len(task.HTTP.Metadata))
|
||||
var rquid string
|
||||
for k, v := range task.HTTP.Metadata {
|
||||
if k == "x-request-id" && v == "generate" {
|
||||
uid, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
l.Error(ctx, "failed to generate x-request-id", err)
|
||||
uid = uuid.Nil
|
||||
} else {
|
||||
v = uid.String()
|
||||
}
|
||||
}
|
||||
metadata[k] = v
|
||||
rquid = v
|
||||
}
|
||||
|
||||
l.Info(ctx, fmt.Sprintf("call %s.%s endpoint %s", treq.Service(), treq.Method(), treq.Endpoint()), "x-request-id", rquid)
|
||||
m.Counter(semconv.ClientRequestInflight, labels...).Inc()
|
||||
ts := time.Now()
|
||||
cerr = httpconn.Call(ctx, rquid, l, c, task.HTTP.Addr, time.Duration(task.Timeout),
|
||||
treq,
|
||||
rsp,
|
||||
append(opts, client.WithRequestMetadata(metadata))...,
|
||||
)
|
||||
te := time.Since(ts)
|
||||
m.Counter(semconv.ClientRequestInflight, labels...).Dec()
|
||||
|
||||
m.Summary(semconv.ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
|
||||
m.Histogram(semconv.ClientRequestDurationSeconds, labels...).Update(te.Seconds())
|
||||
|
||||
if cerr != nil {
|
||||
m.Counter(semconv.ClientRequestTotal, append(labels, "status", "failure")...).Inc()
|
||||
} else {
|
||||
m.Counter(semconv.ClientRequestTotal, append(labels, "status", "success")...).Inc()
|
||||
}
|
||||
}
|
||||
*/
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
14
go.mod
14
go.mod
@ -3,9 +3,12 @@ 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
|
||||
go.unistack.org/micro-codec-json/v3 v3.10.1
|
||||
@ -20,8 +23,17 @@ 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
|
||||
github.com/valyala/histogram v1.2.0 // indirect
|
||||
@ -29,7 +41,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f // indirect
|
||||
google.golang.org/grpc v1.68.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
32
go.sum
32
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=
|
||||
@ -1202,8 +1226,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -1,14 +1,33 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
yamlcodec "go.unistack.org/micro-codec-yaml/v3"
|
||||
mtime "go.unistack.org/micro/v3/util/time"
|
||||
)
|
||||
|
||||
var Filesytem fs.FS
|
||||
|
||||
func init() {
|
||||
dir, _ := os.Getwd()
|
||||
Filesytem = os.DirFS(dir)
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
App *AppConfig `json:"app,omitempty" yaml:"app,omitempty"`
|
||||
Meter *MeterConfig `json:"meter,omitempty" yaml:"meter,omitempty"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
MultiUser bool `json:"multi_user,omitempty" yaml:"multi_user,omitempty"`
|
||||
ChecksFiles []string `json:"checks_files,omitempty" yaml:"checks_files,omitempty"`
|
||||
Checks []*CheckConfig `json:"checks,omitempty" yaml:"checks,omitempty"`
|
||||
MultiUser bool `json:"multi_user,omitempty" yaml:"multi_user,omitempty"`
|
||||
}
|
||||
|
||||
type MeterConfig struct {
|
||||
@ -16,19 +35,14 @@ type MeterConfig struct {
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
App *AppConfig `json:"app,omitempty" yaml:"app,omitempty"`
|
||||
Meter *MeterConfig `json:"meter,omitempty" yaml:"meter,omitempty"`
|
||||
Checks []*CheckConfig `json:"checks,omitempty" yaml:"checks,omitempty"`
|
||||
}
|
||||
|
||||
type CheckConfig struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Tasks []*TaskConfig `json:"tasks,omitempty" yaml:"tasks,omitempty"`
|
||||
Timeout mtime.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
Interval mtime.Duration `json:"interval,omitempty" yaml:"interval,omitempty"`
|
||||
Active bool `json:"active,omitempty" yaml:"active,omitempty"`
|
||||
User string `json:"user,omitempty" yaml:"user,omitempty"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
User string `json:"user,omitempty" yaml:"user,omitempty"`
|
||||
Tasks []*TaskConfig `json:"tasks,omitempty" yaml:"tasks,omitempty"`
|
||||
TasksFiles []string `json:"tasks_files,omitempty" yaml:"tasks_files,omitempty"`
|
||||
Timeout mtime.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
|
||||
Interval mtime.Duration `json:"interval,omitempty" yaml:"interval,omitempty"`
|
||||
Active bool `json:"active,omitempty" yaml:"active,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
@ -65,11 +79,84 @@ type TaskConfig struct {
|
||||
Active bool `json:"active,omitempty" yaml:"active,omitempty"`
|
||||
}
|
||||
|
||||
func (cfg *Config) Parse(r io.Reader) error {
|
||||
buf, err := io.ReadAll(r)
|
||||
func Load(fileSystem fs.FS, name string, cfg *Config) error {
|
||||
if err := load(fileSystem, name, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, checksPatternFile := range cfg.App.ChecksFiles {
|
||||
checkRoot := filepath.Dir(checksPatternFile)
|
||||
checksFiles := fsWalkDir(fileSystem, checkRoot, checksPatternFile)
|
||||
for _, checkFile := range checksFiles {
|
||||
checks := []*CheckConfig{}
|
||||
if err := load(fileSystem, checkFile, &checks); err != nil {
|
||||
return err
|
||||
}
|
||||
for ckecksIdx := range checks {
|
||||
for _, tasksPatternFile := range checks[ckecksIdx].TasksFiles {
|
||||
taskRoot := filepath.Join(filepath.Dir(checksPatternFile), filepath.Dir(tasksPatternFile))
|
||||
tasksFiles := fsWalkDir(fileSystem, taskRoot, filepath.Join(filepath.Dir(checksPatternFile), tasksPatternFile))
|
||||
for tasksIdx := range tasksFiles {
|
||||
tasks := []*TaskConfig{}
|
||||
if err := load(fileSystem, tasksFiles[tasksIdx], &tasks); err != nil {
|
||||
return err
|
||||
}
|
||||
checks[ckecksIdx].Tasks = append(checks[ckecksIdx].Tasks, tasks...)
|
||||
}
|
||||
}
|
||||
cfg.App.Checks = append(cfg.App.Checks, checks[ckecksIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !cfg.App.MultiUser {
|
||||
for _, check := range cfg.App.Checks {
|
||||
check.User = uuid.Nil.String()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fsWalkDir(fileSystem fs.FS, root string, pattern string) []string {
|
||||
var files []string
|
||||
fs.WalkDir(fileSystem, root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() || !d.Type().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
var ok bool
|
||||
if ok, err = filepath.Match(pattern, path); err == nil && ok {
|
||||
files = append(files, path)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
return files
|
||||
}
|
||||
|
||||
func load(fileSystem fs.FS, name string, cfg interface{}) error {
|
||||
f, err := fileSystem.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return yamlcodec.NewCodec().Unmarshal(buf, cfg)
|
||||
c := yamlcodec.NewCodec()
|
||||
var buf []byte
|
||||
|
||||
if buf, err = io.ReadAll(f); err == nil {
|
||||
if err = f.Close(); err == nil {
|
||||
err = c.Unmarshal(buf, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load config %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -3,8 +3,8 @@
|
||||
package grpcconn
|
||||
|
||||
import (
|
||||
"github.com/emicklei/proto"
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var protoSets = map[string]*desc.FileDescriptor
|
||||
|
84
pkg/protoset/protoset.go
Normal file
84
pkg/protoset/protoset.go
Normal file
@ -0,0 +1,84 @@
|
||||
package protoset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
protocodec "go.unistack.org/micro-codec-proto/v3"
|
||||
"google.golang.org/protobuf/reflect/protodesc"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
"google.golang.org/protobuf/types/descriptorpb"
|
||||
"google.golang.org/protobuf/types/dynamicpb"
|
||||
)
|
||||
|
||||
var errNotFound = errors.New("file descriptor not found")
|
||||
|
||||
type ProtoSet struct {
|
||||
mu sync.Mutex
|
||||
files map[string]*protoregistry.Files
|
||||
}
|
||||
|
||||
func NewProtoSet() *ProtoSet {
|
||||
return &ProtoSet{
|
||||
mu: sync.Mutex{},
|
||||
files: make(map[string]*protoregistry.Files, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProtoSet) GetMessage(addr, pkg, svc, mth string) (protoreflect.Message, protoreflect.Message, error) {
|
||||
if addr == "" || svc == "" || mth == "" || pkg == "" {
|
||||
return nil, nil, errors.New("addr or service name is empty")
|
||||
}
|
||||
p.mu.Lock()
|
||||
pfile, ok := p.files[addr+"|"+svc]
|
||||
p.mu.Unlock()
|
||||
if !ok || pfile == nil {
|
||||
return nil, nil, errNotFound
|
||||
}
|
||||
|
||||
pdesc, err := pfile.FindDescriptorByName(protoreflect.FullName(pkg + "." + svc))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find service %s.%s, err: %w", pkg, svc, err)
|
||||
}
|
||||
|
||||
sdesc, ok := pdesc.(protoreflect.ServiceDescriptor)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("failed to find service " + pkg + "." + svc)
|
||||
}
|
||||
|
||||
mdesc := sdesc.Methods().ByName(protoreflect.Name(mth))
|
||||
if mdesc == nil {
|
||||
return nil, nil, fmt.Errorf("unknown method " + mth)
|
||||
}
|
||||
|
||||
req := dynamicpb.NewMessageType(mdesc.Input()).New()
|
||||
rsp := dynamicpb.NewMessageType(mdesc.Output()).New()
|
||||
|
||||
return req, rsp, nil
|
||||
}
|
||||
|
||||
func (p *ProtoSet) AddProtoset(addr, svc string, data []byte) error {
|
||||
fdset := &descriptorpb.FileDescriptorSet{}
|
||||
if err := protocodec.NewCodec().Unmarshal(data, fdset); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal protoset file: %w", err)
|
||||
}
|
||||
|
||||
pfileoptions := protodesc.FileOptions{AllowUnresolvable: true}
|
||||
pfiles, err := pfileoptions.NewFiles(fdset)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to use protoset file, err: %w", err)
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
p.files[addr+"|"+svc] = pfiles
|
||||
p.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ProtoSet) AddReflection(ctx context.Context, service string, addr string) error {
|
||||
return nil
|
||||
}
|
49
pkg/protoset/protoset_test.go
Normal file
49
pkg/protoset/protoset_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package protoset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProtoSet_1(t *testing.T) {
|
||||
p := NewProtoSet()
|
||||
data, err := os.ReadFile("path to .protoset")
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = p.AddProtoset("localhost:9090", "CardService", data)
|
||||
assert.Nil(t, err)
|
||||
|
||||
req, rsp, err := p.GetMessage("localhost:9090", "card_proto", "CardService", "GetCardList")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, req)
|
||||
assert.NotNil(t, rsp)
|
||||
fmt.Printf("req: %v, rsp: %v \n", req, rsp)
|
||||
}
|
||||
|
||||
func TestProtoSet_2_bad(t *testing.T) {
|
||||
p := NewProtoSet()
|
||||
data, err := os.ReadFile("path to .protoset")
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = p.AddProtoset("localhost:9090", "CardService", data)
|
||||
assert.Nil(t, err)
|
||||
|
||||
req, rsp, err := p.GetMessage("localhost:9090", "card_proto", "Card", "GetCardList")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, req)
|
||||
assert.Nil(t, rsp)
|
||||
fmt.Printf("req: %v, rsp: %v \n", req, rsp)
|
||||
}
|
||||
|
||||
func TestProtoSet_3_not_found(t *testing.T) {
|
||||
p := NewProtoSet()
|
||||
|
||||
req, rsp, err := p.GetMessage("localhost:9090", "card_proto", "CardService", "GetCardList")
|
||||
assert.ErrorIs(t, err, errNotFound)
|
||||
assert.Nil(t, req)
|
||||
assert.Nil(t, rsp)
|
||||
fmt.Printf("req: %v, rsp: %v \n", req, rsp)
|
||||
}
|
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
|
||||
}
|
40
pkg/swaggerset/swaggerset_test.go
Normal file
40
pkg/swaggerset/swaggerset_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwaggerSet_2(t *testing.T) {
|
||||
Run4()
|
||||
}
|
183
pkg/swaggerset/util.go
Normal file
183
pkg/swaggerset/util.go
Normal file
@ -0,0 +1,183 @@
|
||||
package swaggerset
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"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{}
|
||||
}
|
||||
|
||||
func Run4() {
|
||||
// Загружаем Swagger-документ
|
||||
loader := openapi3.NewLoader()
|
||||
doc, err := loader.LoadFromFile("swagger2.yaml")
|
||||
if err != nil {
|
||||
log.Fatalf("Ошибка загрузки Swagger-документа: %v", err)
|
||||
}
|
||||
|
||||
// Пример обработки путей
|
||||
for path, pathItem := range doc.Paths.Map() {
|
||||
fmt.Printf("Path: %s\n", path)
|
||||
|
||||
// Пример обработки методов
|
||||
if pathItem.Get != nil {
|
||||
handleOperation("GET", pathItem.Get)
|
||||
}
|
||||
if pathItem.Post != nil {
|
||||
handleOperation("POST", pathItem.Post)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка операции (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, "")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user