From aae95798b29ba3a004e35f938449c84557ec65de Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 24 Jul 2023 13:42:50 +1000 Subject: [PATCH] Refactor some reflection --- backend/internal/api/middleware/list_query.go | 3 +- backend/internal/entity/accesslist/model.go | 4 +- backend/internal/entity/auth/model.go | 4 +- backend/internal/entity/certificate/model.go | 4 +- .../entity/certificateauthority/model.go | 4 +- backend/internal/entity/dnsprovider/model.go | 4 +- backend/internal/entity/filters.go | 2 +- backend/internal/entity/filters_schema.go | 252 ----------------- backend/internal/entity/host/model.go | 4 +- .../internal/entity/nginxtemplate/model.go | 4 +- backend/internal/entity/setting/model.go | 4 +- backend/internal/entity/stream/model.go | 4 +- backend/internal/entity/upstream/model.go | 4 +- .../internal/entity/upstreamserver/model.go | 4 +- backend/internal/entity/user/model.go | 3 +- backend/internal/jwt/keys_db.go | 4 +- backend/internal/model/filter.go | 6 +- .../internal/{entity => model}/model_base.go | 2 +- backend/internal/nginx/template_test.go | 6 +- backend/internal/tags/filters.go | 265 +++++++++++++++++- 20 files changed, 287 insertions(+), 300 deletions(-) delete mode 100644 backend/internal/entity/filters_schema.go rename backend/internal/{entity => model}/model_base.go (97%) diff --git a/backend/internal/api/middleware/list_query.go b/backend/internal/api/middleware/list_query.go index fd8506f9..1b7dda8a 100644 --- a/backend/internal/api/middleware/list_query.go +++ b/backend/internal/api/middleware/list_query.go @@ -9,7 +9,6 @@ import ( c "npm/internal/api/context" h "npm/internal/api/http" - "npm/internal/entity" "npm/internal/model" "npm/internal/tags" "npm/internal/util" @@ -23,7 +22,7 @@ import ( // After we have determined what the Filters are to be, they are saved on the Context // to be used later in other endpoints. func ListQuery(obj interface{}) func(http.Handler) http.Handler { - schemaData := entity.GetFilterSchema(obj, true) + schemaData := tags.GetFilterSchema(obj) filterMap := tags.GetFilterMap(obj) return func(next http.Handler) http.Handler { diff --git a/backend/internal/entity/accesslist/model.go b/backend/internal/entity/accesslist/model.go index 49bd6aa9..5b55b36b 100644 --- a/backend/internal/entity/accesslist/model.go +++ b/backend/internal/entity/accesslist/model.go @@ -2,8 +2,8 @@ package accesslist import ( "npm/internal/database" - "npm/internal/entity" "npm/internal/entity/user" + "npm/internal/model" "npm/internal/types" "github.com/rotisserie/eris" @@ -11,7 +11,7 @@ import ( // Model is the model type Model struct { - entity.ModelBase + model.ModelBase UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"` Meta types.JSONB `json:"meta" gorm:"column:meta"` diff --git a/backend/internal/entity/auth/model.go b/backend/internal/entity/auth/model.go index 17ca4195..9a92bb70 100644 --- a/backend/internal/entity/auth/model.go +++ b/backend/internal/entity/auth/model.go @@ -2,7 +2,7 @@ package auth import ( "npm/internal/database" - "npm/internal/entity" + "npm/internal/model" "github.com/rotisserie/eris" "golang.org/x/crypto/bcrypt" @@ -15,7 +15,7 @@ const ( // Model is the model type Model struct { - entity.ModelBase + model.ModelBase UserID uint `json:"user_id" gorm:"column:user_id"` Type string `json:"type" gorm:"column:type;default:password"` Secret string `json:"secret,omitempty" gorm:"column:secret"` diff --git a/backend/internal/entity/certificate/model.go b/backend/internal/entity/certificate/model.go index 329c7a48..48f682a6 100644 --- a/backend/internal/entity/certificate/model.go +++ b/backend/internal/entity/certificate/model.go @@ -10,11 +10,11 @@ import ( "npm/internal/acme" "npm/internal/config" "npm/internal/database" - "npm/internal/entity" "npm/internal/entity/certificateauthority" "npm/internal/entity/dnsprovider" "npm/internal/entity/user" "npm/internal/logger" + "npm/internal/model" "npm/internal/serverevents" "npm/internal/types" "npm/internal/util" @@ -44,7 +44,7 @@ const ( // Model is the model type Model struct { - entity.ModelBase + model.ModelBase UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` Type string `json:"type" gorm:"column:type" filter:"type,string"` CertificateAuthorityID types.NullableDBUint `json:"certificate_authority_id" gorm:"column:certificate_authority_id" filter:"certificate_authority_id,integer"` diff --git a/backend/internal/entity/certificateauthority/model.go b/backend/internal/entity/certificateauthority/model.go index ecb40beb..5f1b5a01 100644 --- a/backend/internal/entity/certificateauthority/model.go +++ b/backend/internal/entity/certificateauthority/model.go @@ -5,15 +5,15 @@ import ( "path/filepath" "npm/internal/database" - "npm/internal/entity" "npm/internal/errors" + "npm/internal/model" "github.com/rotisserie/eris" ) // Model is the model type Model struct { - entity.ModelBase + model.ModelBase Name string `json:"name" gorm:"column:name" filter:"name,string"` AcmeshServer string `json:"acmesh_server" gorm:"column:acmesh_server" filter:"acmesh_server,string"` CABundle string `json:"ca_bundle" gorm:"column:ca_bundle" filter:"ca_bundle,string"` diff --git a/backend/internal/entity/dnsprovider/model.go b/backend/internal/entity/dnsprovider/model.go index 86bcb5a1..d5ddfa48 100644 --- a/backend/internal/entity/dnsprovider/model.go +++ b/backend/internal/entity/dnsprovider/model.go @@ -5,8 +5,8 @@ import ( "npm/internal/database" "npm/internal/dnsproviders" - "npm/internal/entity" "npm/internal/logger" + "npm/internal/model" "npm/internal/types" "github.com/rotisserie/eris" @@ -14,7 +14,7 @@ import ( // Model is the model type Model struct { - entity.ModelBase + model.ModelBase UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"` AcmeshName string `json:"acmesh_name" gorm:"column:acmesh_name" filter:"acmesh_name,string"` diff --git a/backend/internal/entity/filters.go b/backend/internal/entity/filters.go index 8613f48a..6e0c04f9 100644 --- a/backend/internal/entity/filters.go +++ b/backend/internal/entity/filters.go @@ -9,7 +9,7 @@ import ( func GetFilterMap(m interface{}, includeBaseEntity bool) map[string]model.FilterMapValue { filterMap := tags.GetFilterMap(m) if includeBaseEntity { - return mergeFilterMaps(tags.GetFilterMap(ModelBase{}), filterMap) + return mergeFilterMaps(tags.GetFilterMap(model.ModelBase{}), filterMap) } return filterMap diff --git a/backend/internal/entity/filters_schema.go b/backend/internal/entity/filters_schema.go deleted file mode 100644 index 6c4fa9b6..00000000 --- a/backend/internal/entity/filters_schema.go +++ /dev/null @@ -1,252 +0,0 @@ -package entity - -import ( - "fmt" - "reflect" - "strings" - - "npm/internal/logger" - "npm/internal/util" - - "github.com/rotisserie/eris" -) - -// GetFilterSchema creates a jsonschema for validating filters, based on the model -// object given and by reading the struct "filter" tags. -func GetFilterSchema(m interface{}, includeBaseEntity bool) string { - var schemas []string - t := reflect.TypeOf(m) - - if t.Kind() != reflect.Struct { - logger.Error("GetFilterSchemaError", eris.Errorf("%v type can't have attributes inspected", t.Kind())) - return "" - } - - // The base entity model - if includeBaseEntity { - b := reflect.TypeOf(ModelBase{}) - for i := 0; i < b.NumField(); i++ { - bField := b.Field(i) - bFilterTag := bField.Tag.Get("filter") - if bFilterTag != "" && bFilterTag != "-" { - schemas = append(schemas, getFilterTagSchema(bFilterTag)) - } - } - } - - // The actual interface - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - filterTag := field.Tag.Get("filter") - - if filterTag != "" && filterTag != "-" { - schemas = append(schemas, getFilterTagSchema(filterTag)) - } - } - - return util.PrettyPrintJSON(newFilterSchema(schemas)) -} - -func getFilterTagSchema(filterTag string) string { - // split out tag value "field,filtreType" - // with a default filter type of string - items := strings.Split(filterTag, ",") - if len(items) == 1 { - items = append(items, "string") - } - - switch items[1] { - case "number": - fallthrough - case "int": - fallthrough - case "integer": - return intFieldSchema(items[0]) - case "bool": - fallthrough - case "boolean": - return boolFieldSchema(items[0]) - case "date": - return dateFieldSchema(items[0]) - case "regex": - if len(items) < 3 { - items = append(items, ".*") - } - return regexFieldSchema(items[0], items[2]) - - default: - return stringFieldSchema(items[0]) - } -} - -// newFilterSchema is the main method to specify a new Filter Schema for use in Middleware -func newFilterSchema(fieldSchemas []string) string { - return fmt.Sprintf(baseFilterSchema, strings.Join(fieldSchemas, ", ")) -} - -// boolFieldSchema returns the Field Schema for a Boolean accepted value field -func boolFieldSchema(fieldName string) string { - return fmt.Sprintf(`{ - "type": "object", - "properties": { - "field": { - "type": "string", - "pattern": "^%s$" - }, - "modifier": %s, - "value": { - "oneOf": [ - %s, - { - "type": "array", - "items": %s - } - ] - } - } - }`, fieldName, boolModifiers, filterBool, filterBool) -} - -// intFieldSchema returns the Field Schema for a Integer accepted value field -func intFieldSchema(fieldName string) string { - return fmt.Sprintf(`{ - "type": "object", - "properties": { - "field": { - "type": "string", - "pattern": "^%s$" - }, - "modifier": %s, - "value": { - "oneOf": [ - { - "type": "string", - "pattern": "^[0-9]+$" - }, - { - "type": "array", - "items": { - "type": "string", - "pattern": "^[0-9]+$" - } - } - ] - } - } - }`, fieldName, allModifiers) -} - -// stringFieldSchema returns the Field Schema for a String accepted value field -func stringFieldSchema(fieldName string) string { - return fmt.Sprintf(`{ - "type": "object", - "properties": { - "field": { - "type": "string", - "pattern": "^%s$" - }, - "modifier": %s, - "value": { - "oneOf": [ - %s, - { - "type": "array", - "items": %s - } - ] - } - } - }`, fieldName, stringModifiers, filterString, filterString) -} - -// regexFieldSchema returns the Field Schema for a String accepted value field matching a Regex -func regexFieldSchema(fieldName string, regex string) string { - return fmt.Sprintf(`{ - "type": "object", - "properties": { - "field": { - "type": "string", - "pattern": "^%s$" - }, - "modifier": %s, - "value": { - "oneOf": [ - { - "type": "string", - "pattern": "%s" - }, - { - "type": "array", - "items": { - "type": "string", - "pattern": "%s" - } - } - ] - } - } - }`, fieldName, stringModifiers, regex, regex) -} - -// dateFieldSchema returns the Field Schema for a String accepted value field matching a Date format -func dateFieldSchema(fieldName string) string { - return fmt.Sprintf(`{ - "type": "object", - "properties": { - "field": { - "type": "string", - "pattern": "^%s$" - }, - "modifier": %s, - "value": { - "oneOf": [ - { - "type": "string", - "pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$" - }, - { - "type": "array", - "items": { - "type": "string", - "pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$" - } - } - ] - } - } - }`, fieldName, allModifiers) -} - -const allModifiers = `{ - "type": "string", - "pattern": "^(equals|not|contains|starts|ends|in|notin|min|max|greater|less)$" -}` - -const boolModifiers = `{ - "type": "string", - "pattern": "^(equals|not)$" -}` - -const stringModifiers = `{ - "type": "string", - "pattern": "^(equals|not|contains|starts|ends|in|notin)$" -}` - -const filterBool = `{ - "type": "string", - "pattern": "^(TRUE|true|t|yes|y|on|1|FALSE|f|false|n|no|off|0)$" -}` - -const filterString = `{ - "type": "string", - "minLength": 1 -}` - -const baseFilterSchema = `{ - "type": "array", - "items": { - "oneOf": [ - %s - ] - } -}` diff --git a/backend/internal/entity/host/model.go b/backend/internal/entity/host/model.go index 7457b487..64d0d11b 100644 --- a/backend/internal/entity/host/model.go +++ b/backend/internal/entity/host/model.go @@ -3,11 +3,11 @@ package host import ( "fmt" "npm/internal/database" - "npm/internal/entity" "npm/internal/entity/certificate" "npm/internal/entity/nginxtemplate" "npm/internal/entity/upstream" "npm/internal/entity/user" + "npm/internal/model" "npm/internal/status" "npm/internal/types" "npm/internal/util" @@ -26,7 +26,7 @@ const ( // Model is the model type Model struct { - entity.ModelBase + model.ModelBase UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` Type string `json:"type" gorm:"column:type" filter:"type,string"` NginxTemplateID uint `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"` diff --git a/backend/internal/entity/nginxtemplate/model.go b/backend/internal/entity/nginxtemplate/model.go index ec2359cb..68a215f8 100644 --- a/backend/internal/entity/nginxtemplate/model.go +++ b/backend/internal/entity/nginxtemplate/model.go @@ -2,14 +2,14 @@ package nginxtemplate import ( "npm/internal/database" - "npm/internal/entity" + "npm/internal/model" "github.com/rotisserie/eris" ) // Model is the model type Model struct { - entity.ModelBase + model.ModelBase UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"` Type string `json:"type" gorm:"column:type" filter:"type,string"` diff --git a/backend/internal/entity/setting/model.go b/backend/internal/entity/setting/model.go index d2e71f1e..9f06f6b4 100644 --- a/backend/internal/entity/setting/model.go +++ b/backend/internal/entity/setting/model.go @@ -4,14 +4,14 @@ import ( "strings" "npm/internal/database" - "npm/internal/entity" + "npm/internal/model" "gorm.io/datatypes" ) // Model is the model type Model struct { - entity.ModelBase + model.ModelBase Name string `json:"name" gorm:"column:name" filter:"name,string"` Description string `json:"description" gorm:"column:description" filter:"description,string"` Value datatypes.JSON `json:"value" gorm:"column:value"` diff --git a/backend/internal/entity/stream/model.go b/backend/internal/entity/stream/model.go index 6bd6771e..518ec3e3 100644 --- a/backend/internal/entity/stream/model.go +++ b/backend/internal/entity/stream/model.go @@ -2,7 +2,7 @@ package stream import ( "npm/internal/database" - "npm/internal/entity" + "npm/internal/model" "npm/internal/types" "github.com/rotisserie/eris" @@ -10,7 +10,7 @@ import ( // Model is the model type Model struct { - entity.ModelBase + model.ModelBase ExpiresOn types.DBDate `json:"expires_on" gorm:"column:expires_on" filter:"expires_on,integer"` UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` Provider string `json:"provider" gorm:"column:provider" filter:"provider,string"` diff --git a/backend/internal/entity/upstream/model.go b/backend/internal/entity/upstream/model.go index 71117708..b9d21a27 100644 --- a/backend/internal/entity/upstream/model.go +++ b/backend/internal/entity/upstream/model.go @@ -4,10 +4,10 @@ import ( "strings" "npm/internal/database" - "npm/internal/entity" "npm/internal/entity/nginxtemplate" "npm/internal/entity/upstreamserver" "npm/internal/entity/user" + "npm/internal/model" "npm/internal/status" "npm/internal/util" @@ -17,7 +17,7 @@ import ( // Model is the model // See: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream type Model struct { - entity.ModelBase + model.ModelBase UserID uint `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` Name string `json:"name" gorm:"column:name" filter:"name,string"` NginxTemplateID uint `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"` diff --git a/backend/internal/entity/upstreamserver/model.go b/backend/internal/entity/upstreamserver/model.go index 9adc7cfa..0fe177da 100644 --- a/backend/internal/entity/upstreamserver/model.go +++ b/backend/internal/entity/upstreamserver/model.go @@ -2,12 +2,12 @@ package upstreamserver import ( "npm/internal/database" - "npm/internal/entity" + "npm/internal/model" ) // Model is the model type Model struct { - entity.ModelBase + model.ModelBase UpstreamID uint `json:"upstream_id" gorm:"column:upstream_id" filter:"upstream_id,integer"` Server string `json:"server" gorm:"column:server" filter:"server,string"` Weight int `json:"weight" gorm:"column:weight" filter:"weight,integer"` diff --git a/backend/internal/entity/user/model.go b/backend/internal/entity/user/model.go index 1d83497c..8c19b438 100644 --- a/backend/internal/entity/user/model.go +++ b/backend/internal/entity/user/model.go @@ -7,6 +7,7 @@ import ( "npm/internal/entity" "npm/internal/entity/auth" "npm/internal/errors" + "npm/internal/model" "npm/internal/util" "github.com/drexedam/gravatar" @@ -15,7 +16,7 @@ import ( // Model is the model type Model struct { - entity.ModelBase + model.ModelBase Name string `json:"name" gorm:"column:name" filter:"name,string"` Nickname string `json:"nickname" gorm:"column:nickname" filter:"nickname,string"` Email string `json:"email" gorm:"column:email" filter:"email,email"` diff --git a/backend/internal/jwt/keys_db.go b/backend/internal/jwt/keys_db.go index 501f70cf..7ab2647c 100644 --- a/backend/internal/jwt/keys_db.go +++ b/backend/internal/jwt/keys_db.go @@ -2,14 +2,14 @@ package jwt import ( "npm/internal/database" - "npm/internal/entity" + "npm/internal/model" ) var currentKeys KeysModel // KeysModel is the model type KeysModel struct { - entity.ModelBase + model.ModelBase PublicKey string `gorm:"column:public_key"` PrivateKey string `gorm:"column:private_key"` } diff --git a/backend/internal/model/filter.go b/backend/internal/model/filter.go index fae252a8..056e6ef6 100644 --- a/backend/internal/model/filter.go +++ b/backend/internal/model/filter.go @@ -9,6 +9,8 @@ type Filter struct { // FilterMapValue ... type FilterMapValue struct { - Type string - Field string + Type string + Field string + Schema string + Model string } diff --git a/backend/internal/entity/model_base.go b/backend/internal/model/model_base.go similarity index 97% rename from backend/internal/entity/model_base.go rename to backend/internal/model/model_base.go index 0050ab79..f59d5cda 100644 --- a/backend/internal/entity/model_base.go +++ b/backend/internal/model/model_base.go @@ -1,4 +1,4 @@ -package entity +package model import ( "gorm.io/plugin/soft_delete" diff --git a/backend/internal/nginx/template_test.go b/backend/internal/nginx/template_test.go index 789d9ad7..ad2108a0 100644 --- a/backend/internal/nginx/template_test.go +++ b/backend/internal/nginx/template_test.go @@ -3,9 +3,9 @@ package nginx import ( "testing" - "npm/internal/entity" "npm/internal/entity/certificate" "npm/internal/entity/host" + "npm/internal/model" "npm/internal/types" "github.com/stretchr/testify/assert" @@ -47,7 +47,7 @@ server { IsDisabled: false, }, cert: certificate.Model{ - ModelBase: entity.ModelBase{ + ModelBase: model.ModelBase{ ID: 77, }, Status: certificate.StatusProvided, @@ -65,7 +65,7 @@ server { IsDisabled: false, }, cert: certificate.Model{ - ModelBase: entity.ModelBase{ + ModelBase: model.ModelBase{ ID: 66, }, Status: certificate.StatusProvided, diff --git a/backend/internal/tags/filters.go b/backend/internal/tags/filters.go index bddb80b0..25613165 100644 --- a/backend/internal/tags/filters.go +++ b/backend/internal/tags/filters.go @@ -1,11 +1,16 @@ package tags import ( + "fmt" "reflect" "regexp" "strings" + "npm/internal/logger" "npm/internal/model" + "npm/internal/util" + + "github.com/rotisserie/eris" ) func GetFilterMap(m interface{}) map[string]model.FilterMapValue { @@ -16,10 +21,21 @@ func GetFilterMap(m interface{}) map[string]model.FilterMapValue { var filterMap = make(map[string]model.FilterMapValue) + // If this is an entity model (and it probably is) + // then include the base model as well + if strings.Contains(name, ".Model") && !strings.Contains(name, "ModelBase") { + filterMap = GetFilterMap(model.ModelBase{}) + } + // TypeOf returns the reflection Type that represents the dynamic type of variable. // If variable is a nil interface value, TypeOf returns nil. t := reflect.TypeOf(m) + if t.Kind() != reflect.Struct { + logger.Error("GetFilterMapError", eris.Errorf("%v type can't have attributes inspected", t.Kind())) + return nil + } + // Iterate over all available fields and read the tag value for i := 0; i < t.NumField(); i++ { // Get the field, returns https://golang.org/pkg/reflect/#StructField @@ -28,26 +44,247 @@ func GetFilterMap(m interface{}) map[string]model.FilterMapValue { // Get the field tag value filterTag := field.Tag.Get("filter") dbTag := field.Tag.Get("gorm") - if filterTag != "" && dbTag != "" && dbTag != "-" && filterTag != "-" { - // db can have many parts, we need to pull out the "column:value" part - dbField := field.Name - r := regexp.MustCompile(`(?:^|;)column:([^;|$]+)(?:$|;)`) - if matches := r.FindStringSubmatch(dbTag); len(matches) > 1 { - dbField = matches[1] - } - // Filter tag can be a 2 part thing: name,type - // ie: account_id,integer - // So we need to split and use the first part + f := model.FilterMapValue{ + Model: name, + } + + // Filter -> Schema mapping + if filterTag != "" && filterTag != "-" { + f.Schema = getFilterTagSchema(filterTag) parts := strings.Split(filterTag, ",") - if len(parts) > 1 { - filterMap[parts[0]] = model.FilterMapValue{ - Type: parts[1], - Field: dbField, + + // Filter -> DB Field mapping + if dbTag != "" && dbTag != "-" { + // db can have many parts, we need to pull out the "column:value" part + f.Field = field.Name + r := regexp.MustCompile(`(?:^|;)column:([^;|$]+)(?:$|;)`) + if matches := r.FindStringSubmatch(dbTag); len(matches) > 1 { + f.Field = matches[1] + } + // Filter tag can be a 2 part thing: name,type + // ie: account_id,integer + // So we need to split and use the first part + if len(parts) > 1 { + f.Type = parts[1] } } + filterMap[parts[0]] = f } } setCache(name, filterMap) return filterMap } + +func getFilterTagSchema(filterTag string) string { + // split out tag value "field,filtreType" + // with a default filter type of string + items := strings.Split(filterTag, ",") + if len(items) == 1 { + items = append(items, "string") + } + + switch items[1] { + case "number": + fallthrough + case "int": + fallthrough + case "integer": + return intFieldSchema(items[0]) + case "bool": + fallthrough + case "boolean": + return boolFieldSchema(items[0]) + case "date": + return dateFieldSchema(items[0]) + case "regex": + if len(items) < 3 { + items = append(items, ".*") + } + return regexFieldSchema(items[0], items[2]) + + default: + return stringFieldSchema(items[0]) + } +} + +// GetFilterSchema creates a jsonschema for validating filters, based on the model +// object given and by reading the struct "filter" tags. +func GetFilterSchema(m interface{}) string { + filterMap := GetFilterMap(m) + schemas := make([]string, 0) + + for _, f := range filterMap { + schemas = append(schemas, f.Schema) + } + + str := fmt.Sprintf(baseFilterSchema, strings.Join(schemas, ", ")) + return util.PrettyPrintJSON(str) +} + +// boolFieldSchema returns the Field Schema for a Boolean accepted value field +func boolFieldSchema(fieldName string) string { + return fmt.Sprintf(`{ + "type": "object", + "properties": { + "field": { + "type": "string", + "pattern": "^%s$" + }, + "modifier": %s, + "value": { + "oneOf": [ + %s, + { + "type": "array", + "items": %s + } + ] + } + } + }`, fieldName, boolModifiers, filterBool, filterBool) +} + +// intFieldSchema returns the Field Schema for a Integer accepted value field +func intFieldSchema(fieldName string) string { + return fmt.Sprintf(`{ + "type": "object", + "properties": { + "field": { + "type": "string", + "pattern": "^%s$" + }, + "modifier": %s, + "value": { + "oneOf": [ + { + "type": "string", + "pattern": "^[0-9]+$" + }, + { + "type": "array", + "items": { + "type": "string", + "pattern": "^[0-9]+$" + } + } + ] + } + } + }`, fieldName, allModifiers) +} + +// stringFieldSchema returns the Field Schema for a String accepted value field +func stringFieldSchema(fieldName string) string { + return fmt.Sprintf(`{ + "type": "object", + "properties": { + "field": { + "type": "string", + "pattern": "^%s$" + }, + "modifier": %s, + "value": { + "oneOf": [ + %s, + { + "type": "array", + "items": %s + } + ] + } + } + }`, fieldName, stringModifiers, filterString, filterString) +} + +// regexFieldSchema returns the Field Schema for a String accepted value field matching a Regex +func regexFieldSchema(fieldName string, regex string) string { + return fmt.Sprintf(`{ + "type": "object", + "properties": { + "field": { + "type": "string", + "pattern": "^%s$" + }, + "modifier": %s, + "value": { + "oneOf": [ + { + "type": "string", + "pattern": "%s" + }, + { + "type": "array", + "items": { + "type": "string", + "pattern": "%s" + } + } + ] + } + } + }`, fieldName, stringModifiers, regex, regex) +} + +// dateFieldSchema returns the Field Schema for a String accepted value field matching a Date format +func dateFieldSchema(fieldName string) string { + return fmt.Sprintf(`{ + "type": "object", + "properties": { + "field": { + "type": "string", + "pattern": "^%s$" + }, + "modifier": %s, + "value": { + "oneOf": [ + { + "type": "string", + "pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$" + }, + { + "type": "array", + "items": { + "type": "string", + "pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$" + } + } + ] + } + } + }`, fieldName, allModifiers) +} + +const allModifiers = `{ + "type": "string", + "pattern": "^(equals|not|contains|starts|ends|in|notin|min|max|greater|less)$" +}` + +const boolModifiers = `{ + "type": "string", + "pattern": "^(equals|not)$" +}` + +const stringModifiers = `{ + "type": "string", + "pattern": "^(equals|not|contains|starts|ends|in|notin)$" +}` + +const filterBool = `{ + "type": "string", + "pattern": "^(TRUE|true|t|yes|y|on|1|FALSE|f|false|n|no|off|0)$" +}` + +const filterString = `{ + "type": "string", + "minLength": 1 +}` + +const baseFilterSchema = `{ + "type": "array", + "items": { + "oneOf": [ + %s + ] + } +}`