Simplified

master
Paulo Simão 2021-10-03 11:39:42 -03:00
parent 5893ad898b
commit 984592b7fd
14 changed files with 784 additions and 199 deletions

2
go.mod
View File

@ -3,7 +3,7 @@ module go.digitalcircle.com.br/tools/apigen
go 1.17 go 1.17
require ( require (
github.com/alecthomas/kong v0.2.15 github.com/alecthomas/kong v0.2.17
github.com/fatih/structtag v1.2.0 github.com/fatih/structtag v1.2.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0

5
go.sum
View File

@ -1,5 +1,7 @@
github.com/alecthomas/kong v0.2.15 h1:HP3K1XuFn0wGSWFGVW67V+65tXw/Ht8FDYiLNAuX2Ug= 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.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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 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.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 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= 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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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=

View File

@ -1,6 +1,7 @@
package lib package lib
import ( import (
"errors"
"fmt" "fmt"
"github.com/fatih/structtag" "github.com/fatih/structtag"
"go/ast" "go/ast"
@ -117,18 +118,33 @@ func addStruct(a *ast.GenDecl) {
api.Types[tp.Name] = &tp api.Types[tp.Name] = &tp
} }
func addFunction(a *ast.FuncDecl) { func addFunction(a *ast.FuncDecl) error {
md := manageComments(a.Doc) md := manageComments(a.Doc)
if md["API"] == "" { if md["API"] == "" {
return return nil
} }
llog("Adding Fuction: %s => %#v", a.Name, md) llog("Adding Fuction: %s => %#v", a.Name, md)
reqType := &APIParamType{} reqType := &APIParamType{}
resType := &APIParamType{} resType := &APIParamType{}
if len(a.Type.Params.List) > 1 { 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 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) { switch x := a.Type.Params.List[1].Type.(type) {
case *ast.StarExpr: case *ast.StarExpr:
reqType.Ispointer = true reqType.Ispointer = true
@ -139,33 +155,10 @@ func addFunction(a *ast.FuncDecl) {
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name] api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name reqType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
} }
case *ast.ArrayType: default:
reqType.IsArray = true return errors.New(fmt.Sprintf("Function %s does not have 2 IN parameters (context and pointer to req struct)", a.Name.Name))
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 a.Type.Results != nil && len(a.Type.Results.List) > 0 { }
switch x := a.Type.Results.List[0].Type.(type) { switch x := a.Type.Results.List[0].Type.(type) {
case *ast.StarExpr: case *ast.StarExpr:
@ -177,29 +170,8 @@ func addFunction(a *ast.FuncDecl) {
api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name] api.UsedImportsFunctions[y.X.(*ast.Ident).Name] = api.Imports[y.X.(*ast.Ident).Name]
resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name resType.Typename = y.X.(*ast.Ident).Name + "." + y.Sel.Name
} }
case *ast.ArrayType: default:
resType.IsArray = true return errors.New(fmt.Sprintf("Function %s does not have 2 OUT parameters (pointer to res struct and err)", a.Name.Name))
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
} }
@ -227,8 +199,8 @@ func addFunction(a *ast.FuncDecl) {
} }
api.Methods[a.Name.Name] = &fn api.Methods[a.Name.Name] = &fn
}
return nil
} }
func load(src string) error { func load(src string) error {
@ -255,6 +227,11 @@ func load(src string) error {
// Print the AST. // Print the AST.
ast.Inspect(v, func(n ast.Node) bool { 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) { switch x := n.(type) {
case *ast.GenDecl: case *ast.GenDecl:
@ -278,7 +255,10 @@ func load(src string) error {
case *ast.File: case *ast.File:
manageCommentsGroups(x.Comments) manageCommentsGroups(x.Comments)
case *ast.FuncDecl: case *ast.FuncDecl:
addFunction(x) err = addFunction(x)
if err != nil {
return false
}
llog("Adding fn: %s", x.Name) llog("Adding fn: %s", x.Name)
case *ast.ValueSpec: case *ast.ValueSpec:
if x.Names[0].Name == "BASEPATH" { if x.Names[0].Name == "BASEPATH" {
@ -341,5 +321,5 @@ func load(src string) error {
api.Namespace = packageName api.Namespace = packageName
return nil return err
} }

View File

@ -31,12 +31,113 @@ func processGoServerOutput(f string) error {
"encoding/json" "encoding/json"
"strings" "strings"
"net/http" "net/http"
)`) "errors"
"reflect"
"strconv"
)`)
for k := range api.UsedImportsFunctions { for k := range api.UsedImportsFunctions {
WNL(`import "%s"`, k) 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 { WNL(`type API struct {
Mux *http.ServeMux Mux *http.ServeMux
Perms map[string]string 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 { for _, v := range api.Methods {
WNL(`func h_%s(w http.ResponseWriter, r *http.Request) { WNL(`func h_%s(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@ -90,6 +156,12 @@ func (a *API) GetPerm(r *http.Request) string {
WNL(" req := %s", ResImplType(v.ReqType)) 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 {`) WNL(` if r.Method!=http.MethodGet && r.Method!=http.MethodHead {`)
if v.ReqType.Ispointer || v.ReqType.IsArray { 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) return os.WriteFile(f, buf.Bytes(), 0600)
} }

58
test/demo/api.py 100644
View File

@ -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

101
test/demo/api.ts 100644
View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -0,0 +1,3 @@
POST http://localhost:8080/some
{"fielda":"Hello World","fieldb":""}

View File

@ -25,7 +25,7 @@ type AStr struct {
@PERM: ASD @PERM: ASD
@VERB: POST @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) //print("Got:" + s)
//out = time.Now().String() + " - Hey Ya!" //out = time.Now().String() + " - Hey Ya!"
return return

View File

@ -1,25 +1,33 @@
package goapi package goapi
import "time"
import "crypto"
import ( import (
"bytes" "bytes"
"crypto"
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"time"
) )
var Basepath string = "" var Basepath string = ""
var Host string = "" var Host string = ""
var ExtraHeaders map[string]string = make(map[string]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) { func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
b := &bytes.Buffer{} b := &bytes.Buffer{}
err := json.NewEncoder(b).Encode(bodyo) err := json.NewEncoder(b).Encode(bodyo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
body := bytes.NewReader(b.Bytes()) body := bytes.NewReader(b.Bytes())
req, err := http.NewRequest(m, Host+Basepath+path, body) req, err := http.NewRequest(m, Host+Basepath+path, body)
if err != nil { if err != nil {
@ -32,19 +40,19 @@ func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
req.Header.Set(k, v) req.Header.Set(k, v)
} }
cli := http.Client{}
res, err := cli.Do(req) res, err := cli.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer res.Body.Close()
if res.StatusCode >= 400 { if res.StatusCode >= 400 {
bs, err := ioutil.ReadAll(res.Body) bs, err := ioutil.ReadAll(res.Body)
if err != nil { if err != nil {
panic(err) return nil, err
} }
return nil, errors.New(string(bs)) return nil, errors.New(string(bs))
} }
@ -53,65 +61,24 @@ func invoke(m string, path string, bodyo interface{}) (*json.Decoder, error) {
} }
type AStr struct { type AStr struct {
Arrofpstr []string
City string
Country string
HouseNumber int64 HouseNumber int64
IsCondo bool
Recursive map[string]AStr
Some crypto.Decrypter
SomeWeirdTest string SomeWeirdTest string
Recursive map[string]AStr
When time.Time 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 var dec *json.Decoder
dec, err = invoke("POST", "/someapi", req) dec, err = invoke("POST", "/someapi", req)
if err != nil { if err != nil {
return return
} }
var ret string var ret *AStr = &AStr{}
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
err = dec.Decode(ret) err = dec.Decode(ret)
return ret, err return ret, err
} }