// able typescript generated from golang // Copyright (C) 2022 Fabio Prada package tsrpc import ( "bytes" "fmt" "slices" "strings" "text/template" ) type TSEndpoint struct { Name string Path string Method string Request string Response string Source string File string Line int Imports map[string][]string } func ParseEndpoint(source string, file string, line int) TSEndpoint { p := strings.Index(source, "TSEndpoint=") s := strings.Trim(source[p+len("TSEndpoint="):], " ") a := strings.Split(s, ";") n := 0 endpoint := TSEndpoint{} endpoint.Source = strings.Trim(source, "\t") endpoint.File = file endpoint.Line = line endpoint.Imports = make(map[string][]string) for _, v := range a { t := strings.Split(v, "=") if len(t) < 2 || strings.Trim(t[1], " ") == "" { exitOnError(fmt.Errorf("worong endpoint: %s", s)) } if len(t) == 2 { switch strings.Trim(t[0], " ") { case "path": n++ endpoint.Path = strings.Trim(t[1], " ") case "method": n++ endpoint.Method = strings.Trim(t[1], " ") case "name": n++ endpoint.Name = strings.Trim(t[1], " ") case "request": n++ endpoint.Request = strings.Trim(t[1], " ") case "response": n++ endpoint.Response = strings.Trim(t[1], " ") } } else { exitOnError(fmt.Errorf("wrong endpoint props: %s", s)) } } if endpoint.Method != "POST" && endpoint.Method != "GET" && endpoint.Method != "DELETE" && endpoint.Method != "PUT" { exitOnError(fmt.Errorf("wrong endpoint method: %s", s)) } if (endpoint.Method == "GET" || endpoint.Method == "DELETE") && n < 4 { exitOnError(fmt.Errorf("wrong endpoint number of props: %s", s)) } if (endpoint.Method == "POST" || endpoint.Method == "PUT") && n < 5 { exitOnError(fmt.Errorf("wrong endpoint number of props: %s", s)) } return endpoint } type tplData struct { E *TSEndpoint Path string Params []string } func (e *TSEndpoint) VerifyTypes(info TSInfo, p string) { kind := "" if e.Name == "listUsers" { fmt.Println("endpoint request", e.Request) } a := strings.Split(e.Request, ".") if e.Request != "" { if len(a) == 2 { kind = a[1] if strings.HasPrefix(a[1], "[]") { kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[1], "[]")) } // nullable pointer if strings.HasPrefix(a[1], "*") { kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[1], "*")) } if !info.find(a[0], a[1]) { exitOnError(fmt.Errorf("endpoint request not found: %s AT %s Line: %d ", e.Request, e.File, e.Line)) } if a[0] == p { e.Request = fmt.Sprintf("%s", kind) } else { e.Request = fmt.Sprintf("%s.%s", kind, a[1]) if _, ok := e.Imports[a[0]]; !ok { e.Imports[a[0]] = []string{} } allreadyImported := slices.Contains(e.Imports[a[0]], strings.Trim(a[1], "[]*")) if !allreadyImported { e.Imports[a[0]] = append(e.Imports[a[0]], strings.Trim(a[1], "[]*")) } } info.setTypescript(a[0], a[1], true) } if len(a) == 1 { kind = a[0] if strings.HasPrefix(a[0], "[]") { kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[0], "[]")) } // nullable pointer if strings.HasPrefix(a[0], "*") { kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[0], "*")) } e.Request = fmt.Sprintf("%s", kind) } // if request is a struct, set it as typescript to generate the ts struct } kind = "" a = strings.Split(e.Response, ".") if len(a) == 2 { kind = a[1] if strings.HasPrefix(a[1], "[]") { kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[1], "[]")) } // nullable pointer if strings.HasPrefix(a[1], "*") { kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[1], "*")) } if !info.find(a[0], a[1]) { exitOnError(fmt.Errorf("endpoint response not found: %s AT %s Line: %d ", e.Response, e.File, e.Line)) } if a[0] == p { e.Response = fmt.Sprintf("%s", kind) } else { e.Response = fmt.Sprintf("%s.%s", a[0], kind) if _, ok := e.Imports[a[0]]; !ok { e.Imports[a[0]] = []string{} } allreadyImported := slices.Contains(e.Imports[a[0]], strings.Trim(a[1], "[]*")) if !allreadyImported { e.Imports[a[0]] = append(e.Imports[a[0]], strings.Trim(a[1], "[]*")) fmt.Printf("endpoint %s response import: %s.%s\n", e.Name, a[0], a[1]) } } info.setTypescript(a[0], a[1], true) } if len(a) == 1 { if strings.HasPrefix(a[0], "[]") { kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[0], "[]")) } // nullable pointer if strings.HasPrefix(a[0], "*") { kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[0], "*")) } if IsNativeType(a[0]) { e.Response = fmt.Sprintf("%s%s", a[0], kind) return } e.Response = fmt.Sprintf("%s", kind) // if request is a struct, set it as typescript to generate the ts struct } } func (e *TSEndpoint) ToTs() string { data := tplData{E: e, Path: e.Path, Params: []string{}} tpl := ` {{ .E.Source }} // {{ .E.File }} Line: {{ .E.Line }} {{if eq .E.Method "GET"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.Response}}; error: Nullable }> => { return await api.GET({{ .Path}}) as { data: {{ .E.Response}}; error: Nullable }; }{{end}}{{if eq .E.Method "DELETE"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.Response}}; error: Nullable }> => { return await api.DELETE({{ .Path}}) as { data: {{ .E.Response}}; error: Nullable }; }{{end}}{{if eq .E.Method "PUT"}}export const {{ .E.Name}} = async (data: {{ .E.Request}}):Promise<{ data:{{.E.Response}}; error: Nullable }> => { return await api.PUT("{{ .Path}}", data) as { data: {{ .E.Response}}; error: Nullable }; }{{end}}{{if eq .E.Method "POST"}}export const {{ .E.Name}} = async (data: {{ .E.Request}}):Promise<{ data:{{.E.Response}}; error: Nullable }> => { return await api.POST("{{ .Path}}", data) as { data: {{ .E.Response }}; error: Nullable }; }{{end}}` if e.Method == "GET" || e.Method == "DELETE" { a := strings.Split(e.Path, "/") c := "" f := false for _, v := range a { if len(v) == 0 { continue } prefix := v[0:1] if f { c = ", " } switch prefix { case ":": f = true data.Params = append(data.Params, fmt.Sprintf("%s%s: string", c, v[1:])) data.Path = strings.Replace(data.Path, v, fmt.Sprintf("${%s}", v[1:]), 1) case "*": f = true data.Params = append(data.Params, fmt.Sprintf("%s%s: Nullable", c, v[1:])) data.Path = strings.Replace(data.Path, v, fmt.Sprintf("${%s}", v[1:]), 1) } } if len(data.Params) > 0 { data.Path = fmt.Sprintf("`%s`", data.Path) } else { data.Path = fmt.Sprintf("\"%s\"", data.Path) } } t, err := template.New("test").Parse(tpl) if err != nil { panic(err) } var result bytes.Buffer err = t.Execute(&result, data) if err != nil { panic(err) } return result.String() }