Skip to content

Instantly share code, notes, and snippets.

@cyberhck
Last active February 2, 2021 11:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cyberhck/37a286cbc3040ea3e61642fc151e2f5d to your computer and use it in GitHub Desktop.
Save cyberhck/37a286cbc3040ea3e61642fc151e2f5d to your computer and use it in GitHub Desktop.
test
package main
import (
"github.com/cyberhck/local/tonic"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Authorization string `in:"header" name:"Authorization"`
}
type UserResponse struct {
Name string `json:"name"`
Value string `json:"value"`
}
func main() {
t := tonic.New()
t.POST("/", NewController)
t.POST("/hello", NewController1)
t.Run()
}
func NewController(user User) UserResponse {
return UserResponse{
Name: user.Name + user.Email,
Value: user.Authorization,
}
}
func NewController1(user User) UserResponse {
return UserResponse{
Name: user.Name + user.Email,
Value: user.Authorization,
}
}
package tonic
import (
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/go-openapi/spec"
"reflect"
"strconv"
)
type Tonic struct {
router *gin.Engine
docs spec.Swagger
}
func New() Tonic {
tonic := Tonic{
router: gin.Default(),
docs: spec.Swagger{
SwaggerProps: spec.SwaggerProps{
Swagger: "2.0",
Consumes: []string{"application/json"},
Produces: []string{"application/json"},
Schemes: []string{"http", "https"},
Info: &spec.Info{
InfoProps: spec.InfoProps{
Description: "local server api docs",
Title: "local server",
TermsOfService: "TOS",
Contact: &spec.ContactInfo{
ContactInfoProps: spec.ContactInfoProps{
Name: "opn",
Email: "dev@opn.ooo",
},
},
Version: "2.0",
},
},
Paths: &spec.Paths{
Paths: map[string]spec.PathItem{
},
},
},
},
}
tonic.router.GET("/swagger", func(context *gin.Context) {
context.JSON(200, tonic.getDocs())
})
return tonic
}
func (t Tonic) getDocs() spec.Swagger {
return t.docs
}
func (t Tonic) POST(path string, handler interface{}) {
t.docs.SwaggerProps.Paths.Paths[path] = spec.PathItem{
PathItemProps: spec.PathItemProps{
Post: &spec.Operation{
OperationProps: spec.OperationProps{
},
},
},
}
t.router.POST(path, t.Handler(handler))
}
func (t Tonic) GET(path string, handler interface{}) {
t.router.GET(path, t.Handler(handler))
}
func (t Tonic) Run() {
t.router.Run()
}
func (t Tonic) verifyConditions(handler interface{}) {
if !t.isFunction(handler) {
panic("handler must be a function")
}
if t.getParameterCount(handler) != 1 {
panic("handler must accept exactly 1 argument")
}
if t.getFirstArgumentType(handler) != reflect.Struct {
panic("only parameter must be a struct")
}
}
func (t Tonic) isFunction(i interface{}) bool {
return reflect.TypeOf(i).Kind() == reflect.Func
}
func (t Tonic) getParameterCount(i interface{}) int {
return reflect.TypeOf(i).NumIn()
}
type factoryString = func(ctx *gin.Context) string
type factoryInt = func(ctx *gin.Context) int
func (t Tonic) getFactories(request reflect.Type) (map[string]factoryString, map[string]factoryInt) {
mString := make(map[string]factoryString)
mInt := make(map[string]factoryInt)
for i := 0; i < request.NumField(); i++ {
field := request.Field(i)
if field.Tag.Get("in") == "" {
continue
}
if field.Type.Kind() == reflect.Int {
mInt[field.Name] = t.getIntFactoryFor(field.Tag)
continue
}
if field.Type.Kind() == reflect.String {
mString[field.Name] = t.getStringFactoryFor(request.Field(i).Tag)
continue
}
panic("unsupported type: " + field.Type.Kind().String())
}
return mString, mInt
}
func (t Tonic) getStringFactoryFor(tag reflect.StructTag) factoryString {
if tag.Get("in") != "header" {
panic("currently only header supported on `in` tag") // implement others?
}
return func(ctx *gin.Context) string {
headerName := tag.Get("name")
return ctx.Request.Header.Get(headerName)
}
}
func (t Tonic) getIntFactoryFor(tag reflect.StructTag) factoryInt {
if tag.Get("in") != "header" {
panic("currently only header supported on `in` tag") // implement others?
}
return func(ctx *gin.Context) int {
headerName := tag.Get("name")
value, _ := strconv.Atoi(ctx.Request.Header.Get(headerName))
return value
}
}
func (t Tonic) Handler(handler interface{}) func(context *gin.Context) {
t.verifyConditions(handler)
request := reflect.TypeOf(handler).In(0)
stringFac, intFac := t.getFactories(request)
return func(context *gin.Context) {
req := reflect.New(request).Interface()
err := json.NewDecoder(context.Request.Body).Decode(&req)
if err != nil {
panic(err)
}
for key, value := range stringFac {
result := value(context)
reflect.ValueOf(req).Elem().FieldByName(key).SetString(result)
}
for key, value := range intFac {
result := value(context)
reflect.ValueOf(req).Elem().FieldByName(key).SetInt(int64(result))
}
response := reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(req).Elem()})
context.JSON(200, response[0].Interface())
}
}
func (t Tonic) getFirstArgumentType(i interface{}) reflect.Kind {
return reflect.TypeOf(i).In(0).Kind()
}
func (t Tonic) getReturnArgCount(i interface{}) int {
return reflect.TypeOf(i).NumOut()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment