Implement all but the funcs themselves

This commit is contained in:
Thilo Karraß 2020-10-14 09:03:01 +02:00
parent 9afdada7e8
commit 261d4fb295
6 changed files with 461 additions and 33 deletions

View File

@ -36,19 +36,39 @@ func executeGenerate(cmd *cobra.Command, args []string) error {
log.WithError(err).Fatal("cannot load input data") log.WithError(err).Fatal("cannot load input data")
} }
// path
// - parametres
// - methods
// - parameters
// - body
// - response
ops := make(map[string/*tag*/][]*openapi.Operation)
log.Infof("API: %v, %v", api.Info.Title, api.Info.Version) log.Infof("API: %v, %v", api.Info.Title, api.Info.Version)
log.Info("Paths:") log.Info("Paths:")
for k, _ := range api.Paths { for k, v := range api.Paths {
log.Infof(" - %v", k) name := k
for m, n := range api.Paths[k].Operations() { log.Infof(" - %v", name)
for m, n := range v.Operations() {
v.Name = k
tag := ""
if len(n.Tags) > 0 {
tag = n.Tags[0]
}
log.Infof( " [%v] %v: %v", m, n.OperationId, n.Summary) log.Infof( " [%v] %v: %v", m, n.OperationId, n.Summary)
//if len(ops[tag])==0 {
// ops[tag]= make([]*openapi.Operation)
//}
ops[tag] = append(ops[tag], n)
} }
} }
os.Mkdir(packageName, 0755) os.Mkdir(packageName, 0755)
os.Mkdir(path.Join(packageName, "schema"), 0755) //os.Mkdir(path.Join(packageName, "model"), 0755)
tEnc := encode.NewEncoder(packageName) tEnc := encode.NewEncoder(packageName, api)
/// schema
log.Info("Types:") log.Info("Types:")
for k, v := range api.Components.Schemas { for k, v := range api.Components.Schemas {
log.Infof("File: %v.go", strings.ToLower(k)) log.Infof("File: %v.go", strings.ToLower(k))
@ -72,11 +92,42 @@ func executeGenerate(cmd *cobra.Command, args []string) error {
log.Warnf("no data in %v", k) log.Warnf("no data in %v", k)
continue continue
} }
err = ioutil.WriteFile(path.Join(packageName, k + ".go"), []byte(tData[:]) ,0644) err = ioutil.WriteFile(path.Join(packageName, "model_" + k + ".go"), []byte(tData[:]) ,0644)
if err != nil { if err != nil {
log.WithError(err).Fatal("cannot create file") log.WithError(err).Fatal("cannot create file")
} }
}
log.Info("Parameters:")
for k, v := range api.Components.Parameters {
log.Infof(" - %v (%v) [%v]: %v", k, v.In, v.Schema.GoType(), v.Description)
}
log.Info("Paths again:")
for tag, v := range ops {
log.Infof("tag: %v", tag)
for method, op := range v {
log.Infof(" - [%v]%v: %v", method, op.OperationId, op.Summary)
log.Infof(" %v", op.Path().Name)
for _, p := range op.Path().Parameters {
log.Infof(" %v", p.Ref)
}
for _, p := range op.Parameters {
log.Infof(" %v: %v", p.In, p.Name)
}
}
tData := tEnc.Funcs(tag, v)
log.Infof("%v.go: %v", tag, tData)
err = ioutil.WriteFile(path.Join(packageName, "api_" + tag + ".go"), []byte(tData[:]) ,0644)
if err != nil {
log.WithError(err).Fatal("cannot create file")
}
}
log.Info("writing api.go")
err = ioutil.WriteFile(path.Join(packageName, "api.go"), []byte(tEnc.Api(*api.Servers[0])[:]) ,0644)
if err != nil {
log.WithError(err).Fatal("cannot create file")
} }
return nil return nil

16
encode/api.go Normal file
View File

@ -0,0 +1,16 @@
package encode // import "udico.de/uditaren/opier/encode"
import (
"bytes"
"fmt"
"strings"
"udico.de/opier/openapi"
)
func (e Encoder) Api(server openapi.Server) string {
tBuf := bytes.Buffer{}
tBuf.WriteString(fmt.Sprintf("package %v\n\n", e.Package))
tBuf.WriteString(e.GeneratedHeader())
tBuf.WriteString(Comment(server.Description, 0))
tBuf.WriteString(fmt.Sprintf("const %v_OPENAPI_BASE string = \"%v\"", strings.ToUpper(e.Package), server.Url))
return tBuf.String()
}

