mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-01-24 15:03:27 +01:00
Release catalyst
This commit is contained in:
342
generator/generator.go
Normal file
342
generator/generator.go
Normal file
@@ -0,0 +1,342 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/go-openapi/analysis"
|
||||
"github.com/go-swagger/go-swagger/generator"
|
||||
"github.com/iancoleman/strcase"
|
||||
"github.com/tidwall/sjson"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed templates/simplemodel.gotmpl
|
||||
var model embed.FS
|
||||
|
||||
func gotype(name string, s Schema, required []string) string {
|
||||
_, x := sgotype(name, s, required, false)
|
||||
return x
|
||||
}
|
||||
|
||||
func sgotype(name string, s Schema, required []string, nopointer bool) (bool, string) {
|
||||
req := ""
|
||||
if !nopointer && !contains(required, name) {
|
||||
req = "*"
|
||||
}
|
||||
|
||||
if s.Ref != "" {
|
||||
return false, req + path.Base(s.Ref)
|
||||
}
|
||||
|
||||
primitive := false
|
||||
t := ""
|
||||
|
||||
switch s.Type {
|
||||
case "string":
|
||||
if s.Format == "date-time" {
|
||||
t = req + "time.Time"
|
||||
} else {
|
||||
t = req + "string"
|
||||
primitive = true
|
||||
}
|
||||
case "boolean":
|
||||
t = req + "bool"
|
||||
primitive = true
|
||||
case "object":
|
||||
if s.AdditionalProperties != nil {
|
||||
subPrimitive, subType := sgotype(name, *s.AdditionalProperties, required, true)
|
||||
if subPrimitive {
|
||||
t = "map[string]" + subType
|
||||
} else {
|
||||
t = "map[string]*" + subType
|
||||
}
|
||||
} else {
|
||||
t = "interface{}"
|
||||
}
|
||||
case "number", "integer":
|
||||
if s.Format != "" {
|
||||
t = req + s.Format
|
||||
} else {
|
||||
t = req + "int"
|
||||
}
|
||||
primitive = true
|
||||
case "array":
|
||||
subPrimitive, subType := sgotype(name, *s.Items, required, true)
|
||||
if subPrimitive {
|
||||
t = "[]" + subType
|
||||
} else {
|
||||
t = "[]*" + subType
|
||||
}
|
||||
case "":
|
||||
t = "interface{}"
|
||||
default:
|
||||
panic(fmt.Sprintf("%#v", s))
|
||||
}
|
||||
|
||||
return primitive, t
|
||||
}
|
||||
|
||||
func omitempty(name string, required []string) bool {
|
||||
return !contains(required, name)
|
||||
}
|
||||
|
||||
func contains(required []string, name string) bool {
|
||||
for _, r := range required {
|
||||
if r == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func tojson(name string, i Definition) string {
|
||||
b, _ := json.Marshal(i)
|
||||
b, _ = sjson.SetBytes(b, "$id", "#/definitions/"+name)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func camel(s string) string {
|
||||
if s == "id" {
|
||||
return "ID"
|
||||
}
|
||||
return strcase.ToCamel(s)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
p := flag.Arg(0)
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
f, err := os.Open("generated/community.yml")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := Swagger{}
|
||||
dec := yaml.NewDecoder(f)
|
||||
err = dec.Decode(&s)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
t := template.New("simplemodel.gotmpl")
|
||||
t.Funcs(map[string]interface{}{
|
||||
"camel": camel,
|
||||
"gotype": gotype,
|
||||
"omitempty": omitempty,
|
||||
"tojson": tojson,
|
||||
})
|
||||
templ := template.Must(t.ParseFS(model, "templates/simplemodel.gotmpl"))
|
||||
|
||||
err = os.MkdirAll("generated/models", os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
props := map[string][]string{}
|
||||
for defName, definition := range s.Definitions {
|
||||
for propName := range definition.Properties {
|
||||
props[defName] = append(props[defName], propName)
|
||||
}
|
||||
}
|
||||
|
||||
// for _, definition := range s.Definitions {
|
||||
// if definition.Embed != "" {
|
||||
// if parentProps, ok := props[definition.Embed]; ok {
|
||||
// for _, parentProp := range parentProps {
|
||||
// delete(definition.Properties, parentProp)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
err = templ.Execute(buf, &s)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
fmtCode, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
fmtCode = buf.Bytes()
|
||||
}
|
||||
|
||||
err = os.WriteFile("generated/models/models.go", fmtCode, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
generator.FuncMapFunc = func(opts *generator.LanguageOpts) template.FuncMap {
|
||||
df := generator.DefaultFuncMap(opts)
|
||||
|
||||
df["path"] = func(basePath, lpath string, parameters generator.GenParameters) string {
|
||||
u := url.URL{Path: path.Join(basePath, lpath)}
|
||||
q := u.Query()
|
||||
for _, p := range parameters {
|
||||
if p.Location == "path" {
|
||||
if example, ok := p.Extensions["x-example"]; ok {
|
||||
u.Path = strings.ReplaceAll(u.Path, "{"+p.Name+"}", fmt.Sprint(example))
|
||||
}
|
||||
}
|
||||
if p.Location == "query" {
|
||||
if example, ok := p.Extensions["x-example"]; ok {
|
||||
q.Set(p.Name, fmt.Sprint(example))
|
||||
}
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
df["body"] = func(parameters generator.GenParameters) interface{} {
|
||||
for _, p := range parameters {
|
||||
if p.Location == "body" {
|
||||
if example, ok := p.Extensions["x-example"]; ok {
|
||||
return example
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
df["ginizePath"] = func(path string) string {
|
||||
return strings.Replace(strings.Replace(path, "{", ":", -1), "}", "", -1)
|
||||
}
|
||||
df["export"] = func(name string) string {
|
||||
return strings.ToUpper(name[0:1]) + name[1:]
|
||||
}
|
||||
df["basePaths"] = func(operations []generator.GenOperation) []string {
|
||||
var l []string
|
||||
var seen = map[string]bool{}
|
||||
for _, operation := range operations {
|
||||
if _, ok := seen[operation.BasePath]; !ok {
|
||||
l = append(l, strings.TrimPrefix(operation.BasePath, "/"))
|
||||
seen[operation.BasePath] = true
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
df["roles"] = func(reqs []analysis.SecurityRequirement) string {
|
||||
for _, req := range reqs {
|
||||
if req.Name == "roles" {
|
||||
var roles []string
|
||||
for _, scope := range req.Scopes {
|
||||
roles = append(roles, "role."+strcase.ToCamel(strings.ReplaceAll(scope, ":", "_")))
|
||||
// roles = append(roles, permission.FromString(scope))
|
||||
}
|
||||
return strings.Join(roles, ", ")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return df
|
||||
}
|
||||
|
||||
opts := &generator.GenOpts{
|
||||
Spec: "generated/community.yml",
|
||||
Target: "generated",
|
||||
APIPackage: "operations",
|
||||
ModelPackage: "models",
|
||||
ServerPackage: "restapi",
|
||||
ClientPackage: "client",
|
||||
DefaultScheme: "http",
|
||||
IncludeModel: true,
|
||||
IncludeValidator: true,
|
||||
IncludeHandler: true,
|
||||
IncludeParameters: true,
|
||||
IncludeResponses: true,
|
||||
IncludeURLBuilder: true,
|
||||
IncludeMain: true,
|
||||
IncludeSupport: true,
|
||||
ValidateSpec: true,
|
||||
FlattenOpts: &analysis.FlattenOpts{
|
||||
Minimal: true,
|
||||
Verbose: true,
|
||||
},
|
||||
Name: "catalyst-test",
|
||||
FlagStrategy: "go-flags",
|
||||
CompatibilityMode: "modern",
|
||||
Sections: generator.SectionOpts{
|
||||
Application: []generator.TemplateOpts{
|
||||
{
|
||||
Name: "api-server-test",
|
||||
Source: path.Join(p, "templates/api_server_test.gotmpl"),
|
||||
Target: "{{ .Target }}/test",
|
||||
FileName: "api_server_test.go",
|
||||
},
|
||||
// {
|
||||
// Name: "configure",
|
||||
// Source: "generator/config.gotmpl",
|
||||
// Target: "{{ joinFilePath .Target .ServerPackage }}",
|
||||
// FileName: "config.go",
|
||||
// SkipExists: false,
|
||||
// SkipFormat: false,
|
||||
// },
|
||||
{
|
||||
Name: "embedded_spec",
|
||||
Source: "asset:swaggerJsonEmbed",
|
||||
Target: "{{ joinFilePath .Target .ServerPackage }}",
|
||||
FileName: "embedded_spec.go",
|
||||
},
|
||||
{
|
||||
Name: "server",
|
||||
Source: path.Join(p, "templates/api.gotmpl"),
|
||||
Target: "{{ joinFilePath .Target .ServerPackage }}",
|
||||
FileName: "api.go",
|
||||
},
|
||||
{
|
||||
Name: "response.go",
|
||||
Source: path.Join(p, "templates/response.gotmpl"),
|
||||
Target: "{{ .Target }}/restapi/api",
|
||||
FileName: "response.go",
|
||||
},
|
||||
},
|
||||
Operations: []generator.TemplateOpts{
|
||||
{
|
||||
Name: "parameters",
|
||||
Source: path.Join(p, "templates/parameter.gotmpl"),
|
||||
Target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target .ServerPackage .APIPackage .Package }}{{ else }}{{ joinFilePath .Target .ServerPackage .Package }}{{ end }}",
|
||||
FileName: "{{ (snakize (pascalize .Name)) }}_parameters.go",
|
||||
},
|
||||
},
|
||||
Models: []generator.TemplateOpts{
|
||||
{
|
||||
Name: "definition",
|
||||
Source: "asset:model",
|
||||
Target: "{{ joinFilePath .Target .ModelPackage }}/old",
|
||||
FileName: "{{ (snakize (pascalize .Name)) }}.go",
|
||||
},
|
||||
// {
|
||||
// Name: "model",
|
||||
// Source: "generator/model.gotmpl",
|
||||
// Target: "{{ joinFilePath .Target .ModelPackage }}/old2",
|
||||
// FileName: "{{ (snakize (pascalize .Name)) }}.go",
|
||||
// },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = opts.EnsureDefaults()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = generator.GenerateServer("catalyst", nil, nil, opts)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
// loads.Spec()
|
||||
// swagger.
|
||||
}
|
||||
61
generator/swagger.go
Normal file
61
generator/swagger.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
type Swagger struct {
|
||||
Swagger string `yaml:"swagger" json:"swagger"`
|
||||
Info Info `yaml:"info" json:"info"`
|
||||
Paths map[string]PathItem `yaml:"paths" json:"paths"`
|
||||
Definitions map[string]Definition `yaml:"definitions" json:"definitions"`
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
Version string `yaml:"version" json:"version"`
|
||||
Title string `yaml:"title" json:"title"`
|
||||
}
|
||||
|
||||
type PathItem struct {
|
||||
Get Operation `yaml:"get" json:"get"`
|
||||
Post Operation `yaml:"post" json:"post"`
|
||||
}
|
||||
|
||||
type Operation struct {
|
||||
Tags []string `yaml:"tags" json:"tags"`
|
||||
Summary string `yaml:"summary" json:"summary"`
|
||||
Description string `yaml:"description" json:"description"`
|
||||
OperationID string `yaml:"operationId" json:"operationId"`
|
||||
Parameters []Parameter `yaml:"parameters" json:"parameters"`
|
||||
Responses map[string]Response `yaml:"responses" json:"responses"`
|
||||
}
|
||||
|
||||
type Parameter struct {
|
||||
// Description string `yaml:"description" json:"description"`
|
||||
Name string `yaml:"name" json:"name"`
|
||||
In string `yaml:"in" json:"in"`
|
||||
Required bool `yaml:"required" json:"required"`
|
||||
Schema Schema `yaml:"schema" json:"schema"`
|
||||
Direct Schema `yaml:"-,inline" json:"-,inline"`
|
||||
}
|
||||
|
||||
type Schema struct {
|
||||
Ref string `yaml:"$ref,omitempty" json:"$ref,omitempty"`
|
||||
Format string `yaml:"format,omitempty" json:"format,omitempty"`
|
||||
Title string `yaml:"title,omitempty" json:"title,omitempty"`
|
||||
Description string `yaml:"description" json:"description,omitempty"`
|
||||
Default interface{} `yaml:"default,omitempty" json:"default,omitempty"`
|
||||
Maximum interface{} `yaml:"maximum,omitempty" json:"maximum,omitempty"`
|
||||
Items *Schema `yaml:"items,omitempty" json:"items,omitempty"`
|
||||
Type string `yaml:"type" json:"type,omitempty"`
|
||||
AdditionalProperties *Schema `yaml:"additionalProperties,omitempty" json:"additionalProperties,omitempty"`
|
||||
Enum []string `yaml:"enum,omitempty" json:"enum,omitempty"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Description string `yaml:"description" json:"description"`
|
||||
Schema Schema `yaml:"schema" json:"schema"`
|
||||
}
|
||||
|
||||
type Definition struct {
|
||||
Type string `yaml:"type" json:"type"`
|
||||
Required []string `yaml:"required,omitempty" json:"required,omitempty"`
|
||||
Embed string `yaml:"x-embed" json:"x-embed"`
|
||||
Properties map[string]Schema `yaml:"properties" json:"properties"`
|
||||
}
|
||||
124
generator/templates/api.gotmpl
Normal file
124
generator/templates/api.gotmpl
Normal file
@@ -0,0 +1,124 @@
|
||||
package {{ .APIPackage }}
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
{{range .DefaultImports}}{{printf "%q" .}}
|
||||
{{end}}
|
||||
{{range $key, $value := .Imports}}{{$key}} {{ printf "%q" $value}}
|
||||
{{end}}
|
||||
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
|
||||
"github.com/SecurityBrewery/catalyst/role"
|
||||
)
|
||||
|
||||
// Service is the interface that must be implemented in order to provide
|
||||
// business logic for the Server service.
|
||||
type Service interface {
|
||||
{{range .Operations}}{{ pascalize .Name }}(ctx context.Context{{ if .Params }}, params *{{.Package}}.{{ pascalize .Name }}Params{{ end }}) *api.Response
|
||||
{{end}}
|
||||
}
|
||||
|
||||
// Config defines the config options for the API server.
|
||||
type Config struct {
|
||||
Address string
|
||||
InsecureHTTP bool
|
||||
TLSCertFile string
|
||||
TLSKeyFile string
|
||||
}
|
||||
|
||||
// Server defines the Server service.
|
||||
type Server struct {
|
||||
*gin.Engine
|
||||
config *Config
|
||||
server *http.Server
|
||||
service Service
|
||||
|
||||
{{ range .Operations | basePaths }}{{ . | export }}Group *gin.RouterGroup
|
||||
{{end}}
|
||||
|
||||
RoleAuth func([]role.Role) gin.HandlerFunc
|
||||
}
|
||||
|
||||
// New initializes a new Server service.
|
||||
func New(svc Service, config *Config) *Server {
|
||||
engine := gin.New()
|
||||
engine.Use(gin.Recovery())
|
||||
|
||||
return &Server{
|
||||
Engine: engine,
|
||||
service: svc,
|
||||
config: config,
|
||||
server: &http.Server{
|
||||
Addr: config.Address,
|
||||
Handler: engine,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
},
|
||||
|
||||
{{range .Operations | basePaths }}{{ . | export }}Group: engine.Group("/{{.}}"),
|
||||
{{end}}
|
||||
RoleAuth: func(i []role.Role) gin.HandlerFunc { return func(c *gin.Context) { c.Next() } },
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigureRoutes configures the routes for the Server service.
|
||||
// Configuring of routes includes setting up Auth if it is enabled.
|
||||
func (s *Server) ConfigureRoutes() {
|
||||
{{range .Operations}}s.{{ slice .BasePath 1 | export }}Group.{{.Method}}({{ .Path | ginizePath | printf "%q" }}, s.RoleAuth([]role.Role{ {{ .SecurityRequirements | roles }} }), {{.Package}}.{{ pascalize .Name }}Endpoint(s.service.{{ pascalize .Name }}))
|
||||
{{end}}}
|
||||
|
||||
// run the Server. It will listen on either HTTP or HTTPS depending on the
|
||||
// config passed to NewServer.
|
||||
func (s *Server) run() error {
|
||||
log.Printf("Serving on address %s\n", s.server.Addr)
|
||||
if s.config.InsecureHTTP {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
return s.server.ListenAndServeTLS(s.config.TLSCertFile, s.config.TLSKeyFile)
|
||||
}
|
||||
|
||||
// Shutdown will gracefully shutdown the Server.
|
||||
func (s *Server) Shutdown() error {
|
||||
return s.server.Shutdown(context.Background())
|
||||
}
|
||||
|
||||
// RunWithSigHandler runs the Server with SIGTERM handling automatically
|
||||
// enabled. The server will listen for a SIGTERM signal and gracefully shutdown
|
||||
// the web server.
|
||||
// It's possible to optionally pass any number shutdown functions which will
|
||||
// execute one by one after the webserver has been shutdown successfully.
|
||||
func (s *Server) RunWithSigHandler(shutdown ...func() error) error {
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigCh
|
||||
s.Shutdown()
|
||||
}()
|
||||
|
||||
err := s.run()
|
||||
if err != nil {
|
||||
if err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range shutdown {
|
||||
err := fn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
184
generator/templates/api_server_test.gotmpl
Normal file
184
generator/templates/api_server_test.gotmpl
Normal file
@@ -0,0 +1,184 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/SecurityBrewery/catalyst/database"
|
||||
"github.com/go-openapi/swag"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
"github.com/SecurityBrewery/catalyst/database/busdb"
|
||||
"github.com/SecurityBrewery/catalyst/generated/models"
|
||||
"github.com/SecurityBrewery/catalyst/test"
|
||||
)
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
type args struct {
|
||||
method string
|
||||
url string
|
||||
data interface{}
|
||||
}
|
||||
type want struct {
|
||||
status int
|
||||
body interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{{range .Operations}}
|
||||
{
|
||||
name: "{{ pascalize .Name }}",
|
||||
args: args{method: "{{ .Method }}", url: {{ path .BasePath .Path .Params | printf "%#v" }}{{ if .Params | body }}, data: {{ .Params | body | printf "%#v" }}{{ end }}},
|
||||
want: want{
|
||||
status: {{ with index .Responses 0 }}{{ .Code }},
|
||||
body: {{ if ne (len .Examples) 0 }}{{ with index .Examples 0 }}{{ .Example | printf "%#v" }}{{ end }}{{ else }}nil{{ end }}{{ end }},
|
||||
},
|
||||
}, {{ end }}
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx, _, _, _, _, db, _, server, cleanup, err := test.Server(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
if err := test.SetupTestData(ctx, db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
setUser := func(context *gin.Context) {
|
||||
busdb.SetContext(context, test.Bob)
|
||||
}
|
||||
server.ApiGroup.Use(setUser)
|
||||
|
||||
server.ConfigureRoutes()
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// setup request
|
||||
var req *http.Request
|
||||
if tt.args.data != nil {
|
||||
b, err := json.Marshal(tt.args.data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(tt.args.method, tt.args.url, bytes.NewBuffer(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req = httptest.NewRequest(tt.args.method, tt.args.url, nil)
|
||||
}
|
||||
|
||||
// run request
|
||||
server.ServeHTTP(w, req)
|
||||
|
||||
result := w.Result()
|
||||
|
||||
// assert results
|
||||
if result.StatusCode != tt.want.status {
|
||||
msg, _ := io.ReadAll(result.Body)
|
||||
|
||||
t.Fatalf("Status got = %v, want %v: %s", result.Status, tt.want.status, msg)
|
||||
}
|
||||
if tt.want.status != http.StatusNoContent {
|
||||
jsonEqual(t, result.Body, tt.want.body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func jsonEqual(t *testing.T, got io.Reader, want interface{}) {
|
||||
var gotObject, wantObject interface{}
|
||||
|
||||
// load bytes
|
||||
wantBytes, err := json.Marshal(want)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gotBytes, err := io.ReadAll(got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fields := []string{
|
||||
"created", "modified", "logs.0.created",
|
||||
"artifacts.0.enrichments.hash\\.sha1.created",
|
||||
"artifacts.1.enrichments.hash\\.sha1.created",
|
||||
"artifacts.2.enrichments.hash\\.sha1.created",
|
||||
|
||||
"playbooks.simple.tasks.input.created",
|
||||
"playbooks.simple.tasks.hash.created",
|
||||
"playbooks.simple.tasks.escalate.created",
|
||||
|
||||
"playbooks.phishing.tasks.input.created",
|
||||
"playbooks.phishing.tasks.hash.created",
|
||||
"playbooks.phishing.tasks.escalate.created",
|
||||
|
||||
"playbooks.phishing.tasks.block-ioc.created",
|
||||
"playbooks.phishing.tasks.block-iocs.created",
|
||||
"playbooks.phishing.tasks.block-sender.created",
|
||||
"playbooks.phishing.tasks.board.created",
|
||||
"playbooks.phishing.tasks.board.closed",
|
||||
"playbooks.phishing.tasks.escalate.created",
|
||||
"playbooks.phishing.tasks.extract-iocs.created",
|
||||
"playbooks.phishing.tasks.fetch-iocs.created",
|
||||
"playbooks.phishing.tasks.mail-available.created",
|
||||
"playbooks.phishing.tasks.search-email-gateway.created",
|
||||
|
||||
"0.playbooks.phishing.tasks.block-ioc.created",
|
||||
"0.playbooks.phishing.tasks.block-iocs.created",
|
||||
"0.playbooks.phishing.tasks.block-sender.created",
|
||||
"0.playbooks.phishing.tasks.board.created",
|
||||
"0.playbooks.phishing.tasks.escalate.created",
|
||||
"0.playbooks.phishing.tasks.extract-iocs.created",
|
||||
"0.playbooks.phishing.tasks.fetch-iocs.created",
|
||||
"0.playbooks.phishing.tasks.mail-available.created",
|
||||
"0.playbooks.phishing.tasks.search-email-gateway.created",
|
||||
|
||||
"tickets.0.playbooks.phishing.tasks.block-ioc.created",
|
||||
"tickets.0.playbooks.phishing.tasks.block-iocs.created",
|
||||
"tickets.0.playbooks.phishing.tasks.block-sender.created",
|
||||
"tickets.0.playbooks.phishing.tasks.board.created",
|
||||
"tickets.0.playbooks.phishing.tasks.escalate.created",
|
||||
"tickets.0.playbooks.phishing.tasks.extract-iocs.created",
|
||||
"tickets.0.playbooks.phishing.tasks.fetch-iocs.created",
|
||||
"tickets.0.playbooks.phishing.tasks.mail-available.created",
|
||||
"tickets.0.playbooks.phishing.tasks.search-email-gateway.created",
|
||||
|
||||
"secret", "0.created", "comments.0.created",
|
||||
}
|
||||
for _, field := range fields {
|
||||
gField := gjson.GetBytes(wantBytes, field)
|
||||
if gField.Exists() && gjson.GetBytes(gotBytes, field).Exists() {
|
||||
gotBytes, err = sjson.SetBytes(gotBytes, field, gField.Value())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// normalize bytes
|
||||
if err = json.Unmarshal(wantBytes, &wantObject); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := json.Unmarshal(gotBytes, &gotObject); err != nil {
|
||||
t.Fatal(string(gotBytes), err)
|
||||
}
|
||||
|
||||
// compare
|
||||
assert.Equal(t, wantObject, gotObject)
|
||||
}
|
||||
347
generator/templates/parameter.gotmpl
Normal file
347
generator/templates/parameter.gotmpl
Normal file
@@ -0,0 +1,347 @@
|
||||
{{ define "sliceparamvalidator"}}
|
||||
{{ if or .MinItems .MaxItems }}
|
||||
{{ camelize .Name }}Size := int64(len({{ if and (not .IsArray) (not .HasDiscriminator) (not .IsInterface) (not .IsStream) .IsNullable }}*{{ end }}{{ .ValueExpression }}))
|
||||
{{ end }}
|
||||
{{ if .MinItems }}
|
||||
if err := validate.MinItems({{ .Path }}, {{ printf "%q" .Location }}, {{ camelize .Name }}Size, {{ .MinItems }}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{ end }}
|
||||
{{ if .MaxItems }}
|
||||
if err := validate.MaxItems({{ .Path }}, {{ printf "%q" .Location }}, {{ camelize .Name }}Size, {{.MaxItems}}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{ end }}
|
||||
{{ if .UniqueItems }}
|
||||
if err := validate.UniqueItems({{ .Path }}, {{ printf "%q" .Location }}, {{ if and (not .IsArray) (not .HasDiscriminator) (not .IsInterface) (not .IsStream) .IsNullable }}*{{ end }}{{ .ValueExpression }}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{ end }}
|
||||
{{ if .Enum }}
|
||||
if err := validate.Enum({{ .Path }}, {{ printf "%q" .Location }}, {{ if and (not .IsArray) (not .HasDiscriminator) (not .IsInterface) (not .IsStream) .IsNullable }}*{{ end }}{{ .ValueExpression }}, {{ .Enum }}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ define "customValidationPrimitive" }}
|
||||
{{if .MinLength}}
|
||||
if err := validate.MinLength({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ if .IsNullable }}(*{{ end }}{{.ValueExpression}}{{ if .IsNullable }}){{ end }}{{ if .IsCustomFormatter }}.String(){{ end }}, {{.MinLength}}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
{{if .MaxLength}}
|
||||
if err := validate.MaxLength({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ if .IsNullable }}(*{{ end }}{{.ValueExpression}}{{ if .IsNullable }}){{ end }}{{ if .IsCustomFormatter }}.String(){{ end }}, {{.MaxLength}}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
{{if .Pattern}}
|
||||
if err := validate.Pattern({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ if .IsNullable }}(*{{ end }}{{.ValueExpression}}{{ if .IsNullable }}){{ end }}{{ if .IsCustomFormatter }}.String(){{ end }}, `{{.Pattern}}`); err != nil {
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
{{if .Minimum}}
|
||||
if err := validate.Minimum{{ if eq .SwaggerType "integer" }}Int{{ end }}({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ if eq .SwaggerType "integer" }}int{{ else }}float{{ end }}64({{ if .IsNullable }}*{{ end }}{{.ValueExpression}}), {{.Minimum}}, {{.ExclusiveMinimum}}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
{{if .Maximum}}
|
||||
if err := validate.Maximum{{ if eq .SwaggerType "integer" }}Int{{ end }}({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ if eq .SwaggerType "integer" }}int{{ else }}float{{ end }}64({{ if .IsNullable }}*{{ end }}{{.ValueExpression}}), {{.Maximum}}, {{.ExclusiveMaximum}}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
{{if .MultipleOf}}
|
||||
if err := validate.MultipleOf({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, float64({{ if .IsNullable }}*{{ end }}{{.ValueExpression}}), {{.MultipleOf}}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
{{if .Enum}}
|
||||
if err := validate.Enum({{ if .Path }}{{ .Path }}{{else}}""{{end}}, {{ printf "%q" .Location }}, {{ if and (not .IsArray) (not .HasDiscriminator) (not .IsInterface) .IsNullable }}*{{ end }}{{.ValueExpression}}{{ if .IsCustomFormatter }}.String(){{ end }}, {{ printf "%#v" .Enum}}); err != nil {
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ define "propertyparamvalidator" }}
|
||||
{{ if .IsPrimitive }}{{ template "customValidationPrimitive" . }}{{ end }}
|
||||
{{ if .IsCustomFormatter }}
|
||||
if err := validate.FormatOf({{.Path}}, "{{.Location}}", "{{.SwaggerFormat}}", {{.ValueExpression}}.String(), formats); err != nil {
|
||||
return err
|
||||
}{{ end }}
|
||||
{{ if .IsArray }}{{ template "sliceparamvalidator" . }}{{ end -}}
|
||||
{{ end }}
|
||||
{{define "bindprimitiveparam" }}
|
||||
{{ end }}
|
||||
{{ define "sliceparambinder" }}
|
||||
var {{ varname .Child.ValueExpression }}R {{ .GoType }}
|
||||
for {{ if or .Child.HasValidations .Child.Converter .Child.IsCustomFormatter }}{{ .IndexVar }}{{ else }}_{{ end }}, {{ varname .Child.ValueExpression }}V := range {{ varname .Child.ValueExpression }}C {
|
||||
{{ if or .Child.IsArray -}}
|
||||
{{ .Child.Child.ValueExpression }}C := swag.SplitByFormat({{ varname .Child.ValueExpression }}V, {{ printf "%q" .Child.CollectionFormat }})
|
||||
{{ template "sliceparambinder" .Child }}
|
||||
{{- else -}}
|
||||
{{ if .Child.Converter -}}
|
||||
{{ varname .Child.ValueExpression }}, err := {{ .Child.Converter }}({{ varname .Child.ValueExpression }}V)
|
||||
if err != nil {
|
||||
return errors.InvalidType({{ .Child.Path }}, {{ printf "%q" .Child.Location }}, "{{ .Child.GoType }}", {{ varname .Child.ValueExpression }})
|
||||
}
|
||||
{{- else if .Child.IsCustomFormatter -}}
|
||||
{{ varname .Child.ValueExpression }}, err := formats.Parse({{ varname .Child.ValueExpression }}V)
|
||||
if err != nil {
|
||||
return errors.InvalidType({{ .Child.Path }}, {{ printf "%q" .Child.Location }}, "{{ .Child.GoType }}", {{ varname .Child.ValueExpression }})
|
||||
}
|
||||
{{- else -}}
|
||||
{{ varname .Child.ValueExpression }} := {{ varname .Child.ValueExpression }}V
|
||||
{{ end }}
|
||||
{{- end }}
|
||||
|
||||
{{ template "propertyparamvalidator" .Child }}
|
||||
{{ varname .Child.ValueExpression }}R = append({{ varname .Child.ValueExpression }}R, {{ varname .Child.ValueExpression }})
|
||||
}
|
||||
{{ end }}
|
||||
package {{ .Package }}
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/validate"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/swag"
|
||||
|
||||
strfmt "github.com/go-openapi/strfmt"
|
||||
|
||||
{{ range .DefaultImports }}{{ printf "%q" .}}
|
||||
{{ end }}
|
||||
{{ range $key, $value := .Imports }}{{ $key }} {{ printf "%q" $value }}
|
||||
{{ end }}
|
||||
"github.com/SecurityBrewery/catalyst/generated/restapi/api"
|
||||
)
|
||||
|
||||
// {{ pascalize .Name }}Endpoint executes the core logic of the related
|
||||
// route endpoint.
|
||||
func {{ pascalize .Name }}Endpoint(handler func(ctx context.Context{{ if .Params }}, params *{{ pascalize .Name }}Params{{ end }}) *api.Response) gin.HandlerFunc {
|
||||
return func (ctx *gin.Context) {
|
||||
|
||||
{{ if .Params }}// generate params from request
|
||||
params := New{{ pascalize .Name }}Params()
|
||||
err := params.ReadRequest(ctx)
|
||||
if err != nil {
|
||||
errObj := err.(*errors.CompositeError)
|
||||
ctx.Writer.Header().Set("Content-Type", "application/problem+json")
|
||||
ctx.JSON(int(errObj.Code()), gin.H{"error": errObj.Error()})
|
||||
return
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
resp := handler(ctx{{ if .Params }}, params{{end}})
|
||||
|
||||
switch resp.Code {
|
||||
case http.StatusNoContent:
|
||||
ctx.AbortWithStatus(resp.Code)
|
||||
default:
|
||||
ctx.JSON(resp.Code, resp.Body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New{{ pascalize .Name }}Params creates a new {{ pascalize .Name }}Params object
|
||||
// with the default values initialized.
|
||||
func New{{ pascalize .Name }}Params() *{{ pascalize .Name }}Params {
|
||||
var (
|
||||
{{ range .Params }}{{ if .HasDefault }}{{ if not .IsFileParam }}{{ varname .ID}}Default = {{ if .IsPrimitive}}{{.GoType}}({{ end}}{{ printf "%#v" .Default }}{{ if .IsPrimitive }}){{ end }}
|
||||
{{ end }}{{ end }}{{end}}
|
||||
)
|
||||
return &{{ pascalize .Name }}Params{ {{ range .Params }}{{ if .HasDefault }}
|
||||
{{ pascalize .ID}}: {{ if and (not .IsArray) (not .HasDiscriminator) (not .IsInterface) (not .IsStream) .IsNullable }}&{{ end }}{{ varname .ID }}Default,
|
||||
{{ end }}{{ end }} }
|
||||
}
|
||||
|
||||
// {{ pascalize .Name }}Params contains all the bound params for the {{ humanize .Name }} operation
|
||||
// typically these are obtained from a http.Request
|
||||
//
|
||||
// swagger:parameters {{ .Name }}
|
||||
type {{ pascalize .Name }}Params struct {
|
||||
|
||||
{{ range .Params }}/*{{ if .Description }}{{ .Description }}{{ end }}{{ if .Required }}
|
||||
Required: true{{ end }}{{ if .Maximum }}
|
||||
Maximum: {{ if .ExclusiveMaximum }}< {{ end }}{{ .Maximum }}{{ end }}{{ if .Minimum }}
|
||||
Minimum: {{ if .ExclusiveMinimum }}> {{ end }}{{ .Minimum }}{{ end }}{{ if .MultipleOf }}
|
||||
Multiple Of: {{ .MultipleOf }}{{ end }}{{ if .MaxLength }}
|
||||
Max Length: {{ .MaxLength }}{{ end }}{{ if .MinLength }}
|
||||
Min Length: {{ .MinLength }}{{ end }}{{ if .Pattern }}
|
||||
Pattern: {{ .Pattern }}{{ end }}{{ if .MaxItems }}
|
||||
Max Items: {{ .MaxItems }}{{ end }}{{ if .MinItems }}
|
||||
Min Items: {{ .MinItems }}{{ end }}{{ if .UniqueItems }}
|
||||
Unique: true{{ end }}{{ if .Location }}
|
||||
In: {{ .Location }}{{ end }}{{ if .CollectionFormat }}
|
||||
Collection Format: {{ .CollectionFormat }}{{ end }}{{ if .HasDefault }}
|
||||
Default: {{ printf "%#v" .Default }}{{ end }}
|
||||
*/
|
||||
{{ if not .Schema }}{{ pascalize .ID }} {{ if and (not .IsArray) (not .HasDiscriminator) (not .IsInterface) (not .IsFileParam) (not .IsStream) .IsNullable }}*{{ end }}{{.GoType}}{{ else }}{{ pascalize .Name }} {{ if and (not .Schema.IsBaseType) .IsNullable (not .Schema.IsStream) }}*{{ end }}{{.GoType}}{{ end }}
|
||||
{{ end}}
|
||||
}
|
||||
|
||||
// ReadRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
|
||||
// for simple values it will use straight method calls
|
||||
func ({{ .ReceiverName }} *{{ pascalize .Name }}Params) ReadRequest(ctx *gin.Context) error {
|
||||
var res []error
|
||||
{{ if .HasQueryParams }}qs := runtime.Values(ctx.Request.URL.Query()){{ end }}
|
||||
|
||||
{{ if .HasFormParams }}if err := ctx.Request.ParseMultipartForm(32 << 20); err != nil {
|
||||
if err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err := ctx.Request.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
}{{ if .HasFormValueParams }}
|
||||
fds := runtime.Values(ctx.Request.Form)
|
||||
{{ end }}{{ end }}
|
||||
|
||||
{{ range .Params }}
|
||||
{{ if not .IsArray }}{{ if .IsQueryParam }}q{{ pascalize .Name }}, qhk{{ pascalize .Name }}, _ := qs.GetOK({{ .Path }})
|
||||
if err := {{ .ReceiverName }}.bind{{ pascalize .ID }}(q{{ pascalize .Name }}, qhk{{ pascalize .Name }}); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
{{ else if .IsPathParam }}r{{ pascalize .Name }} := []string{ctx.Param({{ .Path }})}
|
||||
if err := {{ .ReceiverName }}.bind{{ pascalize .ID }}(r{{ pascalize .Name }}, true); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
{{ else if .IsHeaderParam }}if err := {{ .ReceiverName }}.bind{{ pascalize .ID }}(ctx.Request.Header[http.CanonicalHeaderKey({{ .Path }})], true); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
{{ else if .IsFormParam }}{{if .IsFileParam }}{{ camelize .Name }}, {{ camelize .Name }}Header, err := ctx.Request.FormFile({{ .Path }})
|
||||
if err != nil {
|
||||
res = append(res, errors.New(400, "reading file %q failed: %v", {{ printf "%q" (camelize .Name) }}, err))
|
||||
} else {
|
||||
{{ .ReceiverName }}.{{ pascalize .Name }} = &runtime.File{Data: {{ camelize .Name }}, Header: {{ camelize .Name }}Header}
|
||||
}
|
||||
{{ else }}fd{{ pascalize .Name }}, fdhk{{ pascalize .Name }}, _ := fds.GetOK({{ .Path }})
|
||||
if err := {{ .ReceiverName }}.bind{{ pascalize .ID }}(fd{{ pascalize .Name }}, fdhk{{ pascalize .Name }}); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
{{ end }}{{ end }}
|
||||
{{ else if .IsArray }}{{ if .IsQueryParam }}q{{ pascalize .Name }}, qhk{{ pascalize .Name }}, _ := qs.GetOK({{ .Path }})
|
||||
if err := {{ .ReceiverName }}.bind{{ pascalize .ID }}(q{{ pascalize .Name }}, qhk{{ pascalize .Name }}); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
{{ else if and .IsFormParam }}fd{{ pascalize .Name }}, fdhk{{ pascalize .Name }}, _ := fds.GetOK({{ .Path }})
|
||||
if err := {{ .ReceiverName }}.bind{{ pascalize .ID }}(fd{{ pascalize .Name }}, fdhk{{ pascalize .Name }}); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
{{ end }}{{ end }}
|
||||
|
||||
{{ if and .IsBodyParam .Schema }}if runtime.HasBody(ctx.Request) {
|
||||
{{ if .Schema.IsStream }}{{ .ReceiverName }}.{{ pascalize .Name }} = ctx.Request.Body
|
||||
{{ else }}{{ if and .Schema.IsBaseType .Schema.IsExported }}body, err := {{ .ModelsPackage }}.Unmarshal{{ dropPackage .GoType }}{{ if .IsArray }}Slice{{ end }}(ctx.Request.Body, route.Consumer)
|
||||
if err != nil { {{ if .Required }}
|
||||
if err == io.EOF {
|
||||
err = errors.Required({{ .Path }}, {{ printf "%q" .Location }}, "")
|
||||
}
|
||||
{{ end }}res = append(res, err)
|
||||
{{ else }}var body {{ .GoType }}
|
||||
if err := ctx.BindJSON(&body); err != nil { {{ if .Required }}
|
||||
if err == io.EOF {
|
||||
res = append(res, errors.Required({{ printf "%q" (camelize .Name) }}, {{ printf "%q" .Location }}, ""))
|
||||
} else { {{ end }}
|
||||
res = append(res, errors.NewParseError({{ printf "%q" (camelize .Name) }}, {{ printf "%q" .Location }}, "", err)){{ if .Required }}
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}} else {
|
||||
{{ .ReceiverName }}.{{ pascalize .Name }} = {{ if and (not .Schema.IsBaseType) .IsNullable }}&{{ end }}body
|
||||
}{{ end }}
|
||||
}{{ if .Required }} else {
|
||||
res = append(res, errors.Required({{ printf "%q" (camelize .Name) }}, {{ printf "%q" .Location }}, ""))
|
||||
} {{ end }}
|
||||
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
{{ $className := (pascalize .Name) }}
|
||||
{{ range .Params }}
|
||||
{{ if not (or .IsBodyParam .IsFileParam) }}
|
||||
{{ if or .IsPrimitive .IsCustomFormatter }}
|
||||
func ({{ .ReceiverName }} *{{ $className }}Params) bind{{ pascalize .ID }}(rawData []string, hasKey bool) error {
|
||||
{{ if and (not .IsPathParam) .Required }}if !hasKey {
|
||||
return errors.Required({{ .Path }}, {{ printf "%q" .Location }}, rawData)
|
||||
}
|
||||
{{ end }}var raw string
|
||||
if len(rawData) > 0 {
|
||||
raw = rawData[len(rawData)-1]
|
||||
}
|
||||
{{ if and (not .IsPathParam) .Required (not .AllowEmptyValue) }}if err := validate.RequiredString({{ .Path }}, {{ printf "%q" .Location }}, raw); err != nil {
|
||||
return err
|
||||
}
|
||||
{{ else if and ( not .IsPathParam ) (or (not .Required) .AllowEmptyValue) }}if raw == "" { // empty values pass all other validations
|
||||
{{ if .HasDefault }}var {{ camelize .Name}}Default {{ if not .IsFileParam }}{{ .GoType }}{{ else }}os.File{{end}} = {{ if .IsPrimitive}}{{.GoType}}({{ end}}{{ printf "%#v" .Default }}{{ if .IsPrimitive }}){{ end }}
|
||||
{{ .ValueExpression }} = {{ if and (not .IsArray) (not .HasDiscriminator) (or .IsNullable ) (not .IsStream) }}&{{ end }}{{ camelize .Name }}Default
|
||||
{{ end }}return nil
|
||||
}
|
||||
{{ end }}
|
||||
{{ if .Converter }}value, err := {{ .Converter }}(raw)
|
||||
if err != nil {
|
||||
return errors.InvalidType({{ .Path }}, {{ printf "%q" .Location }}, {{ printf "%q" .GoType }}, raw)
|
||||
}
|
||||
{{ .ValueExpression }} = {{ if .IsNullable }}&{{ end }}value
|
||||
{{ else if .IsCustomFormatter }}value, err := formats.Parse({{ printf "%q" .SwaggerFormat }}, raw)
|
||||
if err != nil {
|
||||
return errors.InvalidType({{ .Path }}, {{ printf "%q" .Location }}, {{ printf "%q" .GoType }}, raw)
|
||||
}
|
||||
{{ .ValueExpression }} = {{ if and (not .IsArray) (not .HasDiscriminator) (not .IsFileParam) (not .IsStream) (not .IsNullable) }}*{{ end }}(value.(*{{ .GoType }}))
|
||||
{{else}}{{ .ValueExpression }} = {{ if .IsNullable }}&{{ end }}raw
|
||||
{{ end }}
|
||||
{{if .HasValidations }}if err := {{ .ReceiverName }}.validate{{ pascalize .ID }}(); err != nil {
|
||||
return err
|
||||
}
|
||||
{{ end }}
|
||||
return nil
|
||||
}
|
||||
{{else if .IsArray}}
|
||||
func ({{ .ReceiverName }} *{{ $className }}Params) bind{{ pascalize .ID }}(rawData []string, hasKey bool) error {
|
||||
{{if .Required }}if !hasKey {
|
||||
return errors.Required({{ .Path }}, {{ printf "%q" .Location }}, rawData)
|
||||
}
|
||||
{{ end }}
|
||||
{{ if eq .CollectionFormat "multi" }}{{ varname .Child.ValueExpression }}C := rawData{{ else }}var qv{{ pascalize .Name }} string
|
||||
if len(rawData) > 0 {
|
||||
qv{{ pascalize .Name }} = rawData[len(rawData) - 1]
|
||||
}
|
||||
|
||||
{{ varname .Child.ValueExpression }}C := swag.SplitByFormat(qv{{ pascalize .Name }}, {{ printf "%q" .CollectionFormat }}){{ end }}
|
||||
{{if and .Required (not .AllowEmptyValue) }}
|
||||
if len({{ varname .Child.ValueExpression }}C) == 0 {
|
||||
return errors.Required({{ .Path }}, {{ printf "%q" .Location }}, {{ varname .Child.ValueExpression }}C)
|
||||
}
|
||||
{{ end }}
|
||||
{{ if not .Required }}{{ if .HasDefault }}defValue := swag.SplitByFormat({{ .Default }}, {{ printf "%q" .CollectionFormat }})
|
||||
if len({{ varname .Child.ValueExpression }}C) == 0 && len(defValue) > 0 {
|
||||
{{ .ValueExpression }} = defValue
|
||||
{{ else }}if len({{ varname .Child.ValueExpression }}C) == 0 {
|
||||
return nil{{ end }}
|
||||
}{{ end }}
|
||||
{{ template "sliceparambinder" . }}
|
||||
{{ .ValueExpression }} = {{ varname .Child.ValueExpression }}R
|
||||
{{ if .HasSliceValidations }}if err := {{ .ReceiverName }}.validate{{ pascalize .ID }}(); err != nil {
|
||||
return err
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
return nil
|
||||
}
|
||||
{{ end }}
|
||||
{{ if or .HasValidations .HasSliceValidations }}
|
||||
func ({{ .ReceiverName }} *{{ $className }}Params) validate{{ pascalize .ID }}() error {
|
||||
{{ template "propertyparamvalidator" . }}
|
||||
return nil
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
6
generator/templates/response.gotmpl
Normal file
6
generator/templates/response.gotmpl
Normal file
@@ -0,0 +1,6 @@
|
||||
package api
|
||||
|
||||
type Response struct {
|
||||
Code int
|
||||
Body interface{}
|
||||
}
|
||||
64
generator/templates/simplemodel.gotmpl
Normal file
64
generator/templates/simplemodel.gotmpl
Normal file
@@ -0,0 +1,64 @@
|
||||
{{- /*gotype: github.com/SecurityBrewery/catalyst/generator.Swagger */ -}}
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xeipuuv/gojsonschema"
|
||||
)
|
||||
|
||||
var (
|
||||
schemaLoader = gojsonschema.NewSchemaLoader()
|
||||
{{ range $index, $element := .Definitions }}{{ $index }}Schema = new(gojsonschema.Schema)
|
||||
{{ end }})
|
||||
|
||||
func init() {
|
||||
err := schemaLoader.AddSchemas(
|
||||
{{ range $index, $element := .Definitions }}gojsonschema.NewStringLoader(`{{ tojson $index $element }}`),
|
||||
{{ end }}
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
{{ range $index, $element := .Definitions }}{{ $index }}Schema = mustCompile(`#/definitions/{{ $index }}`)
|
||||
{{ end }}}
|
||||
|
||||
{{ range $index, $element := .Definitions }}
|
||||
type {{ $index }} struct {
|
||||
{{ range $pindex, $pelement := .Properties }} {{ camel $pindex }} {{ gotype $pindex $pelement $element.Required }} `json:"{{ $pindex }}{{ if omitempty $pindex $element.Required }},omitempty{{ end }}"`
|
||||
{{ end }}}
|
||||
|
||||
{{ end }}
|
||||
|
||||
func mustCompile(uri string) *gojsonschema.Schema {
|
||||
s, err := schemaLoader.Compile(gojsonschema.NewReferenceLoader(uri))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func validate(s *gojsonschema.Schema, b []byte) error {
|
||||
res, err := s.Validate(gojsonschema.NewStringLoader(string(b)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res.Errors()) > 0 {
|
||||
var l []string
|
||||
for _, e := range res.Errors() {
|
||||
l = append(l, e.String())
|
||||
}
|
||||
return fmt.Errorf("validation failed: %v", strings.Join(l, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
{{ range $index, $element := .Definitions }}{{ range $pindex, $pelement := .Properties }}{{ range $eindex, $eelement := .Enum }}
|
||||
{{ $index | camel }}{{ $pindex | camel }}{{ $eelement | camel }} = "{{ $eelement }}"
|
||||
{{ end }}{{ end }}{{ end }}
|
||||
)
|
||||
Reference in New Issue
Block a user