mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-06-22 16:58:14 +02:00
Release catalyst
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 }}
|
||||
@@ -0,0 +1,6 @@
|
||||
package api
|
||||
|
||||
type Response struct {
|
||||
Code int
|
||||
Body interface{}
|
||||
}
|
||||
@@ -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