1
0
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:
Darren Shepherd
2017-12-16 01:25:28 -07:00
parent 480747082c
commit 627e06e37f
3 changed files with 175 additions and 51 deletions

View File

@@ -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) {

View File

@@ -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
} }

View File

@@ -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:"-"`