View File

@ -2,7 +2,6 @@ package encode // import "udico.de/uditaren/opier/encode"
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"strings"
"udico.de/opier/openapi" "udico.de/opier/openapi"
) )
@ -10,13 +9,7 @@ func (e Encoder) Array(name string, schema openapi.Schema) string {
tBuf := bytes.Buffer{} tBuf := bytes.Buffer{}
tBuf.WriteString(fmt.Sprintf("package %v\n\n", e.Package)) tBuf.WriteString(fmt.Sprintf("package %v\n\n", e.Package))
tBuf.WriteString(e.GeneratedHeader()) tBuf.WriteString(e.GeneratedHeader())
tBuf.WriteString(Comment(schema.Description, 0))
if schema.Description != "" {
tDesc := strings.Split(strings.TrimSpace(schema.Description), "\n")
for _, tLine := range tDesc {
tBuf.WriteString(fmt.Sprintf("// %v\n", tLine))
}
}
tBuf.WriteString(fmt.Sprintf("type %v []%v\n", name, schema.Items.GoType())) tBuf.WriteString(fmt.Sprintf("type %v []%v\n", name, schema.Items.GoType()))
return tBuf.String() return tBuf.String()
} }

View File

@ -1,16 +1,20 @@
package encode // import "udico.de/uditaren/opier/encode" package encode // import "udico.de/uditaren/opier/encode"
import ( import (
"fmt"
"strings" "strings"
"udico.de/opier/openapi"
"unicode" "unicode"
) )
type Encoder struct { type Encoder struct {
Package string Package string
api *openapi.OpenAPI
} }
func NewEncoder(pkg string) *Encoder { func NewEncoder(pkg string, api *openapi.OpenAPI) *Encoder {
return &Encoder{ return &Encoder{
Package: pkg, Package: pkg,
api: api,
} }
} }
@ -40,4 +44,17 @@ func (e Encoder) GeneratedHeader() string {
***********************************************/ ***********************************************/
` `
}
func Comment(aComment string, indent int) string {
tBuf := &strings.Builder{}
tComment := strings.TrimSpace(aComment)
tInd := strings.Repeat(" ", indent)
if tComment != "" {
tCommentLines := strings.Split(tComment, "\n")
for _, tCommentLine := range tCommentLines {
tBuf.WriteString(fmt.Sprintf("%v// %v\n", tInd, tCommentLine))
}
}
return tBuf.String()
} }

215
encode/func.go Normal file
View File

@ -0,0 +1,215 @@
package encode // import "udico.de/uditaren/opier/encode"
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"udico.de/opier/openapi"
)
type simpleparam struct {
Name string
Type string
In string
}
// implement sort interface on simpleparam
type simpleparamslice []*simpleparam
func (s simpleparamslice) Len() int { return len(s) }
func (s simpleparamslice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s simpleparamslice) Less(i, j int) bool {
val := func(s string) int {
switch s {
case "path":
return 0
case "head":
return 1
case "query":
return 2
}
return 99
}
iVal := val(s[i].In)
jVal := val(s[j].In)
iStr := s[i].Name
jStr := s[j].Name
if iVal == jVal {
return strings.Compare(iStr, jStr) < 0
}
return iVal < jVal
}
// Funcs generates all functions for a given tag
//
// tag is a
// tag name
func (e Encoder) Funcs(tag string, operations []*openapi.Operation) string {
tBuf := bytes.Buffer{}
tBuf.WriteString(fmt.Sprintf("package %v\n\n", e.Package))
tBuf.WriteString(e.GeneratedHeader())
tBuf.WriteString(e.tagType(tag))
tFuncPrefix := "p" + NormalizeName(tag)
for _, op := range operations {
// name -> type
tParams := make([]*simpleparam, 0, 5)
if op.Description != "" {
tDesc := strings.Split(strings.TrimSpace(op.Description), "\n")
for _, tLine := range tDesc {
tBuf.WriteString(fmt.Sprintf("// %v\n", tLine))
}
}
for _, params := range op.Path().Parameters {
tBuf.WriteString("// param [PATH] ")
if params.Ref != "" {
// ref param
tRefParam := e.api.ResolveParameter(params.Ref)
tBuf.WriteString(fmt.Sprintf("REF: %v\n", params.Ref))
tBuf.WriteString(Comment(tRefParam.Name, 2))
tParams = append(tParams, &simpleparam{
Name: tRefParam.Name,
Type: tRefParam.Schema.GoType(),
In: "path",
})
} else {
tBuf.WriteString(fmt.Sprintf("TYPE: %v\n", params.Schema.Type))
tParams = append(tParams, &simpleparam{
Name: params.Name,
Type: params.Schema.GoType(),
In: "path",
})
}
}
for _, params := range op.Parameters {
//tBuf.WriteString("// param ")
tBuf.WriteString(Comment(fmt.Sprintf("param [%v] %v: %v\n", params.In, params.Name, params.Description), 0))
tParams = append(tParams, &simpleparam{
Name: params.Name,
Type: params.Schema.GoType(),
In: string(params.In),
})
}
if op.RequestBody != nil {
tBuf.WriteString(fmt.Sprintf("// body: %v\n", op.RequestBody.Description))
for k, v := range op.RequestBody.Content {
tBuf.WriteString(fmt.Sprintf("// [%v]: %v\n", k, v.Schema.Ref))
}
}
var tResponseType *openapi.ResponseObject = nil
for tCode, tResponse := range op.Responses {
tBuf.WriteString(Comment(fmt.Sprintf("response [%v]: %v\n", tCode, tResponse.Description), 0))
tCodeVal, _ := strconv.Atoi(tCode)
if tCodeVal/100 == 2 {
tResponseType = tResponse
}
}
tResponses := make([]*simpleparam, 0, 2)
if tResponseType != nil {
for k, v := range tResponseType.Headers {
// each given responseheader is a return parameter
tResponses = append(tResponses, &simpleparam{
Name: k,
Type: v.Schema.TypeOrReference(),
In: "head",
})
}
if len(tResponseType.Content) > 0 {
tSchema := tResponseType.Content["application/json"].Schema
tResponses = append(tResponses, &simpleparam{
Name: "ret",
Type: tSchema.GoType(),
In: "body",
})
}
}
sort.Sort(simpleparamslice(tParams))
sort.Sort(simpleparamslice(tResponses))
// parameter list:
tResponses = append(tResponses, &simpleparam{
Name: "err",
Type: "error",
})
tParamBuf := &strings.Builder{}
for i, tParam := range tParams {
if i > 0 {
tParamBuf.WriteString(", ")
}
tParamBuf.WriteString(NormalizeName(tParam.Name))
tParamBuf.WriteString(" " + tParam.Type)
}
// body parameter:
if op.RequestBody != nil {
ref := op.RequestBody.Content["application/json"].Schema.Ref
if ref != "" {
if tParamBuf.Len() > 0 {
tParamBuf.WriteString(", ")
}
tParamBuf.WriteString(fmt.Sprintf("data *%v", e.api.ResolveSchema(ref).Name()))
} else {
tParamBuf.WriteString(" /* IMPLEMENT BODY SCHEMA which is not $ref */")
}
}
tParamStr := tParamBuf.String()
tResponseBuf := &strings.Builder{}
tResponseBuf.WriteString("(")
for i, tResponse := range tResponses {
if i > 0 {
tResponseBuf.WriteString(", ")
}
tResponseBuf.WriteString(tResponse.Name + " ")
tResponseBuf.WriteString(tResponse.Type)
}
tResponseBuf.WriteString(")")
tResponseStr := tResponseBuf.String()
tBuf.WriteString(fmt.Sprintf("func (p %v) %v(%v) %v {\n", tFuncPrefix, NormalizeName(op.OperationId), tParamStr, tResponseStr))
// add header parameters
// insert path parameters
// add query parameters
// post/put params ("body")
tBuf.WriteString(fmt.Sprintf(" // [%v]%v operation here \n", op.Method(), op.Path().Name))
tBuf.WriteString(fmt.Sprintf(" return\n}\n\n"))
}
return tBuf.String()
}
func (e Encoder) tagType(tag string) string {
tNorm := NormalizeName(tag)
tBuf := bytes.Buffer{}
tBuf.WriteString("import \"net/http\"\n\n")
tBuf.WriteString(Comment(e.getTagDescription(tag), 0))
tBuf.WriteString(fmt.Sprintf("type p%v struct {\n", tNorm))
tBuf.WriteString(" client *http.Client\n")
tBuf.WriteString(" base string\n")
tBuf.WriteString("}\n\n")
tBuf.WriteString(fmt.Sprintf("func %v(aClient *http.Client, aBase string) p%v {\n", NormalizeName("new-"+tag), tNorm))
tBuf.WriteString(fmt.Sprintf(" return p%v{\n", tNorm))
tBuf.WriteString(" client: aClient,\n")
tBuf.WriteString(" base: aBase,\n")
tBuf.WriteString(" }\n")
tBuf.WriteString("}\n\n")
return tBuf.String()
}
func (e Encoder) getTagDescription(tag string) string {
for _, v := range e.api.Tags {
if v.Name == tag {
return v.Description
}
}
return ""
}

View File

@ -14,9 +14,11 @@ type OpenAPI struct {
Version string Version string
Description string Description string
} }
Tags []*TagObject `yaml:"tags"`
Paths map[string]*PathItem `yaml:"paths"` Paths map[string]*PathItem `yaml:"paths"`
Components *Components Components *Components
Servers []*Server `yaml:"servers"`
} }
func Load(fname string) (*OpenAPI, error) { func Load(fname string) (*OpenAPI, error) {
@ -30,6 +32,45 @@ func Load(fname string) (*OpenAPI, error) {
return tRet, err return tRet, err
} }
func (o OpenAPI) ResolveParameter(name string) *Parameter {
if !strings.HasPrefix(name, "#/components/parameters/") {
return nil
}
tName := strings.TrimPrefix(name, "#/components/parameters/")
for pName, param := range o.Components.Parameters {
if tName == pName {
tParam := param
return &tParam
}
}
return nil
}
func (o OpenAPI) ResolveSchema(name string) *Schema {
if !strings.HasPrefix(name, "#/components/schemas/") {
return nil
}
tName := strings.TrimPrefix(name, "#/components/schemas/")
for pSch, tSchema := range o.Components.Schemas {
if tName == pSch {
tSchemaP := tSchema
tSchemaP.name = tName
return &tSchemaP
}
}
return nil
}
type Server struct {
Url string `yaml:"url"`
Description string `yaml:"description"`
}
type TagObject struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
}
// PathItem Describes the operations available on a single path. A Path Item MAY be empty, due to ACL constraints. // PathItem Describes the operations available on a single path. A Path Item MAY be empty, due to ACL constraints.
// The path itself is still exposed to the documentation viewer but they will not know which operations and parameters // The path itself is still exposed to the documentation viewer but they will not know which operations and parameters
// are available. // are available.
@ -40,6 +81,8 @@ type PathItem struct {
// the behavior is undefined. // the behavior is undefined.
Ref string `yaml:"$ref"` Ref string `yaml:"$ref"`
Parameters []*Parameter
// An optional, string summary, intended to apply to all operations in this path. // An optional, string summary, intended to apply to all operations in this path.
Summary string Summary string
@ -55,32 +98,52 @@ type PathItem struct {
Head *Operation Head *Operation
Patch *Operation Patch *Operation
Trace *Operation Trace *Operation
// for internal bookkeeping
Name string
} }
func (p PathItem) Operations() map[string]*Operation { // Never feed the mighty spaghetti (code) monster :(
func (p *PathItem) Operations() map[string]*Operation {
tRet := make(map[string]*Operation) tRet := make(map[string]*Operation)
if p.Get != nil { if p.Get != nil {
p.Get.path = p
p.Get.method = "get"
tRet["get"] = p.Get tRet["get"] = p.Get
} }
if p.Put != nil { if p.Put != nil {
p.Put.path = p
p.Put.method = "put"
tRet["put"] = p.Put tRet["put"] = p.Put
} }
if p.Post != nil { if p.Post != nil {
p.Post.path = p
p.Post.method = "post"
tRet["post"] = p.Post tRet["post"] = p.Post
} }
if p.Delete != nil { if p.Delete != nil {
p.Delete.path = p
p.Delete.method = "delete"
tRet["delete"] = p.Delete tRet["delete"] = p.Delete
} }
if p.Options != nil { if p.Options != nil {
p.Options.path = p
p.Options.method = "options"
tRet["options"] = p.Options tRet["options"] = p.Options
} }
if p.Head != nil { if p.Head != nil {
p.Head.path = p
p.Head.method = "head"
tRet["head"] = p.Head tRet["head"] = p.Head
} }
if p.Patch != nil { if p.Patch != nil {
p.Patch.path = p
p.Patch.method = "patch"
tRet["patch"] = p.Patch tRet["patch"] = p.Patch
} }
if p.Trace != nil { if p.Trace != nil {
p.Trace.path = p
p.Trace.method = "trace"
tRet["trace"] = p.Trace tRet["trace"] = p.Trace
} }
return tRet return tRet
@ -107,9 +170,15 @@ type Operation struct {
OperationId string `yaml:"operationId"` OperationId string `yaml:"operationId"`
//Parameters [] ... //Parameters [] ...
// requestBodyesObject // required Parameters []*Parameter
// callba
// Responses Responscks RequestBody *RequestBodyObject `yaml:"requestBody"`
// callbacks
// Responses Respons
Responses map[string]*ResponseObject
Deprecated bool Deprecated bool
// A declaration of which security mechanisms can be used for this operation. // A declaration of which security mechanisms can be used for this operation.
@ -122,10 +191,59 @@ type Operation struct {
// An alternative server array to service this operation. If an alternative server object is specified at the Path // An alternative server array to service this operation. If an alternative server object is specified at the Path
// Item Object or Root level, it will be overridden by this value. // Item Object or Root level, it will be overridden by this value.
// Servers // Servers
// we need a backref to the Path for building nice maps
path *PathItem
method string
} }
type ResponsesObject struct { func (o Operation) Path() *PathItem {
// ... return o.path
}
func (o Operation) Method() string {
return o.method
}
type RequestBodyObject struct {
Description string `yaml:"description"`
Content map[string]*MediaTypeObject `yaml:"content"`
Required bool `yaml:"required"`
}
type ResponseObject struct {
Ref string `yaml:"$ref"`
Description string `yaml:"description"`
Headers map[string]*Parameter `yaml:"headers"`
Content map[string]*MediaTypeObject `yaml:"content"`
Links map[string]*LinkObject `yaml:"links"`
}
func (r ResponseObject) String() string {
ret := ""
for _, v := range r.Content {
if v.Schema != nil {
if v.Schema.Ref != "" {
ret = v.Schema.Ref
} else {
ret = "IMPLEMENT ME!!!"
}
break // the for loop
}
}
return ret
}
// The Header Object follows the structure of the Parameter Object with the following changes:
// * name MUST NOT be specified, it is given in the corresponding headers map.
// * in MUST NOT be specified, it is implicitly in header.
// * All traits that are affected by the location MUST be applicable to a location of header (for example, style).
type HeaderObject struct {
Parameter
}
type LinkObject struct {
// nope, we just don't want to have these …
} }
type Components struct { type Components struct {
@ -158,15 +276,15 @@ type Parameter struct {
///--- either ///--- either
Style string Style string
Explode bool Explode bool
AllowReserved bool `yaml:"allowReserved"` AllowReserved bool `yaml:"allowReserved"`
Schema *Schema Schema *Schema
// example // example
// examples // examples
///--- or ///--- or
Content map[string]*MediaType Content map[string]*MediaTypeObject
} }
type Schema struct { type Schema struct {
@ -179,16 +297,23 @@ type Schema struct {
// when type==object // when type==object
Properties map[string]*Schema Properties map[string]*Schema
Format string Format string
Minimum int Minimum int
Maximum int Maximum int
Default interface{} Default interface{}
Enum []interface{} Enum []interface{}
XEnumVarnames []string `yaml:"x-enum-varnames"` XEnumVarnames []string `yaml:"x-enum-varnames"`
Required []string Required []string
Description string Description string
// Only filled in by ResolveSchema(...)
name string
}
func (s Schema) Name() string {
return s.name
} }
func (s Schema) TypeOrReference() string { func (s Schema) TypeOrReference() string {
@ -211,6 +336,10 @@ func (s Schema) GoType() string {
tRet = "bool" tRet = "bool"
case "array": case "array":
tRet = fmt.Sprintf("[]%v", s.Items.GoType()) tRet = fmt.Sprintf("[]%v", s.Items.GoType())
case "": // arrays may not be tagged as such. deduce from the items element:
if s.Items != nil {
tRet = fmt.Sprintf("[]%v", s.Items.GoType())
}
} }
} }
return tRet return tRet
@ -235,11 +364,6 @@ func (s Schema) EnumNames() ([]string, int) {
} }
type Referencable interface { type Referencable interface {
}
type MediaType struct {
} }
func NormalizeName(aName string) string { func NormalizeName(aName string) string {
@ -259,3 +383,15 @@ func NormalizeName(aName string) string {
} }
return tRet.String() return tRet.String()
} }
type MediaTypeObject struct {
// The schema defining the content of the request, response, or parameter.
Schema *Schema `yaml:"schema"`
Example string `yaml:"example"`
//examples
//encoding
}
type SchemaObject struct {
Ref string `yaml:"$ref"`
}