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")
}
// 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.Info("Paths:")
for k, _ := range api.Paths {
log.Infof(" - %v", k)
for m, n := range api.Paths[k].Operations() {
for k, v := range api.Paths {
name := k
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)
//if len(ops[tag])==0 {
// ops[tag]= make([]*openapi.Operation)
//}
ops[tag] = append(ops[tag], n)
}
}
os.Mkdir(packageName, 0755)
os.Mkdir(path.Join(packageName, "schema"), 0755)
tEnc := encode.NewEncoder(packageName)
//os.Mkdir(path.Join(packageName, "model"), 0755)
tEnc := encode.NewEncoder(packageName, api)
/// schema
log.Info("Types:")
for k, v := range api.Components.Schemas {
log.Infof("File: %v.go", strings.ToLower(k))
@ -72,12 +92,43 @@ func executeGenerate(cmd *cobra.Command, args []string) error {
log.Warnf("no data in %v", k)
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 {
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
}

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

View File

@ -1,16 +1,20 @@
package encode // import "udico.de/uditaren/opier/encode"
import (
"fmt"
"strings"
"udico.de/opier/openapi"
"unicode"
)
type Encoder struct {
Package string
api *openapi.OpenAPI
}
func NewEncoder(pkg string) *Encoder {
func NewEncoder(pkg string, api *openapi.OpenAPI) *Encoder {
return &Encoder{
Package: pkg,
api: api,
}
}
@ -41,3 +45,16 @@ 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
Description string
}
Tags []*TagObject `yaml:"tags"`
Paths map[string]*PathItem `yaml:"paths"`
Components *Components
Servers []*Server `yaml:"servers"`
}
func Load(fname string) (*OpenAPI, error) {
@ -30,6 +32,45 @@ func Load(fname string) (*OpenAPI, error) {
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.
// The path itself is still exposed to the documentation viewer but they will not know which operations and parameters
// are available.
@ -40,6 +81,8 @@ type PathItem struct {
// the behavior is undefined.
Ref string `yaml:"$ref"`
Parameters []*Parameter
// An optional, string summary, intended to apply to all operations in this path.
Summary string
@ -55,32 +98,52 @@ type PathItem struct {
Head *Operation
Patch *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)
if p.Get != nil {
p.Get.path = p
p.Get.method = "get"
tRet["get"] = p.Get
}
if p.Put != nil {
p.Put.path = p
p.Put.method = "put"
tRet["put"] = p.Put
}
if p.Post != nil {
p.Post.path = p
p.Post.method = "post"
tRet["post"] = p.Post
}
if p.Delete != nil {
p.Delete.path = p
p.Delete.method = "delete"
tRet["delete"] = p.Delete
}
if p.Options != nil {
p.Options.path = p
p.Options.method = "options"
tRet["options"] = p.Options
}
if p.Head != nil {
p.Head.path = p
p.Head.method = "head"
tRet["head"] = p.Head
}
if p.Patch != nil {
p.Patch.path = p
p.Patch.method = "patch"
tRet["patch"] = p.Patch
}
if p.Trace != nil {
p.Trace.path = p
p.Trace.method = "trace"
tRet["trace"] = p.Trace
}
return tRet
@ -107,9 +170,15 @@ type Operation struct {
OperationId string `yaml:"operationId"`
//Parameters [] ...
// requestBodyesObject // required
// callba
// Responses Responscks
Parameters []*Parameter
RequestBody *RequestBodyObject `yaml:"requestBody"`
// callbacks
// Responses Respons
Responses map[string]*ResponseObject
Deprecated bool
// 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
// Item Object or Root level, it will be overridden by this value.
// 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 {
@ -166,7 +284,7 @@ type Parameter struct {
// examples
///--- or
Content map[string]*MediaType
Content map[string]*MediaTypeObject
}
type Schema struct {
@ -189,6 +307,13 @@ type Schema struct {
Required []string
Description string
// Only filled in by ResolveSchema(...)
name string
}
func (s Schema) Name() string {
return s.name
}
func (s Schema) TypeOrReference() string {
@ -211,6 +336,10 @@ func (s Schema) GoType() string {
tRet = "bool"
case "array":
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
@ -235,11 +364,6 @@ func (s Schema) EnumNames() ([]string, int) {
}
type Referencable interface {
}
type MediaType struct {
}
func NormalizeName(aName string) string {
@ -259,3 +383,15 @@ func NormalizeName(aName string) 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"`
}