From b14348ed6874700a465ee4b18e67c68e7a986a6c Mon Sep 17 00:00:00 2001 From: Ehsan Noureddin Moosa Date: Fri, 29 Mar 2024 18:03:53 +0330 Subject: [PATCH] impl typescript stub generator --- example/ex-04-stubgen/api/service.go | 14 +- example/ex-04-stubgen/dto/msg.go | 6 + example/ex-04-stubgen/stub/gen.go | 14 +- .../ex-04-stubgen/stub/sampleservice/stub.go | 291 ++++++++++++++++++ .../stub/sampleservicets/stub.ts | 110 +++++++ kit/internal/tpl/tpl.go | 8 +- kit/internal/tpl/ts/stub.tstmpl | 241 +++++---------- kit/internal/tpl/util.go | 76 ++++- kit/stub/stubgen/gen.go | 4 - kit/stub/stubgen/gen_func.go | 4 + 10 files changed, 591 insertions(+), 177 deletions(-) create mode 100755 example/ex-04-stubgen/stub/sampleservice/stub.go create mode 100755 example/ex-04-stubgen/stub/sampleservicets/stub.ts diff --git a/example/ex-04-stubgen/api/service.go b/example/ex-04-stubgen/api/service.go index 8378a0ac..092bb482 100644 --- a/example/ex-04-stubgen/api/service.go +++ b/example/ex-04-stubgen/api/service.go @@ -18,8 +18,18 @@ var SampleDesc desc.ServiceDescFunc = func() *desc.Service { desc.NewContract(). SetInput(&dto.VeryComplexRequest{}). SetOutput(&dto.VeryComplexResponse{}). - NamedSelector("ComplexDummy", fasthttp.REST(http.MethodPost, "/complexDummy")). - NamedSelector("ComplexDummy2", fasthttp.REST(http.MethodPost, "/complexDummy/:key1")). + NamedSelector("ComplexDummy", fasthttp.POST("/complexDummy")). + NamedSelector("ComplexDummy2", fasthttp.POST("/complexDummy/:key1")). + AddModifier(func(envelope *kit.Envelope) { + envelope.SetHdr("X-Custom-Header", "justForTestingModifier") + }). + SetHandler(DummyHandler), + ). + AddContract( + desc.NewContract(). + SetInput(&dto.VeryComplexRequest{}). + SetOutput(&dto.VeryComplexResponse{}). + NamedSelector("GetComplexDummy", fasthttp.GET("/complexDummy/:key1/xs/:sKey1")). AddModifier(func(envelope *kit.Envelope) { envelope.SetHdr("X-Custom-Header", "justForTestingModifier") }). diff --git a/example/ex-04-stubgen/dto/msg.go b/example/ex-04-stubgen/dto/msg.go index 035d4781..bf5a7425 100644 --- a/example/ex-04-stubgen/dto/msg.go +++ b/example/ex-04-stubgen/dto/msg.go @@ -2,7 +2,13 @@ package dto import "encoding/json" +type SimpleHdr struct { + Key1 string `json:"sKey1"` + Key2 int `json:"sKey2"` +} + type VeryComplexRequest struct { + SimpleHdr Key1 string `json:"key1"` Key1Ptr *string `json:"key1Ptr"` MapKey1 map[string]int `json:"mapKey1"` diff --git a/example/ex-04-stubgen/stub/gen.go b/example/ex-04-stubgen/stub/gen.go index a8099be8..9fbcdbcb 100644 --- a/example/ex-04-stubgen/stub/gen.go +++ b/example/ex-04-stubgen/stub/gen.go @@ -15,11 +15,11 @@ func main() { stubgen.WithStubName("sampleService"), ).MustGenerate(api.SampleDesc) - //stubgen.New( - // stubgen.WithGenFunc(stubgen.TypeScriptStub, ".ts"), - // stubgen.WithTags("json"), - // stubgen.WithPkgName("sampleservice"), - // stubgen.WithFolderName("sampleservicets"), - // stubgen.WithStubName("sampleService"), - //).MustGenerate(api.SampleDesc) + stubgen.New( + stubgen.WithGenFunc(stubgen.TypeScriptStub, ".ts"), + stubgen.WithTags("json"), + stubgen.WithPkgName("sampleservice"), + stubgen.WithFolderName("sampleservicets"), + stubgen.WithStubName("sampleService"), + ).MustGenerate(api.SampleDesc) } diff --git a/example/ex-04-stubgen/stub/sampleservice/stub.go b/example/ex-04-stubgen/stub/sampleservice/stub.go new file mode 100755 index 00000000..f4671524 --- /dev/null +++ b/example/ex-04-stubgen/stub/sampleservice/stub.go @@ -0,0 +1,291 @@ +// Code generated by RonyKIT Stub Generator (Golang); DO NOT EDIT. + +package sampleservice + +import ( + "context" + "fmt" + + "github.com/clubpay/ronykit/kit" + "github.com/clubpay/ronykit/kit/stub" + "github.com/clubpay/ronykit/kit/utils/reflector" +) + +var _ fmt.Stringer + +func init() { + reflector.Register(&ErrorMessage{}, "json") + reflector.Register(&KeyValue{}, "json") + reflector.Register(&SimpleHdr{}, "json") + reflector.Register(&VeryComplexRequest{}, "json") + reflector.Register(&VeryComplexResponse{}, "json") +} + +// ErrorMessage is a data transfer object +type ErrorMessage struct { + Code int `json:"code"` + Item string `json:"item"` +} + +func (x ErrorMessage) GetCode() int { + return x.Code +} + +func (x ErrorMessage) GetItem() string { + return x.Item +} + +// KeyValue is a data transfer object +type KeyValue struct { + Key string `json:"key"` + Value int `json:"value"` +} + +// SimpleHdr is a data transfer object +type SimpleHdr struct { + Key1 string `json:"sKey1"` + Key2 int `json:"sKey2"` +} + +// VeryComplexRequest is a data transfer object +type VeryComplexRequest struct { + SimpleHdr + Key1 string `json:"key1"` + Key1Ptr *string `json:"key1Ptr"` + MapKey1 map[string]int `json:"mapKey1"` + MapKey2 map[int64]KeyValue `json:"mapKey2"` + SliceKey1 []bool `json:"sliceKey1"` + SliceKey2 []*KeyValue `json:"sliceKey2"` + RawKey kit.JSONMessage `json:"rawKey"` +} + +// VeryComplexResponse is a data transfer object +type VeryComplexResponse struct { + Key1 string `json:"key1"` + Key1Ptr *string `json:"key1Ptr"` + MapKey1 map[string]int `json:"mapKey1"` + MapKey2 map[int64]*KeyValue `json:"mapKey2"` + SliceKey1 []uint8 `json:"sliceKey1"` + SliceKey2 []KeyValue `json:"sliceKey2"` +} + +type IsampleServiceStub interface { + ComplexDummy( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, + ) (*VeryComplexResponse, *stub.Error) + ComplexDummy2( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, + ) (*VeryComplexResponse, *stub.Error) + GetComplexDummy( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, + ) (*VeryComplexResponse, *stub.Error) +} + +// sampleServiceStub represents the client/stub for sampleService. +// Implements IsampleServiceStub +type sampleServiceStub struct { + hostPort string + secure bool + verifyTLS bool + + s *stub.Stub +} + +func NewsampleServiceStub(hostPort string, opts ...stub.Option) *sampleServiceStub { + s := &sampleServiceStub{ + s: stub.New(hostPort, opts...), + } + + return s +} + +var _ IsampleServiceStub = (*sampleServiceStub)(nil) + +func (s sampleServiceStub) ComplexDummy( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, +) (*VeryComplexResponse, *stub.Error) { + res := &VeryComplexResponse{} + httpCtx := s.s.REST(opt...). + SetMethod("POST"). + SetResponseHandler( + 400, + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + res := &ErrorMessage{} + err := stub.WrapError(kit.UnmarshalMessage(r.GetBody(), res)) + if err != nil { + return err + } + + return stub.NewErrorWithMsg(res) + }, + ). + SetOKHandler( + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + return stub.WrapError(kit.UnmarshalMessage(r.GetBody(), res)) + }, + ). + DefaultResponseHandler( + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + return stub.NewError(r.StatusCode(), string(r.GetBody())) + }, + ). + AutoRun(ctx, "/complexDummy", kit.JSON, req) + defer httpCtx.Release() + + if err := httpCtx.Err(); err != nil { + return nil, err + } + + return res, nil +} + +func (s sampleServiceStub) ComplexDummy2( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, +) (*VeryComplexResponse, *stub.Error) { + res := &VeryComplexResponse{} + httpCtx := s.s.REST(opt...). + SetMethod("POST"). + SetResponseHandler( + 400, + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + res := &ErrorMessage{} + err := stub.WrapError(kit.UnmarshalMessage(r.GetBody(), res)) + if err != nil { + return err + } + + return stub.NewErrorWithMsg(res) + }, + ). + SetOKHandler( + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + return stub.WrapError(kit.UnmarshalMessage(r.GetBody(), res)) + }, + ). + DefaultResponseHandler( + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + return stub.NewError(r.StatusCode(), string(r.GetBody())) + }, + ). + AutoRun(ctx, "/complexDummy/{key1}", kit.JSON, req) + defer httpCtx.Release() + + if err := httpCtx.Err(); err != nil { + return nil, err + } + + return res, nil +} + +func (s sampleServiceStub) GetComplexDummy( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, +) (*VeryComplexResponse, *stub.Error) { + res := &VeryComplexResponse{} + httpCtx := s.s.REST(opt...). + SetMethod("GET"). + SetResponseHandler( + 400, + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + res := &ErrorMessage{} + err := stub.WrapError(kit.UnmarshalMessage(r.GetBody(), res)) + if err != nil { + return err + } + + return stub.NewErrorWithMsg(res) + }, + ). + SetOKHandler( + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + return stub.WrapError(kit.UnmarshalMessage(r.GetBody(), res)) + }, + ). + DefaultResponseHandler( + func(ctx context.Context, r stub.RESTResponse) *stub.Error { + return stub.NewError(r.StatusCode(), string(r.GetBody())) + }, + ). + AutoRun(ctx, "/complexDummy/{key1}/xs/{sKey1}", kit.JSON, req) + defer httpCtx.Release() + + if err := httpCtx.Err(); err != nil { + return nil, err + } + + return res, nil +} + +type MockOption func(*sampleServiceStubMock) + +func MockComplexDummy( + f func(ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption) (*VeryComplexResponse, *stub.Error), +) MockOption { + return func(sm *sampleServiceStubMock) { + sm.complexdummy = f + } +} + +func MockComplexDummy2( + f func(ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption) (*VeryComplexResponse, *stub.Error), +) MockOption { + return func(sm *sampleServiceStubMock) { + sm.complexdummy2 = f + } +} + +func MockGetComplexDummy( + f func(ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption) (*VeryComplexResponse, *stub.Error), +) MockOption { + return func(sm *sampleServiceStubMock) { + sm.getcomplexdummy = f + } +} + +// sampleServiceStubMock represents the mocked for client/stub for sampleService. +// Implements IsampleServiceStub +type sampleServiceStubMock struct { + complexdummy func(ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption) (*VeryComplexResponse, *stub.Error) + complexdummy2 func(ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption) (*VeryComplexResponse, *stub.Error) + getcomplexdummy func(ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption) (*VeryComplexResponse, *stub.Error) +} + +func NewsampleServiceStubMock(opts ...MockOption) *sampleServiceStubMock { + s := &sampleServiceStubMock{} + for _, o := range opts { + o(s) + } + + return s +} + +var _ IsampleServiceStub = (*sampleServiceStubMock)(nil) + +func (s sampleServiceStubMock) ComplexDummy( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, +) (*VeryComplexResponse, *stub.Error) { + if s.complexdummy == nil { + return nil, stub.WrapError(fmt.Errorf("method not mocked")) + } + + return s.complexdummy(ctx, req, opt...) +} + +func (s sampleServiceStubMock) ComplexDummy2( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, +) (*VeryComplexResponse, *stub.Error) { + if s.complexdummy2 == nil { + return nil, stub.WrapError(fmt.Errorf("method not mocked")) + } + + return s.complexdummy2(ctx, req, opt...) +} + +func (s sampleServiceStubMock) GetComplexDummy( + ctx context.Context, req *VeryComplexRequest, opt ...stub.RESTOption, +) (*VeryComplexResponse, *stub.Error) { + if s.getcomplexdummy == nil { + return nil, stub.WrapError(fmt.Errorf("method not mocked")) + } + + return s.getcomplexdummy(ctx, req, opt...) +} diff --git a/example/ex-04-stubgen/stub/sampleservicets/stub.ts b/example/ex-04-stubgen/stub/sampleservicets/stub.ts new file mode 100755 index 00000000..258d07e0 --- /dev/null +++ b/example/ex-04-stubgen/stub/sampleservicets/stub.ts @@ -0,0 +1,110 @@ +// Code generated by RonyKIT Stub Generator (TypeScript); DO NOT EDIT. + +// ErrorMessage is a data transfer object +export interface ErrorMessage { + code: number + item: string +} + +// KeyValue is a data transfer object +export interface KeyValue { + key: string + value: number +} + +// SimpleHdr is a data transfer object +export interface SimpleHdr { + sKey1: string + sKey2: number +} + +// VeryComplexRequest is a data transfer object +export interface VeryComplexRequest extends SimpleHdr { + key1: string + key1Ptr?: string + mapKey1: { [key: string]: number } + mapKey2: { [key: number]: KeyValue } + sliceKey1: boolean[] + sliceKey2: KeyValue[] + rawKey: any +} + +// VeryComplexResponse is a data transfer object +export interface VeryComplexResponse { + key1: string + key1Ptr?: string + mapKey1: { [key: string]: number } + mapKey2: { [key: number]: KeyValue } + sliceKey1: number[] + sliceKey2: KeyValue[] +} + + +class sampleServiceStub { + readonly serverURL: string; + + constructor(serverURL: string) { + this.serverURL = serverURL.replace(/\/$/, ''); + } + + + // @ts-ignore + async complexDummy(req: VeryComplexRequest, headers?: HeadersInit): Promise { + return fetch(this.serverURL + `/complexDummy`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...headers, + }, + body: JSON.stringify(req) + }).then((res: Response) => { + if (res.status !== 200) { + throw new Error("Failed to fetch the data"); + } + + return res.json() + }) + } + + // @ts-ignore + async complexDummy2(req: VeryComplexRequest, headers?: HeadersInit): Promise { + return fetch(this.serverURL + `/complexDummy/${req.key1}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...headers, + }, + body: JSON.stringify(req) + }).then((res: Response) => { + if (res.status !== 200) { + throw new Error("Failed to fetch the data"); + } + + return res.json() + }) + } + + // @ts-ignore + async getComplexDummy(req: VeryComplexRequest, headers?: HeadersInit): Promise { + const keys = Object.keys(req); + const keyValuePairs = keys.map(key => { + return encodeURIComponent(key) + '=' + encodeURIComponent(req[key]); + }).join('&'); + const queryParams = (keyValuePairs.length > 0) ? `${keyValuePairs}` : "" + const url = `${this.serverURL}/complexDummy/${req.key1}/xs/${req.sKey1}${queryParams}`; + return fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + ...headers, + } + }).then((res: Response) => { + if (res.status !== 200) { + throw new Error("Failed to fetch the data"); + } + + return res.json() + }) + } + +} // end of sampleServiceStub diff --git a/kit/internal/tpl/tpl.go b/kit/internal/tpl/tpl.go index 941e69f1..620d5480 100644 --- a/kit/internal/tpl/tpl.go +++ b/kit/internal/tpl/tpl.go @@ -35,6 +35,7 @@ var funcMaps = map[string]any{ }, "strJoin": strings.Join, "strSplit": strings.Split, + "strReplace": strings.ReplaceAll, "toUpper": strings.ToUpper, "toLower": strings.ToLower, "toTitle": strings.ToTitle, @@ -47,5 +48,10 @@ var funcMaps = map[string]any{ "randomID": utils.RandomID, "randomDigit": utils.RandomDigit, "randomInt": utils.RandomInt64, - "goType": GoType, + + "goType": goType, + "tsType": tsType, + "tsReplacePathParams": tsReplacePathParams, + "strAppend": strAppend, + "strEmptySlice": strEmptySlice, } diff --git a/kit/internal/tpl/ts/stub.tstmpl b/kit/internal/tpl/ts/stub.tstmpl index 24edc071..602f9db3 100644 --- a/kit/internal/tpl/ts/stub.tstmpl +++ b/kit/internal/tpl/ts/stub.tstmpl @@ -1,164 +1,93 @@ +{{/* + This is a template block to generate a DTO struct +*/}} {{ define "dto" }} // {{.Name}} is a data transfer object - {{ range .Comments -}} - // {{.}} + {{- range .Comments -}} + // {{.}} {{ end -}} - export interface I{{.Name}} { - {{- range .Fields -}} + {{$extends := strEmptySlice }} + {{- range .Fields -}} + {{- if and .IsDTO .Embedded }}{{$extends = strAppend $extends .Name}}{{end}} + {{- end }} + {{- if gt (len $extends) 0 }} + export interface {{.Name}} extends {{strJoin $extends ", "}} { + {{- else }} + export interface {{.Name}} { + {{- end }} + {{- range .Fields -}} {{- if .IsDTO }} - {{- if .Embedded }} - {{.Name}} - {{- else }} - {{(index .Tags 0).Value}}: I{{.Type}} - {{- end }} + {{- if .Embedded }} {{continue}} {{end}} + {{(index .Tags 0).Value}}{{ if .IsPtr}}?{{end}}: {{tsType .RType}} {{- else }} - {{(index .Tags 0).Value}}: {{.Type}} - {{- end }} + {{(index .Tags 0).Value}}{{ if .IsPtr}}?{{end}}: {{tsType .RType}} {{- end }} + {{- end }} } -{{ end }} -// Code generated by RonyKIT Stub Generator (Golang); DO NOT EDIT. - - -{{/*var _ fmt.Stringer*/}} - -{{/*{{$tags := strQuote .Tags}}*/}} -{{/*func init() {*/}} -{{/*{{- range $dtoName, $dto := .DTOs }}*/}} -{{/*reflector.Register(&{{$dtoName}}{}, {{strJoin $tags ","}})*/}} -{{/*{{- end }}*/}} -{{/*}*/}} - -{{ range $dtoName, $dto := .DTOs }} -{{ template "dto" $dto }} -{{ end }} - -{{/*{{$serviceName := .Name}}*/}} -{{/*type I{{$serviceName}}Stub interface {*/}} -{{/* {{ range .RESTs }}*/}} -{{/* {{$methodName := .Name}}*/}} -{{/* {{- if ne $methodName "" -}}*/}} -{{/* {{$methodName}}(*/}} -{{/* ctx context.Context, req *{{.Request.Name}}, opt ...stub.RESTOption,*/}} -{{/* ) (*{{.Response.Name}}, *stub.Error)*/}} -{{/* {{- end -}}*/}} -{{/* {{- end }}*/}} -{{/*}*/}} - -{{/*// {{$serviceName}}Stub represents the client/stub for {{$serviceName}}.*/}} -{{/*// Implements I{{$serviceName}}Stub*/}} -{{/*type {{$serviceName}}Stub struct {*/}} -{{/* hostPort string*/}} -{{/* secure bool*/}} -{{/* verifyTLS bool*/}} - -{{/* s *stub.Stub*/}} -{{/*}*/}} - -{{/*func New{{$serviceName}}Stub(hostPort string, opts ...stub.Option) *{{$serviceName}}Stub {*/}} -{{/* s := &{{$serviceName}}Stub{*/}} -{{/* s: stub.New(hostPort, opts...),*/}} -{{/* }*/}} - -{{/* return s*/}} -{{/*}*/}} - -{{/*var _ I{{$serviceName}}Stub = (*{{$serviceName}}Stub)(nil)*/}} - -{{/*{{ range .RESTs }}*/}} -{{/*{{$methodName := .Name}}*/}} -{{/*{{- if ne $methodName "" }}*/}} -{{/*func (s {{$serviceName}}Stub) {{$methodName}}(*/}} -{{/* ctx context.Context, req *{{.Request.Name}}, opt ...stub.RESTOption,*/}} -{{/*) (*{{.Response.Name}}, *stub.Error){*/}} -{{/* res := &{{.Response.Name}}{}*/}} -{{/* httpCtx := s.s.REST(opt...).*/}} -{{/* SetMethod("{{.Method}}").*/}} -{{/* {{ range $idx, $errDto := .PossibleErrors }}*/}} -{{/* SetResponseHandler(*/}} -{{/* {{ $errDto.Code }},*/}} -{{/* func(ctx context.Context, r stub.RESTResponse) *stub.Error {*/}} -{{/* res := &{{$errDto.DTO.Name}}{}*/}} -{{/* err := stub.WrapError(kit.UnmarshalMessage(r.GetBody(), res))*/}} -{{/* if err != nil {*/}} -{{/* return err*/}} -{{/* }*/}} - -{{/* return stub.NewErrorWithMsg(res)*/}} -{{/* },*/}} -{{/* ).*/}} -{{/* {{- end }}*/}} -{{/* SetOKHandler(*/}} -{{/* func(ctx context.Context, r stub.RESTResponse) *stub.Error {*/}} -{{/* return stub.WrapError(kit.UnmarshalMessage(r.GetBody(), res))*/}} -{{/* },*/}} -{{/* ).*/}} -{{/* DefaultResponseHandler(*/}} -{{/* func(ctx context.Context, r stub.RESTResponse) *stub.Error {*/}} -{{/* return stub.NewError(r.StatusCode(), string(r.GetBody()))*/}} -{{/* },*/}} -{{/* ).*/}} -{{/* AutoRun(ctx, "{{.Path}}", {{.Encoding}}, req)*/}} -{{/* defer httpCtx.Release()*/}} - -{{/* if err := httpCtx.Err(); err != nil {*/}} -{{/* return nil, err*/}} -{{/* }*/}} - -{{/* return res, nil*/}} -{{/*}*/}} -{{/*{{ end }}*/}} -{{/*{{- end }}*/}} - -{{/*type MockOption func(*{{$serviceName}}StubMock)*/}} - -{{/*{{ range .RESTs }}*/}} -{{/*{{$methodName := .Name}}*/}} -{{/*{{- if ne $methodName "" }}*/}} -{{/*func Mock{{$methodName}}(*/}} -{{/* f func(ctx context.Context, req *{{.Request.Name}}, opt ...stub.RESTOption) (*{{.Response.Name}}, *stub.Error),*/}} -{{/*) MockOption {*/}} -{{/* return func(sm *{{$serviceName}}StubMock) {*/}} -{{/* sm.{{toLower $methodName}} = f*/}} -{{/* }*/}} -{{/*}*/}} -{{/*{{ end }}*/}} -{{/*{{- end }}*/}} - - -{{/*// {{$serviceName}}StubMock represents the mocked for client/stub for {{$serviceName}}.*/}} -{{/*// Implements I{{$serviceName}}Stub*/}} -{{/*type {{$serviceName}}StubMock struct {*/}} -{{/*{{ range .RESTs }}*/}} -{{/* {{$methodName := .Name}}*/}} -{{/* {{- if ne $methodName "" -}}*/}} -{{/* {{toLower $methodName}} func(ctx context.Context, req *{{.Request.Name}}, opt ...stub.RESTOption) (*{{.Response.Name}}, *stub.Error)*/}} -{{/* {{- end -}}*/}} -{{/*{{- end }}*/}} -{{/*}*/}} - -{{/*func New{{$serviceName}}StubMock(opts ...MockOption) *{{$serviceName}}StubMock {*/}} -{{/* s := &{{$serviceName}}StubMock{}*/}} -{{/* for _, o := range opts {*/}} -{{/* o(s)*/}} -{{/* }*/}} - -{{/* return s*/}} -{{/*}*/}} - -{{/*var _ I{{$serviceName}}Stub = (*{{$serviceName}}StubMock)(nil)*/}} - -{{/*{{ range .RESTs }}*/}} -{{/*{{$methodName := .Name}}*/}} -{{/*{{- if ne $methodName "" }}*/}} -{{/*func (s {{$serviceName}}StubMock) {{$methodName}}(*/}} -{{/* ctx context.Context, req *{{.Request.Name}}, opt ...stub.RESTOption,*/}} -{{/*) (*{{.Response.Name}}, *stub.Error){*/}} -{{/* if s.{{toLower $methodName}} == nil {*/}} -{{/* return nil, stub.WrapError(fmt.Errorf("method not mocked"))*/}} -{{/* }*/}} +{{- end }} + +{{/* Generate the DTO interfaces */}} +// Code generated by RonyKIT Stub Generator (TypeScript); DO NOT EDIT. +{{ range $dtoName, $dto := .DTOs -}} + {{ template "dto" $dto }} +{{- end }} + + +{{/* Generate the Stub Class */}} +{{$serviceName := .Name}} +class {{$serviceName}}Stub { +readonly serverURL: string ; + +constructor(serverURL: string) { + this.serverURL = serverURL.replace(/\/$/, ''); +} + +{{/* + Generating the REST methods +*/}} +{{- range .RESTs -}} + {{$methodName := .Name}} + {{- if ne $methodName "" }} + // @ts-ignore + async {{lowerCamelCase $methodName}}(req: {{.Request.Name}}, headers?: HeadersInit): Promise<{{.Response.Name}}> { + {{- if eq (toLower .Method) "get" }} + const keys = Object.keys(req); + const keyValuePairs = keys.map(key => { + return encodeURIComponent(key) + '=' + encodeURIComponent(req[key]); + }).join('&'); + const queryParams = (keyValuePairs.length > 0) ? `${keyValuePairs}`: "" + const url = `${this.serverURL}{{tsReplacePathParams .Path "req."}}${queryParams}`; + return fetch(url, { + method: "{{.Method}}", + headers: { + "Content-Type": "application/json", + ...headers, + } + }).then((res: Response) => { + if (res.status !== 200) { + throw new Error("Failed to fetch the data"); + } + + return res.json() + }) + {{- else }} + return fetch(this.serverURL + `{{tsReplacePathParams .Path "req."}}`, { + method: "{{.Method}}", + headers: { + "Content-Type": "application/json", + ...headers, + }, + body: JSON.stringify(req) + }).then((res: Response) => { + if (res.status !== 200) { + throw new Error("Failed to fetch the data"); + } + + return res.json() + }) + {{- end }} + } + {{- end }} +{{- end }} -{{/* return s.{{toLower $methodName}}(ctx, req, opt...)*/}} -{{/*}*/}} -{{/*{{ end }}*/}} -{{/*{{- end }}*/}} +} // end of {{$serviceName}}Stub diff --git a/kit/internal/tpl/util.go b/kit/internal/tpl/util.go index 7bfd3b1a..11b70789 100644 --- a/kit/internal/tpl/util.go +++ b/kit/internal/tpl/util.go @@ -3,13 +3,15 @@ package tpl import ( "fmt" "reflect" + "regexp" + "strings" ) -func GoType(t reflect.Type) string { - return goType("", t) +func goType(t reflect.Type) string { + return goTypeRecursive("", t) } -func goType(prefix string, t reflect.Type) string { +func goTypeRecursive(prefix string, t reflect.Type) string { // we need a hacky fix to handle correctly json.RawMessage and kit.RawMessage in auto-generated code // of the stubs switch t.String() { @@ -24,15 +26,15 @@ func goType(prefix string, t reflect.Type) string { case reflect.Slice: prefix += "[]" - return goType(prefix, t.Elem()) + return goTypeRecursive(prefix, t.Elem()) case reflect.Array: prefix += fmt.Sprintf("[%d]", t.Len()) - return goType(prefix, t.Elem()) + return goTypeRecursive(prefix, t.Elem()) case reflect.Ptr: prefix += "*" - return goType(prefix, t.Elem()) + return goTypeRecursive(prefix, t.Elem()) case reflect.Interface: in := t.Name() if in == "" { @@ -43,8 +45,68 @@ func goType(prefix string, t reflect.Type) string { case reflect.Struct: return fmt.Sprintf("%s%s", prefix, t.Name()) case reflect.Map: - return fmt.Sprintf("map[%s]%s", goType("", t.Key()), goType("", t.Elem())) + return fmt.Sprintf("map[%s]%s", goTypeRecursive("", t.Key()), goTypeRecursive("", t.Elem())) default: return fmt.Sprintf("%s%s", prefix, t.Kind().String()) } } + +func tsType(t reflect.Type) string { + return tsTypeRecursive("", t, "") +} + +func tsTypeRecursive(prefix string, t reflect.Type, postfix string) string { + // we need a hacky fix to handle correctly json.RawMessage and kit.RawMessage in auto-generated code + // of the stubs + switch t.String() { + case "json.RawMessage": + return fmt.Sprintf("%s%s", prefix, "any") + case "kit.RawMessage": + return fmt.Sprintf("%s%s", prefix, "any") + } + + //nolint:exhaustive + switch t.Kind() { + case reflect.Slice: + postfix += "[]" + + return tsTypeRecursive(prefix, t.Elem(), postfix) + case reflect.Array: + postfix += fmt.Sprintf("[%d]", t.Len()) + + return tsTypeRecursive(prefix, t.Elem(), postfix) + case reflect.Ptr: + return tsTypeRecursive(prefix, t.Elem(), postfix) + case reflect.Struct: + return fmt.Sprintf("%s%s%s", prefix, t.Name(), postfix) + case reflect.Map: + return fmt.Sprintf("{[key: %s]: %s}", + tsTypeRecursive("", t.Key(), ""), + tsTypeRecursive("", t.Elem(), ""), + ) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + return fmt.Sprintf("%s%s%s", prefix, "number", postfix) + case reflect.Bool: + return fmt.Sprintf("%s%s%s", prefix, "boolean", postfix) + default: + return fmt.Sprintf("%s%s%s", prefix, t.Kind().String(), postfix) + } +} + +func strAppend(arr []string, elem string) []string { + return append(arr, elem) +} + +func strEmptySlice() []string { + return []string{} +} + +var pathParamRegEX = regexp.MustCompile(`{([^}]+)}`) + +func tsReplacePathParams(path string, prefix string) string { + return pathParamRegEX.ReplaceAllStringFunc(path, func(s string) string { + return fmt.Sprintf(`${%s%s}`, prefix, strings.Trim(s, "{}")) + }) +} diff --git a/kit/stub/stubgen/gen.go b/kit/stub/stubgen/gen.go index 8ac60b15..97c5a51e 100644 --- a/kit/stub/stubgen/gen.go +++ b/kit/stub/stubgen/gen.go @@ -16,10 +16,6 @@ type Input struct { Pkg string } -// GenFunc is the function which generates the final code. For example to generate -// golang code use GolangStub -type GenFunc func(in Input) (string, error) - type Generator struct { cfg genConfig } diff --git a/kit/stub/stubgen/gen_func.go b/kit/stub/stubgen/gen_func.go index c2b98949..1c5ae747 100644 --- a/kit/stub/stubgen/gen_func.go +++ b/kit/stub/stubgen/gen_func.go @@ -6,6 +6,10 @@ import ( "github.com/clubpay/ronykit/kit/internal/tpl" ) +// GenFunc is the function which generates the final code. For example to generate +// golang code use GolangStub +type GenFunc func(in Input) (string, error) + func GolangStub(in Input) (string, error) { sb := &strings.Builder{}