Simplified
parent
5893ad898b
commit
984592b7fd
2
go.mod
2
go.mod
|
@ -3,7 +3,7 @@ module go.digitalcircle.com.br/tools/apigen
|
|||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/alecthomas/kong v0.2.15
|
||||
github.com/alecthomas/kong v0.2.17
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
|
|
5
go.sum
5
go.sum
|
@ -1,5 +1,7 @@
|
|||
github.com/alecthomas/kong v0.2.15 h1:HP3K1XuFn0wGSWFGVW67V+65tXw/Ht8FDYiLNAuX2Ug=
|
||||
github.com/alecthomas/kong v0.2.15/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/alecthomas/kong v0.2.17 h1:URDISCI96MIgcIlQyoCAlhOmrSw6pZScBNkctg8r0W0=
|
||||
github.com/alecthomas/kong v0.2.17/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -40,6 +42,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
|
@ -54,3 +57,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
182
lib/loader.go
182
lib/loader.go
|
@ -1,6 +1,7 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fatih/structtag"
|
||||
"go/ast"
|
||||
|
@ -117,118 +118,89 @@ func addStruct(a *ast.GenDecl) {
|
|||
api.Types[tp.Name] = &tp
|
||||
}
|
||||
|
||||
func addFunction(a *ast.FuncDecl) {
|
||||
func addFunction(a *ast.FuncDecl) error {
|
||||
md := manageComments(a.Doc)
|
||||
|
||||
if md["API"] == "" {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
llog("Adding Fuction: %s => %#v", a.Name, md)
|
||||
reqType := &APIParamType{}
|
||||
resType := &APIParamType{}
|
||||
|
||||
if len(a.Type.Params.List) > 1 {
|
||||
switch x := a.Type.Params.List[1].Type.(type) {
|
||||
case *ast.StarExpr:
|
||||
reqType.Ispointer = true
|
||||
switch y := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
reqType.Typename = y.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
|
||||
reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
|
||||
}
|
||||
case *ast.ArrayType:
|
||||
reqType.IsArray = true
|
||||
switch y := x.Elt.(type) {
|
||||
case *ast.Ident:
|
||||
reqType.Typename = y.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
|
||||
reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
|
||||
case *ast.StarExpr:
|
||||
reqType.Ispointer = true
|
||||
switch z := y.X.(type) {
|
||||
case *ast.Ident:
|
||||
reqType.Typename = z.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[z.X.(*ast.Ident).Name] = api.Imports[z.X.(*ast.Ident).Name]
|
||||
reqType.Typename = z.X.(*ast.Ident).Name + "." + z.Sel.Name
|
||||
}
|
||||
}
|
||||
case *ast.Ident:
|
||||
reqType.Typename = x.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[x.X.(*ast.Ident).Name] = api.Imports[x.X.(*ast.Ident).Name]
|
||||
reqType.Typename = x.X.(*ast.Ident).Name + "." + x.Sel.Name
|
||||
}
|
||||
if len(a.Type.Params.List) != 2 {
|
||||
return errors.New(fmt.Sprintf("Function %s does not have 2 IN parameters (context and pointer to req struct)", a.Name.Name))
|
||||
}
|
||||
|
||||
if a.Type.Results != nil && len(a.Type.Results.List) > 0 {
|
||||
|
||||
switch x := a.Type.Results.List[0].Type.(type) {
|
||||
case *ast.StarExpr:
|
||||
resType.Ispointer = true
|
||||
switch y := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
resType.Typename = y.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
|
||||
resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
|
||||
}
|
||||
case *ast.ArrayType:
|
||||
resType.IsArray = true
|
||||
switch y := x.Elt.(type) {
|
||||
case *ast.Ident:
|
||||
resType.Typename = y.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
|
||||
resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
|
||||
case *ast.StarExpr:
|
||||
resType.Ispointer = true
|
||||
switch z := y.X.(type) {
|
||||
case *ast.Ident:
|
||||
resType.Typename = z.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[z.X.(*ast.Ident).Name] = api.Imports[z.X.(*ast.Ident).Name]
|
||||
resType.Typename = z.X.(*ast.Ident).Name + "." + z.Sel.Name
|
||||
}
|
||||
}
|
||||
case *ast.Ident:
|
||||
resType.Typename = x.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[x.X.(*ast.Ident).Name] = api.Imports[x.X.(*ast.Ident).Name]
|
||||
resType.Typename = x.X.(*ast.Ident).Name + "." + x.Sel.Name
|
||||
|
||||
}
|
||||
|
||||
if md["RAW"] == "true" {
|
||||
reqType.Typename = md["REQ"]
|
||||
resType.Typename = md["RES"]
|
||||
}
|
||||
|
||||
verb := md["VERB"]
|
||||
if verb == "" {
|
||||
verb = http.MethodPost
|
||||
}
|
||||
fn := APIMethod{
|
||||
Name: a.Name.Name,
|
||||
Desc: a.Name.Name,
|
||||
Verb: verb,
|
||||
Path: md["PATH"],
|
||||
Perm: md["PERM"],
|
||||
ReqType: reqType,
|
||||
ResType: resType,
|
||||
}
|
||||
|
||||
if fn.Path == "" {
|
||||
fn.Path = "/" + strings.Replace(strings.ToLower(a.Name.Name), "_", "/", -1)
|
||||
}
|
||||
|
||||
api.Methods[a.Name.Name] = &fn
|
||||
if len(a.Type.Results.List) != 2 {
|
||||
return errors.New(fmt.Sprintf("Function %s does not have 2 OUT parameters (pointer to res struct and err)", a.Name.Name))
|
||||
}
|
||||
if a.Type.Results.List[1].Type.(*ast.Ident).Name != "error" {
|
||||
return errors.New(fmt.Sprintf("Function %s does not have error as 2nd OUT parameter", a.Name.Name))
|
||||
}
|
||||
|
||||
p0tp := a.Type.Params.List[0].Type.(*ast.SelectorExpr)
|
||||
if p0tp.X.(*ast.Ident).Name != "context" && p0tp.Sel.Name != "Context" {
|
||||
return errors.New(fmt.Sprintf("Function %s, param 1 is not of type context.Context", a.Name))
|
||||
}
|
||||
|
||||
switch x := a.Type.Params.List[1].Type.(type) {
|
||||
case *ast.StarExpr:
|
||||
reqType.Ispointer = true
|
||||
switch y := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
reqType.Typename = y.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
|
||||
reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
|
||||
}
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("Function %s does not have 2 IN parameters (context and pointer to req struct)", a.Name.Name))
|
||||
|
||||
}
|
||||
|
||||
switch x := a.Type.Results.List[0].Type.(type) {
|
||||
case *ast.StarExpr:
|
||||
resType.Ispointer = true
|
||||
switch y := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
resType.Typename = y.Name
|
||||
case *ast.SelectorExpr:
|
||||
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
|
||||
resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
|
||||
}
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("Function %s does not have 2 OUT parameters (pointer to res struct and err)", a.Name.Name))
|
||||
|
||||
}
|
||||
|
||||
if md["RAW"] == "true" {
|
||||
reqType.Typename = md["REQ"]
|
||||
resType.Typename = md["RES"]
|
||||
}
|
||||
|
||||
verb := md["VERB"]
|
||||
if verb == "" {
|
||||
verb = http.MethodPost
|
||||
}
|
||||
fn := APIMethod{
|
||||
Name: a.Name.Name,
|
||||
Desc: a.Name.Name,
|
||||
Verb: verb,
|
||||
Path: md["PATH"],
|
||||
Perm: md["PERM"],
|
||||
ReqType: reqType,
|
||||
ResType: resType,
|
||||
}
|
||||
|
||||
if fn.Path == "" {
|
||||
fn.Path = "/" + strings.Replace(strings.ToLower(a.Name.Name), "_", "/", -1)
|
||||
}
|
||||
|
||||
api.Methods[a.Name.Name] = &fn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func load(src string) error {
|
||||
|
@ -255,6 +227,11 @@ func load(src string) error {
|
|||
// Print the AST.
|
||||
ast.Inspect(v, func(n ast.Node) bool {
|
||||
|
||||
//tp := reflect.TypeOf(n)
|
||||
//if tp != nil {
|
||||
// log.Printf("Type: %#v", tp.String())
|
||||
//}
|
||||
|
||||
switch x := n.(type) {
|
||||
|
||||
case *ast.GenDecl:
|
||||
|
@ -278,7 +255,10 @@ func load(src string) error {
|
|||
case *ast.File:
|
||||
manageCommentsGroups(x.Comments)
|
||||
case *ast.FuncDecl:
|
||||
addFunction(x)
|
||||
err = addFunction(x)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
llog("Adding fn: %s", x.Name)
|
||||
case *ast.ValueSpec:
|
||||
if x.Names[0].Name == "BASEPATH" {
|
||||
|
@ -341,5 +321,5 @@ func load(src string) error {
|
|||
|
||||
api.Namespace = packageName
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -27,16 +27,117 @@ func processGoServerOutput(f string) error {
|
|||
|
||||
WNL("package %s", api.Namespace)
|
||||
WNL(`import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"net/http"
|
||||
)`)
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"net/http"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)`)
|
||||
|
||||
for k := range api.UsedImportsFunctions {
|
||||
WNL(`import "%s"`, k)
|
||||
}
|
||||
|
||||
WNL(`func handleTag(s string, r *http.Request) string {
|
||||
parts := strings.Split(s, ":")
|
||||
where := "Q"
|
||||
key := ""
|
||||
if len(parts) == 1 {
|
||||
key = parts[0]
|
||||
} else {
|
||||
where = parts[0]
|
||||
key = parts[1]
|
||||
}
|
||||
|
||||
switch where {
|
||||
case "Q":
|
||||
return r.URL.Query().Get(key)
|
||||
case "H":
|
||||
return r.Header.Get(key)
|
||||
case "P":
|
||||
switch key {
|
||||
case "*":
|
||||
return r.URL.Path
|
||||
case "last":
|
||||
pps := strings.Split(r.URL.Path, "/")
|
||||
return pps[len(pps)-1]
|
||||
case "len":
|
||||
pps := strings.Split(r.URL.Path, "/")
|
||||
return strconv.Itoa(len(pps))
|
||||
default:
|
||||
pps := strings.Split(r.URL.Path, "/")
|
||||
n, _ := strconv.Atoi(key)
|
||||
if n < len(pps) {
|
||||
return pps[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func convert(s string, tpname string) interface{} {
|
||||
switch tpname {
|
||||
case "string":
|
||||
return s
|
||||
|
||||
case "int":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return v
|
||||
case "int8":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return int8(v)
|
||||
case "int16":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return int16(v)
|
||||
case "int32":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return int32(v)
|
||||
case "int64":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return int64(v)
|
||||
case "uint":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return uint(v)
|
||||
case "float32":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return float32(v)
|
||||
case "float64":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return float64(v)
|
||||
case "bool":
|
||||
return s == "true" || s == "1" || s == "Y"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Map(r *http.Request, in interface{}) error {
|
||||
|
||||
tp := reflect.TypeOf(in)
|
||||
vl := reflect.ValueOf(in)
|
||||
if tp.Kind() == reflect.Ptr {
|
||||
tp = tp.Elem()
|
||||
vl = vl.Elem()
|
||||
}
|
||||
if tp.Kind() != reflect.Struct {
|
||||
return errors.New("Type is not struct")
|
||||
}
|
||||
|
||||
for i := 0; i < tp.NumField(); i++ {
|
||||
k, ok := tp.Field(i).Tag.Lookup("in")
|
||||
if ok {
|
||||
str := handleTag(k, r)
|
||||
v := convert(str, tp.Field(i).Type.Name())
|
||||
strv := reflect.ValueOf(v)
|
||||
vl.Field(i).Set(strv)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
`)
|
||||
|
||||
WNL(`type API struct {
|
||||
Mux *http.ServeMux
|
||||
Perms map[string]string
|
||||
|
@ -47,41 +148,6 @@ func (a *API) GetPerm(r *http.Request) string {
|
|||
}
|
||||
`)
|
||||
|
||||
WNL(`func Init() *API{
|
||||
mux := &http.ServeMux{}
|
||||
|
||||
ret := &API{
|
||||
Mux: mux,
|
||||
Perms: make(map[string]string),
|
||||
}`)
|
||||
for _, v := range api.Methods {
|
||||
if v.Perm != "" {
|
||||
WNL(` ret.Perms["%s_%s"]="%s"`, v.Verb, v.Path, v.Perm)
|
||||
}
|
||||
|
||||
}
|
||||
for _, v := range api.SortedPaths {
|
||||
WNL(` mux.HandleFunc("%s",func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {`, v.Path)
|
||||
for _, v1 := range v.SortedVerbs {
|
||||
WNL(` case "%s":`, v1.Method.Verb)
|
||||
if v1.Method.Raw {
|
||||
WNL(` %s(w,r)`, v1.Method.Name)
|
||||
} else {
|
||||
WNL(` h_%s(w,r)`, v1.Method.Name)
|
||||
}
|
||||
}
|
||||
|
||||
WNL(` default:
|
||||
http.Error(w,"Method not allowed",500)
|
||||
}`)
|
||||
|
||||
}
|
||||
WNL(` })`)
|
||||
|
||||
WNL(` return ret
|
||||
}`)
|
||||
|
||||
for _, v := range api.Methods {
|
||||
WNL(`func h_%s(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
@ -90,6 +156,12 @@ func (a *API) GetPerm(r *http.Request) string {
|
|||
|
||||
WNL(" req := %s", ResImplType(v.ReqType))
|
||||
|
||||
WNL(` err:=Map(r,req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}`)
|
||||
|
||||
WNL(` if r.Method!=http.MethodGet && r.Method!=http.MethodHead {`)
|
||||
|
||||
if v.ReqType.Ispointer || v.ReqType.IsArray {
|
||||
|
@ -119,5 +191,41 @@ func (a *API) GetPerm(r *http.Request) string {
|
|||
|
||||
}
|
||||
|
||||
WNL(`func Init() *API{
|
||||
mux := &http.ServeMux{}
|
||||
|
||||
ret := &API{
|
||||
Mux: mux,
|
||||
Perms: make(map[string]string),
|
||||
}`)
|
||||
for _, v := range api.Methods {
|
||||
if v.Perm != "" {
|
||||
WNL(` ret.Perms["%s_%s"]="%s"`, v.Verb, v.Path, v.Perm)
|
||||
}
|
||||
|
||||
}
|
||||
for _, v := range api.SortedPaths {
|
||||
WNL(` mux.HandleFunc("%s",func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {`, v.Path)
|
||||
for _, v1 := range v.SortedVerbs {
|
||||
WNL(` case "%s":`, v1.Method.Verb)
|
||||
if v1.Method.Raw {
|
||||
WNL(` %s(w,r)`, v1.Method.Name)
|
||||
} else {
|
||||
WNL(` h_%s(w,r)`, v1.Method.Name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WNL(` default:
|
||||
http.Error(w,"Method not allowed",500)
|
||||
}`)
|
||||
|
||||
WNL(` })`)
|
||||
}
|
||||
|
||||
WNL(` return ret
|
||||
}`)
|
||||
|
||||
return os.WriteFile(f, buf.Bytes(), 0600)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
from dataclasses import dataclass
|
||||
import requests
|
||||
import json
|
||||
from typing import List
|
||||
#region Base
|
||||
|
||||
|
||||
__ctx = {"apibase":""}
|
||||
|
||||
|
||||
def SetAPIBase(s: str):
|
||||
__ctx["apibase"] = s
|
||||
|
||||
|
||||
def GetAPIBase() -> str:
|
||||
return __ctx["apibase"]
|
||||
|
||||
|
||||
def SetCookie(s: str):
|
||||
__ctx["cookie"] = s
|
||||
|
||||
|
||||
def GetCookie() -> str:
|
||||
return __ctx["cookie"]
|
||||
|
||||
|
||||
def InvokeTxt(path: str, method: str, body) -> str:
|
||||
headers = {"Content-type": "application/json", "Cookie": "dc="+GetCookie()}
|
||||
fpath = GetAPIBase() + path
|
||||
r = requests.request(method, fpath, json=body, headers=headers)
|
||||
return r.text
|
||||
|
||||
|
||||
def InvokeJSON(path: str, method: str, body) -> dict:
|
||||
d = body.__dict__
|
||||
return json.loads(InvokeTxt(path, method, d))
|
||||
|
||||
#endregion
|
||||
|
||||
#region Types
|
||||
@ dataclass
|
||||
class SomeReq :
|
||||
fielda: str
|
||||
|
||||
|
||||
@ dataclass
|
||||
class SomeRes :
|
||||
msg: str
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
def Dosome(req:SomeReq)-> SomeRes:
|
||||
return InvokeJSON("/some","POST",req)
|
||||
|
||||
|
||||
#endregion
|
|
@ -0,0 +1,101 @@
|
|||
//#region Base
|
||||
|
||||
|
||||
var apibase="";
|
||||
|
||||
export function SetAPIBase(s:string){
|
||||
apibase=s;
|
||||
}
|
||||
|
||||
export function GetAPIBase(): string{
|
||||
return apibase;
|
||||
}
|
||||
|
||||
let REGEX_DATE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)(Z|([+\-])(\d{2}):(\d{2}))$/
|
||||
|
||||
type HTMLMethod = "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "TRACE"
|
||||
|
||||
async function Invoke(path: string, method: HTMLMethod, body?: any): Promise<Response> {
|
||||
let jbody = undefined
|
||||
let init = {method: method, mode: "cors", credentials: "include", withCredentials: true}
|
||||
if (!!body) {
|
||||
let jbody = JSON.stringify(body)
|
||||
//@ts-ignore
|
||||
init.body = jbody
|
||||
}
|
||||
if (apibase.endsWith("/") && path.startsWith("/")) {
|
||||
path = path.substr(1, path.length)
|
||||
}
|
||||
let fpath = (apibase + path)
|
||||
//@ts-ignore
|
||||
let res = await fetch(fpath, init)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async function InvokeJSON(path: string, method: HTMLMethod, body?: any): Promise<any> {
|
||||
|
||||
let txt = await InvokeTxt(path, method, body)
|
||||
if (txt == "") {
|
||||
txt = "{}"
|
||||
}
|
||||
let ret = JSON.parse(txt, (k: string, v: string) => {
|
||||
if (REGEX_DATE.exec(v)) {
|
||||
return new Date(v)
|
||||
}
|
||||
return v
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
async function InvokeTxt(path: string, method: HTMLMethod, body?: any): Promise<string> {
|
||||
//@ts-ignore
|
||||
let res = await Invoke(path, method, body)
|
||||
|
||||
let txt = await res.text()
|
||||
|
||||
if (res.status < 200 || res.status >= 400) {
|
||||
// webix.alert("API Error:" + res.status + "\n" + txt)
|
||||
console.error("API Error:" + res.status + "\n" + txt)
|
||||
let e = new Error(txt)
|
||||
throw e
|
||||
}
|
||||
|
||||
return txt
|
||||
}
|
||||
|
||||
async function InvokeOk(path: string, method: HTMLMethod, body?: any): Promise<boolean> {
|
||||
|
||||
//@ts-ignore
|
||||
let res = await Invoke(path, method, body)
|
||||
|
||||
let txt = await res.text()
|
||||
if (res.status >= 400) {
|
||||
console.error("API Error:" + res.status + "\n" + txt)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Types
|
||||
export interface SomeReq {
|
||||
fielda ?: string
|
||||
}
|
||||
|
||||
export interface SomeRes {
|
||||
msg ?: string
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Methods
|
||||
/**
|
||||
Dosome*/
|
||||
export async function Dosome(req:SomeReq):Promise<SomeRes>{
|
||||
return InvokeJSON("/some","POST",req)
|
||||
}
|
||||
|
||||
//#endregion
|
|
@ -0,0 +1,201 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func handleTag(s string, r *http.Request) string {
|
||||
parts := strings.Split(s, ":")
|
||||
where := "Q"
|
||||
key := ""
|
||||
if len(parts) == 1 {
|
||||
key = parts[0]
|
||||
} else {
|
||||
where = parts[0]
|
||||
key = parts[1]
|
||||
}
|
||||
|
||||
switch where {
|
||||
case "Q":
|
||||
return r.URL.Query().Get(key)
|
||||
case "H":
|
||||
return r.Header.Get(key)
|
||||
case "P":
|
||||
switch key {
|
||||
case "*":
|
||||
return r.URL.Path
|
||||
case "last":
|
||||
pps := strings.Split(r.URL.Path, "/")
|
||||
return pps[len(pps)-1]
|
||||
case "len":
|
||||
pps := strings.Split(r.URL.Path, "/")
|
||||
return strconv.Itoa(len(pps))
|
||||
default:
|
||||
pps := strings.Split(r.URL.Path, "/")
|
||||
n, _ := strconv.Atoi(key)
|
||||
if n < len(pps) {
|
||||
return pps[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func convert(s string, tpname string) interface{} {
|
||||
switch tpname {
|
||||
case "string":
|
||||
return s
|
||||
|
||||
case "int":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return v
|
||||
case "int8":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return int8(v)
|
||||
case "int16":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return int16(v)
|
||||
case "int32":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return int32(v)
|
||||
case "int64":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return int64(v)
|
||||
case "uint":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return uint(v)
|
||||
case "float32":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return float32(v)
|
||||
case "float64":
|
||||
v, _ := strconv.Atoi(s)
|
||||
return float64(v)
|
||||
case "bool":
|
||||
return s == "true" || s == "1" || s == "Y"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Map(r *http.Request, in interface{}) error {
|
||||
|
||||
tp := reflect.TypeOf(in)
|
||||
vl := reflect.ValueOf(in)
|
||||
if tp.Kind() == reflect.Ptr {
|
||||
tp = tp.Elem()
|
||||
vl = vl.Elem()
|
||||
}
|
||||
if tp.Kind() != reflect.Struct {
|
||||
return errors.New("Type is not struct")
|
||||
}
|
||||
|
||||
for i := 0; i < tp.NumField(); i++ {
|
||||
k, ok := tp.Field(i).Tag.Lookup("in")
|
||||
if ok {
|
||||
str := handleTag(k, r)
|
||||
v := convert(str, tp.Field(i).Type.Name())
|
||||
strv := reflect.ValueOf(v)
|
||||
vl.Field(i).Set(strv)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type API struct {
|
||||
Mux *http.ServeMux
|
||||
Perms map[string]string
|
||||
}
|
||||
|
||||
func (a *API) GetPerm(r *http.Request) string {
|
||||
return a.Perms[r.Method+"_"+strings.Split(r.RequestURI, "?")[0]]
|
||||
}
|
||||
|
||||
func h_Dosome(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(r.Context(), "REQ", r)
|
||||
ctx = context.WithValue(ctx, "RES", w)
|
||||
req := &SomeReq{}
|
||||
err := Map(r, req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
res, err := Dosome(ctx, req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "Application/json")
|
||||
err = json.NewEncoder(w).Encode(res)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
func h_Dosome2(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(r.Context(), "REQ", r)
|
||||
ctx = context.WithValue(ctx, "RES", w)
|
||||
req := &SomeReq2{}
|
||||
err := Map(r, req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
err := json.NewDecoder(r.Body).Decode(req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
res, err := Dosome2(ctx, req)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "Application/json")
|
||||
err = json.NewEncoder(w).Encode(res)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
func Init() *API {
|
||||
mux := &http.ServeMux{}
|
||||
|
||||
ret := &API{
|
||||
Mux: mux,
|
||||
Perms: make(map[string]string),
|
||||
}
|
||||
mux.HandleFunc("/some", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
h_Dosome(w, r)
|
||||
default:
|
||||
http.Error(w, "Method not allowed", 500)
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/some2", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
h_Dosome2(w, r)
|
||||
default:
|
||||
http.Error(w, "Method not allowed", 500)
|
||||
}
|
||||
})
|
||||
return ret
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
//@API
|
||||
type SomeReq struct {
|
||||
Fielda string `json:"fielda"`
|
||||
Fieldb time.Time `json:"fielda"`
|
||||
}
|
||||
|
||||
//@API
|
||||
type SomeReq2 struct {
|
||||
Fielda string `json:"fielda"`
|
||||
Fieldb time.Time `json:"fielda"`
|
||||
}
|
||||
|
||||
//@API
|
||||
type SomeRes struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
//@API
|
||||
//@PATH: /some
|
||||
//@VERB: POST
|
||||
func Dosome(ctx context.Context, req *SomeReq) (res *SomeRes, err error) {
|
||||
res = &SomeRes{Msg: fmt.Sprintf("%s: %s", req.Fielda, req.Fieldb.String())}
|
||||
return
|
||||
}
|
||||
|
||||
//@API
|
||||
//@PATH: /some2
|
||||
//@VERB: POST
|
||||
func Dosome2(ctx context.Context, req *SomeReq2) (res *SomeRes, err error) {
|
||||
res = &SomeRes{Msg: fmt.Sprintf("%s: %s", req.Fielda, req.Fieldb.String())}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package api
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var Basepath string = ""
|
||||
var Host string = ""
|
||||
var ExtraHeaders map[string]string = make(map[string]string)
|
||||
|
||||
var cli *http.Client
|
||||
|
||||
func SetCli(nc *http.Client) {
|
||||
cli = nc
|
||||
}
|
||||
|
||||
func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
|
||||
b := &bytes.Buffer{}
|
||||
err := json.NewEncoder(b).Encode(bodyo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body := bytes.NewReader(b.Bytes())
|
||||
req, err := http.NewRequest(m, Host+Basepath+path, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-type", "application/json")
|
||||
|
||||
for k, v := range ExtraHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
res, err := cli.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode >= 400 {
|
||||
bs, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.New(string(bs))
|
||||
}
|
||||
|
||||
ret := json.NewDecoder(res.Body)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type SomeReq struct {
|
||||
Fielda string
|
||||
Fieldb time.Time
|
||||
}
|
||||
type SomeReq2 struct {
|
||||
Fielda string
|
||||
Fieldb time.Time
|
||||
}
|
||||
type SomeRes struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
func Dosome(req *SomeReq) (res *SomeRes, err error) {
|
||||
var dec *json.Decoder
|
||||
dec, err = invoke("POST", "/some", req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ret *SomeRes = &SomeRes{}
|
||||
err = dec.Decode(ret)
|
||||
return ret, err
|
||||
}
|
||||
func Dosome2(req *SomeReq2) (res *SomeRes, err error) {
|
||||
var dec *json.Decoder
|
||||
dec, err = invoke("POST", "/some2", req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ret *SomeRes = &SomeRes{}
|
||||
err = dec.Decode(ret)
|
||||
return ret, err
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
api "go.digitalcircle.com.br/tools/apigen/test/demo/cli"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
api.Host = "http://localhost:8080"
|
||||
api.SetCli(&http.Client{})
|
||||
res, err := api.Dosome(&api.SomeReq{
|
||||
Fielda: "ASD123",
|
||||
Fieldb: time.Unix(0, 0),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Printf("%#v", res)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"go.digitalcircle.com.br/tools/apigen/test/demo/api"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
aapi := api.Init()
|
||||
http.ListenAndServe(":8080", aapi.Mux)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
POST http://localhost:8080/some
|
||||
|
||||
{"fielda":"Hello World","fieldb":""}
|
|
@ -25,7 +25,7 @@ type AStr struct {
|
|||
@PERM: ASD
|
||||
@VERB: POST
|
||||
*/
|
||||
func SomeAPI(ctx context.Context, s *AStr) (out *AStr, err error) {
|
||||
func SomeAPI(ctx context.Context, s *AStr, c int) (out *AStr, err error) {
|
||||
//print("Got:" + s)
|
||||
//out = time.Now().String() + " - Hey Ya!"
|
||||
return
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
package goapi
|
||||
|
||||
import "time"
|
||||
import "crypto"
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Basepath string = ""
|
||||
var Host string = ""
|
||||
var ExtraHeaders map[string]string = make(map[string]string)
|
||||
|
||||
var Ver string = ""
|
||||
var cli http.Client
|
||||
|
||||
func SetCli(nc http.Client) {
|
||||
cli = nc
|
||||
}
|
||||
|
||||
func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
|
||||
b := &bytes.Buffer{}
|
||||
err := json.NewEncoder(b).Encode(bodyo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body := bytes.NewReader(b.Bytes())
|
||||
req, err := http.NewRequest(m, Host+Basepath+path, body)
|
||||
if err != nil {
|
||||
|
@ -32,19 +40,19 @@ func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
|
|||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
cli := http.Client{}
|
||||
res, err := cli.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode >= 400 {
|
||||
bs, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, errors.New(string(bs))
|
||||
}
|
||||
|
||||
|
@ -53,65 +61,24 @@ func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
|
|||
}
|
||||
|
||||
type AStr struct {
|
||||
Arrofpstr []string
|
||||
|
||||
City string
|
||||
|
||||
Country string
|
||||
|
||||
HouseNumber int64
|
||||
|
||||
IsCondo bool
|
||||
|
||||
Recursive map[string]AStr
|
||||
|
||||
Some crypto.Decrypter
|
||||
|
||||
HouseNumber int64
|
||||
SomeWeirdTest string
|
||||
|
||||
When time.Time
|
||||
Recursive map[string]AStr
|
||||
When time.Time
|
||||
Some crypto.Decrypter
|
||||
Country string
|
||||
City string
|
||||
IsCondo bool
|
||||
Arrofpstr []string
|
||||
}
|
||||
|
||||
func SomeAPI(req string) (res string, err error) {
|
||||
func SomeAPI(req *AStr) (res *AStr, err error) {
|
||||
var dec *json.Decoder
|
||||
dec, err = invoke("POST", "/someapi", req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ret string
|
||||
err = dec.Decode(ret)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func SomeAPI2(req crypto.Hash) (res []string, err error) {
|
||||
var dec *json.Decoder
|
||||
dec, err = invoke("DELETE", "/someapi", req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ret []string
|
||||
err = dec.Decode(ret)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func SomeGET(req string) (res string, err error) {
|
||||
var dec *json.Decoder
|
||||
dec, err = invoke("GET", "/someapi", req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ret string
|
||||
err = dec.Decode(ret)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func SomePUT(req string) (res string, err error) {
|
||||
var dec *json.Decoder
|
||||
dec, err = invoke("PUT", "/someapi", req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ret string
|
||||
var ret *AStr = &AStr{}
|
||||
err = dec.Decode(ret)
|
||||
return ret, err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue