mirror of
https://github.com/rancher/norman.git
synced 2025-09-04 16:50:41 +00:00
Enable dynamic schemas
This commit is contained in:
@@ -26,7 +26,7 @@ type Server struct {
|
|||||||
Resolver parse.ResolverFunc
|
Resolver parse.ResolverFunc
|
||||||
SubContextAttributeProvider types.SubContextAttributeProvider
|
SubContextAttributeProvider types.SubContextAttributeProvider
|
||||||
ResponseWriters map[string]ResponseWriter
|
ResponseWriters map[string]ResponseWriter
|
||||||
schemas *types.Schemas
|
Schemas *types.Schemas
|
||||||
QueryFilter types.QueryFilter
|
QueryFilter types.QueryFilter
|
||||||
StoreWrapper StoreWrapper
|
StoreWrapper StoreWrapper
|
||||||
URLParser parse.URLParser
|
URLParser parse.URLParser
|
||||||
@@ -46,7 +46,7 @@ type Defaults struct {
|
|||||||
|
|
||||||
func NewAPIServer() *Server {
|
func NewAPIServer() *Server {
|
||||||
s := &Server{
|
s := &Server{
|
||||||
schemas: types.NewSchemas(),
|
Schemas: types.NewSchemas(),
|
||||||
ResponseWriters: map[string]ResponseWriter{
|
ResponseWriters: map[string]ResponseWriter{
|
||||||
"json": &writer.JSONResponseWriter{},
|
"json": &writer.JSONResponseWriter{},
|
||||||
"html": &writer.HTMLResponseWriter{},
|
"html": &writer.HTMLResponseWriter{},
|
||||||
@@ -68,12 +68,13 @@ func NewAPIServer() *Server {
|
|||||||
QueryFilter: handler.QueryFilter,
|
QueryFilter: handler.QueryFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.Schemas.AddHook = s.setupDefaults
|
||||||
s.Parser = s.parser
|
s.Parser = s.parser
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) parser(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error) {
|
func (s *Server) parser(rw http.ResponseWriter, req *http.Request) (*types.APIContext, error) {
|
||||||
ctx, err := parse.Parse(rw, req, s.schemas, s.URLParser, s.Resolver)
|
ctx, err := parse.Parse(rw, req, s.Schemas, s.URLParser, s.Resolver)
|
||||||
ctx.ResponseWriter = s.ResponseWriters[ctx.ResponseFormat]
|
ctx.ResponseWriter = s.ResponseWriters[ctx.ResponseFormat]
|
||||||
if ctx.ResponseWriter == nil {
|
if ctx.ResponseWriter == nil {
|
||||||
ctx.ResponseWriter = s.ResponseWriters["json"]
|
ctx.ResponseWriter = s.ResponseWriters["json"]
|
||||||
@@ -102,20 +103,15 @@ func (s *Server) AddSchemas(schemas *types.Schemas) error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, schema := range builtin.Schemas.Schemas() {
|
for _, schema := range builtin.Schemas.Schemas() {
|
||||||
s.addSchema(*schema)
|
s.Schemas.AddSchema(*schema)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, schema := range schemas.Schemas() {
|
for _, schema := range schemas.Schemas() {
|
||||||
s.addSchema(*schema)
|
s.Schemas.AddSchema(*schema)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.schemas.Err()
|
return s.Schemas.Err()
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) addSchema(schema types.Schema) {
|
|
||||||
s.setupDefaults(&schema)
|
|
||||||
s.schemas.AddSchema(schema)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setupDefaults(schema *types.Schema) {
|
func (s *Server) setupDefaults(schema *types.Schema) {
|
||||||
|
206
types/schemas.go
206
types/schemas.go
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/rancher/norman/name"
|
"github.com/rancher/norman/name"
|
||||||
"github.com/rancher/norman/types/convert"
|
"github.com/rancher/norman/types/convert"
|
||||||
"github.com/rancher/norman/types/definition"
|
"github.com/rancher/norman/types/definition"
|
||||||
@@ -14,7 +16,9 @@ type SchemaCollection struct {
|
|||||||
Data []Schema
|
Data []Schema
|
||||||
}
|
}
|
||||||
|
|
||||||
type SchemaInitFunc func(*Schemas) *Schemas
|
type SchemasInitFunc func(*Schemas) *Schemas
|
||||||
|
|
||||||
|
type SchemaHook func(*Schema)
|
||||||
|
|
||||||
type MappersFactory func() []Mapper
|
type MappersFactory func() []Mapper
|
||||||
|
|
||||||
@@ -24,14 +28,17 @@ type BackReference struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Schemas struct {
|
type Schemas struct {
|
||||||
|
sync.Mutex
|
||||||
schemasByPath map[string]map[string]*Schema
|
schemasByPath map[string]map[string]*Schema
|
||||||
schemasBySubContext map[string]*Schema
|
schemasBySubContext map[string]*Schema
|
||||||
mappers map[string]map[string][]Mapper
|
mappers map[string]map[string][]Mapper
|
||||||
references map[string][]BackReference
|
references map[string][]BackReference
|
||||||
|
embedded map[string]*Schema
|
||||||
DefaultMappers MappersFactory
|
DefaultMappers MappersFactory
|
||||||
DefaultPostMappers MappersFactory
|
DefaultPostMappers MappersFactory
|
||||||
versions []APIVersion
|
versions []APIVersion
|
||||||
schemas []*Schema
|
schemas []*Schema
|
||||||
|
AddHook SchemaHook
|
||||||
errors []error
|
errors []error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,10 +48,11 @@ func NewSchemas() *Schemas {
|
|||||||
schemasBySubContext: map[string]*Schema{},
|
schemasBySubContext: map[string]*Schema{},
|
||||||
mappers: map[string]map[string][]Mapper{},
|
mappers: map[string]map[string][]Mapper{},
|
||||||
references: map[string][]BackReference{},
|
references: map[string][]BackReference{},
|
||||||
|
embedded: map[string]*Schema{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Schemas) Init(initFunc SchemaInitFunc) *Schemas {
|
func (s *Schemas) Init(initFunc SchemasInitFunc) *Schemas {
|
||||||
return initFunc(s)
|
return initFunc(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,13 +61,11 @@ func (s *Schemas) Err() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Schemas) SubContext(subContext string) *Schema {
|
func (s *Schemas) SubContext(subContext string) *Schema {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
return s.schemasBySubContext[subContext]
|
return s.schemasBySubContext[subContext]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Schemas) SubContextSchemas() map[string]*Schema {
|
|
||||||
return s.schemasBySubContext
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Schemas) AddSchemas(schema *Schemas) *Schemas {
|
func (s *Schemas) AddSchemas(schema *Schemas) *Schemas {
|
||||||
for _, schema := range schema.Schemas() {
|
for _, schema := range schema.Schemas() {
|
||||||
s.AddSchema(*schema)
|
s.AddSchema(*schema)
|
||||||
@@ -67,27 +73,58 @@ func (s *Schemas) AddSchemas(schema *Schemas) *Schemas {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Schemas) RemoveSchema(schema Schema) *Schemas {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
return s.doRemoveSchema(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schemas) doRemoveSchema(schema Schema) *Schemas {
|
||||||
|
delete(s.schemasByPath[schema.Version.Path], schema.ID)
|
||||||
|
|
||||||
|
s.removeReferences(&schema)
|
||||||
|
|
||||||
|
delete(s.schemasBySubContext, schema.SubContext)
|
||||||
|
|
||||||
|
if schema.Embed {
|
||||||
|
s.removeEmbed(&schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schemas) removeReferences(schema *Schema) {
|
||||||
|
fullType := convert.ToFullReference(schema.Version.Path, schema.ID)
|
||||||
|
delete(s.references, fullType)
|
||||||
|
|
||||||
|
for name, values := range s.references {
|
||||||
|
changed := false
|
||||||
|
var modified []BackReference
|
||||||
|
for _, value := range values {
|
||||||
|
if value.Schema.ID == schema.ID && value.Schema.Version.Path == schema.Version.Path {
|
||||||
|
changed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
modified = append(modified, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
s.references[name] = modified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Schemas) AddSchema(schema Schema) *Schemas {
|
func (s *Schemas) AddSchema(schema Schema) *Schemas {
|
||||||
schema.Type = "/meta/schemas/schema"
|
s.Lock()
|
||||||
if schema.ID == "" {
|
defer s.Unlock()
|
||||||
s.errors = append(s.errors, fmt.Errorf("ID is not set on schema: %v", schema))
|
return s.doAddSchema(schema)
|
||||||
return s
|
}
|
||||||
}
|
|
||||||
if schema.Version.Path == "" || schema.Version.Version == "" {
|
func (s *Schemas) doAddSchema(schema Schema) *Schemas {
|
||||||
s.errors = append(s.errors, fmt.Errorf("version is not set on schema: %s", schema.ID))
|
s.setupDefaults(&schema)
|
||||||
return s
|
|
||||||
}
|
if s.AddHook != nil {
|
||||||
if schema.PluralName == "" {
|
s.AddHook(&schema)
|
||||||
schema.PluralName = name.GuessPluralName(schema.ID)
|
|
||||||
}
|
|
||||||
if schema.CodeName == "" {
|
|
||||||
schema.CodeName = convert.Capitalize(schema.ID)
|
|
||||||
}
|
|
||||||
if schema.CodeNamePlural == "" {
|
|
||||||
schema.CodeNamePlural = name.GuessPluralName(schema.CodeName)
|
|
||||||
}
|
|
||||||
if schema.BaseType == "" {
|
|
||||||
schema.BaseType = schema.ID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
schemas, ok := s.schemasByPath[schema.Version.Path]
|
schemas, ok := s.schemasByPath[schema.Version.Path]
|
||||||
@@ -101,20 +138,8 @@ func (s *Schemas) AddSchema(schema Schema) *Schemas {
|
|||||||
schemas[schema.ID] = &schema
|
schemas[schema.ID] = &schema
|
||||||
s.schemas = append(s.schemas, &schema)
|
s.schemas = append(s.schemas, &schema)
|
||||||
|
|
||||||
for name, field := range schema.ResourceFields {
|
if !schema.Embed {
|
||||||
if !definition.IsReferenceType(field.Type) {
|
s.addReferences(&schema)
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
refType := definition.SubType(field.Type)
|
|
||||||
if !strings.HasPrefix(refType, "/") {
|
|
||||||
refType = convert.ToFullReference(schema.Version.Path, refType)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.references[refType] = append(s.references[refType], BackReference{
|
|
||||||
FieldName: name,
|
|
||||||
Schema: &schema,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,11 +147,100 @@ func (s *Schemas) AddSchema(schema Schema) *Schemas {
|
|||||||
s.schemasBySubContext[schema.SubContext] = &schema
|
s.schemasBySubContext[schema.SubContext] = &schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if schema.Embed {
|
||||||
|
s.embed(&schema)
|
||||||
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Schemas) removeEmbed(schema *Schema) {
|
||||||
|
target := s.doSchema(&schema.Version, schema.EmbedType, false)
|
||||||
|
if target == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newSchema := *target
|
||||||
|
newSchema.ResourceFields = map[string]Field{}
|
||||||
|
|
||||||
|
for k, v := range target.ResourceFields {
|
||||||
|
newSchema.ResourceFields[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range schema.ResourceFields {
|
||||||
|
delete(newSchema.ResourceFields, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.doRemoveSchema(*target)
|
||||||
|
s.doAddSchema(newSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schemas) embed(schema *Schema) {
|
||||||
|
target := s.doSchema(&schema.Version, schema.EmbedType, false)
|
||||||
|
if target == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newSchema := *target
|
||||||
|
newSchema.ResourceFields = map[string]Field{}
|
||||||
|
|
||||||
|
for k, v := range target.ResourceFields {
|
||||||
|
newSchema.ResourceFields[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range schema.ResourceFields {
|
||||||
|
newSchema.ResourceFields[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
s.doRemoveSchema(*target)
|
||||||
|
s.doAddSchema(newSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schemas) addReferences(schema *Schema) {
|
||||||
|
for name, field := range schema.ResourceFields {
|
||||||
|
if !definition.IsReferenceType(field.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
refType := definition.SubType(field.Type)
|
||||||
|
if !strings.HasPrefix(refType, "/") {
|
||||||
|
refType = convert.ToFullReference(schema.Version.Path, refType)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.references[refType] = append(s.references[refType], BackReference{
|
||||||
|
FieldName: name,
|
||||||
|
Schema: schema,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schemas) setupDefaults(schema *Schema) {
|
||||||
|
schema.Type = "/meta/schemas/schema"
|
||||||
|
if schema.ID == "" {
|
||||||
|
s.errors = append(s.errors, fmt.Errorf("ID is not set on schema: %v", schema))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if schema.Version.Path == "" || schema.Version.Version == "" {
|
||||||
|
s.errors = append(s.errors, fmt.Errorf("version is not set on schema: %s", schema.ID))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if schema.PluralName == "" {
|
||||||
|
schema.PluralName = name.GuessPluralName(schema.ID)
|
||||||
|
}
|
||||||
|
if schema.CodeName == "" {
|
||||||
|
schema.CodeName = convert.Capitalize(schema.ID)
|
||||||
|
}
|
||||||
|
if schema.CodeNamePlural == "" {
|
||||||
|
schema.CodeNamePlural = name.GuessPluralName(schema.CodeName)
|
||||||
|
}
|
||||||
|
if schema.BaseType == "" {
|
||||||
|
schema.BaseType = schema.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Schemas) References(schema *Schema) []BackReference {
|
func (s *Schemas) References(schema *Schema) []BackReference {
|
||||||
refType := convert.ToFullReference(schema.Version.Path, schema.ID)
|
refType := convert.ToFullReference(schema.Version.Path, schema.ID)
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
return s.references[refType]
|
return s.references[refType]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +256,8 @@ func (s *Schemas) AddMapper(version *APIVersion, schemaID string, mapper Mapper)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Schemas) SchemasForVersion(version APIVersion) map[string]*Schema {
|
func (s *Schemas) SchemasForVersion(version APIVersion) map[string]*Schema {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
return s.schemasByPath[version.Path]
|
return s.schemasByPath[version.Path]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +298,10 @@ func (s *Schemas) mapper(version *APIVersion, name string) []Mapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Schemas) Schema(version *APIVersion, name string) *Schema {
|
func (s *Schemas) Schema(version *APIVersion, name string) *Schema {
|
||||||
|
return s.doSchema(version, name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Schemas) doSchema(version *APIVersion, name string, lock bool) *Schema {
|
||||||
var (
|
var (
|
||||||
path string
|
path string
|
||||||
)
|
)
|
||||||
@@ -196,7 +316,13 @@ func (s *Schemas) Schema(version *APIVersion, name string) *Schema {
|
|||||||
path = "core"
|
path = "core"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if lock {
|
||||||
|
s.Lock()
|
||||||
|
}
|
||||||
schemas, ok := s.schemasByPath[path]
|
schemas, ok := s.schemasByPath[path]
|
||||||
|
if lock {
|
||||||
|
s.Unlock()
|
||||||
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -79,6 +79,8 @@ type TypeScope string
|
|||||||
|
|
||||||
type Schema struct {
|
type Schema struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
|
Embed bool `json:"embed,omitempty"`
|
||||||
|
EmbedType string `json:"embedType,omitempty"`
|
||||||
CodeName string `json:"-"`
|
CodeName string `json:"-"`
|
||||||
CodeNamePlural string `json:"-"`
|
CodeNamePlural string `json:"-"`
|
||||||
PkgName string `json:"-"`
|
PkgName string `json:"-"`
|
||||||
|
Reference in New Issue
Block a user