mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-14 03:54:47 +00:00
Generate typesafe typescript and go clients, examples (#194)
This commit is contained in:
193
cmd/clients/example.go
Normal file
193
cmd/clients/example.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/crufter/nested"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/stoewer/go-strcase"
|
||||
)
|
||||
|
||||
func schemaToGoExample(serviceName, typeName string, schemas map[string]*openapi3.SchemaRef, values map[string]interface{}) string {
|
||||
var recurse func(props map[string]*openapi3.SchemaRef, path []string) string
|
||||
|
||||
var spec *openapi3.SchemaRef = schemas[typeName]
|
||||
if spec == nil {
|
||||
existing := ""
|
||||
for k, _ := range schemas {
|
||||
existing += k + " "
|
||||
}
|
||||
panic("can't find schema " + typeName + " but found " + existing)
|
||||
}
|
||||
detectType := func(currentType string, properties map[string]*openapi3.SchemaRef) (string, bool) {
|
||||
index := map[string]bool{}
|
||||
for key, prop := range properties {
|
||||
index[key+prop.Value.Title] = true
|
||||
}
|
||||
for k, schema := range schemas {
|
||||
// we don't want to return the type matching itself
|
||||
if strings.ToLower(k) == currentType {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(k, "Request") || strings.HasSuffix(k, "Response") {
|
||||
continue
|
||||
}
|
||||
if len(schema.Value.Properties) != len(properties) {
|
||||
continue
|
||||
}
|
||||
found := false
|
||||
for key, prop := range schema.Value.Properties {
|
||||
|
||||
_, ok := index[key+prop.Value.Title]
|
||||
found = ok
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return schema.Value.Title, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
var fieldSeparator, objectOpen, objectClose, arrayPrefix, arrayPostfix, fieldDelimiter, stringType, boolType string
|
||||
var int32Type, int64Type, floatType, doubleType, mapType, anyType, typeInstancePrefix string
|
||||
var fieldUpperCase bool
|
||||
language := "go"
|
||||
switch language {
|
||||
case "go":
|
||||
fieldUpperCase = true
|
||||
fieldSeparator = ": "
|
||||
arrayPrefix = "[]"
|
||||
arrayPostfix = ""
|
||||
objectOpen = "{\n"
|
||||
objectClose = "}"
|
||||
fieldDelimiter = ","
|
||||
stringType = "string"
|
||||
boolType = "bool"
|
||||
int32Type = "int32"
|
||||
int64Type = "int64"
|
||||
floatType = "float32"
|
||||
doubleType = "float64"
|
||||
mapType = "map[string]%v"
|
||||
anyType = "interface{}"
|
||||
typeInstancePrefix = "&"
|
||||
}
|
||||
|
||||
valueToType := func(v *openapi3.SchemaRef) string {
|
||||
switch v.Value.Type {
|
||||
case "string":
|
||||
return stringType
|
||||
case "boolean":
|
||||
return boolType
|
||||
case "number":
|
||||
switch v.Value.Format {
|
||||
case "int32":
|
||||
return int32Type
|
||||
case "int64":
|
||||
return int64Type
|
||||
case "float":
|
||||
return floatType
|
||||
case "double":
|
||||
return doubleType
|
||||
}
|
||||
default:
|
||||
return "unrecognized: " + v.Value.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
printMap := func(m map[string]interface{}, level int) string {
|
||||
ret := ""
|
||||
for k, v := range m {
|
||||
marsh, _ := json.Marshal(v)
|
||||
ret += strings.Repeat("\t", level) + fmt.Sprintf("\"%v\": %v,\n", k, string(marsh))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
recurse = func(props map[string]*openapi3.SchemaRef, path []string) string {
|
||||
ret := ""
|
||||
|
||||
i := 0
|
||||
var keys []string
|
||||
for k := range props {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for i, v := range path {
|
||||
path[i] = strcase.LowerCamelCase(v)
|
||||
}
|
||||
for _, k := range keys {
|
||||
v := props[k]
|
||||
ret += strings.Repeat("\t", len(path))
|
||||
if fieldUpperCase {
|
||||
k = strcase.UpperCamelCase(k)
|
||||
}
|
||||
|
||||
var val interface{}
|
||||
p := strings.Replace(strings.Join(append(path, strcase.LowerCamelCase(k)), "."), ".[", "[", -1)
|
||||
val, ok := nested.Get(values, p)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// hack
|
||||
if str, ok := val.(string); ok {
|
||||
if str == "<nil>" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch v.Value.Type {
|
||||
case "object":
|
||||
typ, found := detectType(k, v.Value.Properties)
|
||||
if found {
|
||||
ret += k + fieldSeparator + typeInstancePrefix + serviceName + "." + strings.Title(typ) + objectOpen + recurse(v.Value.Properties, append(path, k)) + objectClose + fieldDelimiter
|
||||
} else {
|
||||
// type is a dynamic map
|
||||
// if additional properties is present, then it's a map string string or other typed map
|
||||
if v.Value.AdditionalProperties != nil {
|
||||
ret += k + fieldSeparator + fmt.Sprintf(mapType, valueToType(v.Value.AdditionalProperties)) + objectOpen + printMap(val.(map[string]interface{}), len(path)+1) + objectClose + fieldDelimiter
|
||||
} else {
|
||||
// if additional properties is not present, it's an any type,
|
||||
// like the proto struct type
|
||||
ret += k + fieldSeparator + fmt.Sprintf(mapType, anyType) + objectOpen + printMap(val.(map[string]interface{}), len(path)+1) + objectClose + fieldDelimiter
|
||||
}
|
||||
}
|
||||
case "array":
|
||||
typ, found := detectType(k, v.Value.Items.Value.Properties)
|
||||
if found {
|
||||
ret += k + fieldSeparator + arrayPrefix + serviceName + "." + strings.Title(typ) + objectOpen + serviceName + "." + strings.Title(typ) + objectOpen + recurse(v.Value.Items.Value.Properties, append(append(path, k), "[0]")) + objectClose + objectClose + arrayPostfix + fieldDelimiter
|
||||
} else {
|
||||
switch v.Value.Items.Value.Type {
|
||||
case "string":
|
||||
ret += k + fieldSeparator + arrayPrefix + fmt.Sprintf("\"%v\"", val) + arrayPostfix + fieldDelimiter
|
||||
case "number", "boolean":
|
||||
ret += k + fieldSeparator + arrayPrefix + fmt.Sprintf("%v", val) + arrayPostfix + fieldDelimiter
|
||||
case "object":
|
||||
ret += k + fieldSeparator + arrayPrefix + fmt.Sprintf(mapType, valueToType(v.Value.AdditionalProperties)) + objectOpen + fmt.Sprintf(mapType, valueToType(v.Value.AdditionalProperties)) + objectOpen + recurse(v.Value.Items.Value.Properties, append(append(path, k), "[0]")) + strings.Repeat("\t", len(path)) + objectClose + objectClose + arrayPostfix + fieldDelimiter
|
||||
}
|
||||
}
|
||||
case "string":
|
||||
if strings.Contains(val.(string), "\n") {
|
||||
ret += k + fieldSeparator + fmt.Sprintf("`%v`", val) + fieldDelimiter
|
||||
} else {
|
||||
ret += k + fieldSeparator + fmt.Sprintf("\"%v\"", val) + fieldDelimiter
|
||||
}
|
||||
case "number", "boolean":
|
||||
ret += k + fieldSeparator + fmt.Sprintf("%v", val) + fieldDelimiter
|
||||
}
|
||||
|
||||
if i < len(props) {
|
||||
ret += "\n"
|
||||
}
|
||||
i++
|
||||
|
||||
}
|
||||
return ret
|
||||
}
|
||||
return recurse(spec.Value.Properties, []string{})
|
||||
}
|
||||
Reference in New Issue
Block a user