mirror of
https://github.com/rancher/norman.git
synced 2025-09-03 16:25:09 +00:00
Enable dynamic schemas
This commit is contained in:
@@ -26,7 +26,7 @@ type Server struct {
|
||||
Resolver parse.ResolverFunc
|
||||
SubContextAttributeProvider types.SubContextAttributeProvider
|
||||
ResponseWriters map[string]ResponseWriter
|
||||
schemas *types.Schemas
|
||||
Schemas *types.Schemas
|
||||
QueryFilter types.QueryFilter
|
||||
StoreWrapper StoreWrapper
|
||||
URLParser parse.URLParser
|
||||
@@ -46,7 +46,7 @@ type Defaults struct {
|
||||
|
||||
func NewAPIServer() *Server {
|
||||
s := &Server{
|
||||
schemas: types.NewSchemas(),
|
||||
Schemas: types.NewSchemas(),
|
||||
ResponseWriters: map[string]ResponseWriter{
|
||||
"json": &writer.JSONResponseWriter{},
|
||||
"html": &writer.HTMLResponseWriter{},
|
||||
@@ -68,12 +68,13 @@ func NewAPIServer() *Server {
|
||||
QueryFilter: handler.QueryFilter,
|
||||
}
|
||||
|
||||
s.Schemas.AddHook = s.setupDefaults
|
||||
s.Parser = s.parser
|
||||
return s
|
||||
}
|
||||
|
||||
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]
|
||||
if ctx.ResponseWriter == nil {
|
||||
ctx.ResponseWriter = s.ResponseWriters["json"]
|
||||
@@ -102,20 +103,15 @@ func (s *Server) AddSchemas(schemas *types.Schemas) error {
|
||||
return
|
||||
}
|
||||
for _, schema := range builtin.Schemas.Schemas() {
|
||||
s.addSchema(*schema)
|
||||
s.Schemas.AddSchema(*schema)
|
||||
}
|
||||
})
|
||||
|
||||
for _, schema := range schemas.Schemas() {
|
||||
s.addSchema(*schema)
|
||||
s.Schemas.AddSchema(*schema)
|
||||
}
|
||||
|
||||
return s.schemas.Err()
|
||||
}
|
||||
|
||||
func (s *Server) addSchema(schema types.Schema) {
|
||||
s.setupDefaults(&schema)
|
||||
s.schemas.AddSchema(schema)
|
||||
return s.Schemas.Err()
|
||||
}
|
||||
|
||||
func (s *Server) setupDefaults(schema *types.Schema) {
|
||||
|
186
types/schemas.go
186
types/schemas.go
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/rancher/norman/name"
|
||||
"github.com/rancher/norman/types/convert"
|
||||
"github.com/rancher/norman/types/definition"
|
||||
@@ -14,7 +16,9 @@ type SchemaCollection struct {
|
||||
Data []Schema
|
||||
}
|
||||
|
||||
type SchemaInitFunc func(*Schemas) *Schemas
|
||||
type SchemasInitFunc func(*Schemas) *Schemas
|
||||
|
||||
type SchemaHook func(*Schema)
|
||||
|
||||
type MappersFactory func() []Mapper
|
||||
|
||||
@@ -24,14 +28,17 @@ type BackReference struct {
|
||||
}
|
||||
|
||||
type Schemas struct {
|
||||
sync.Mutex
|
||||
schemasByPath map[string]map[string]*Schema
|
||||
schemasBySubContext map[string]*Schema
|
||||
mappers map[string]map[string][]Mapper
|
||||
references map[string][]BackReference
|
||||
embedded map[string]*Schema
|
||||
DefaultMappers MappersFactory
|
||||
DefaultPostMappers MappersFactory
|
||||
versions []APIVersion
|
||||
schemas []*Schema
|
||||
AddHook SchemaHook
|
||||
errors []error
|
||||
}
|
||||
|
||||
@@ -41,10 +48,11 @@ func NewSchemas() *Schemas {
|
||||
schemasBySubContext: map[string]*Schema{},
|
||||
mappers: map[string]map[string][]Mapper{},
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -53,13 +61,11 @@ func (s *Schemas) Err() error {
|
||||
}
|
||||
|
||||
func (s *Schemas) SubContext(subContext string) *Schema {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
return s.schemasBySubContext[subContext]
|
||||
}
|
||||
|
||||
func (s *Schemas) SubContextSchemas() map[string]*Schema {
|
||||
return s.schemasBySubContext
|
||||
}
|
||||
|
||||
func (s *Schemas) AddSchemas(schema *Schemas) *Schemas {
|
||||
for _, schema := range schema.Schemas() {
|
||||
s.AddSchema(*schema)
|
||||
@@ -67,27 +73,58 @@ func (s *Schemas) AddSchemas(schema *Schemas) *Schemas {
|
||||
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 {
|
||||
schema.Type = "/meta/schemas/schema"
|
||||
if schema.ID == "" {
|
||||
s.errors = append(s.errors, fmt.Errorf("ID is not set on schema: %v", schema))
|
||||
return s
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
return s.doAddSchema(schema)
|
||||
}
|
||||
if schema.Version.Path == "" || schema.Version.Version == "" {
|
||||
s.errors = append(s.errors, fmt.Errorf("version is not set on schema: %s", schema.ID))
|
||||
return s
|
||||
}
|
||||
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) doAddSchema(schema Schema) *Schemas {
|
||||
s.setupDefaults(&schema)
|
||||
|
||||
if s.AddHook != nil {
|
||||
s.AddHook(&schema)
|
||||
}
|
||||
|
||||
schemas, ok := s.schemasByPath[schema.Version.Path]
|
||||
@@ -101,6 +138,64 @@ func (s *Schemas) AddSchema(schema Schema) *Schemas {
|
||||
schemas[schema.ID] = &schema
|
||||
s.schemas = append(s.schemas, &schema)
|
||||
|
||||
if !schema.Embed {
|
||||
s.addReferences(&schema)
|
||||
}
|
||||
}
|
||||
|
||||
if schema.SubContext != "" {
|
||||
s.schemasBySubContext[schema.SubContext] = &schema
|
||||
}
|
||||
|
||||
if schema.Embed {
|
||||
s.embed(&schema)
|
||||
}
|
||||
|
||||
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
|
||||
@@ -113,20 +208,39 @@ func (s *Schemas) AddSchema(schema Schema) *Schemas {
|
||||
|
||||
s.references[refType] = append(s.references[refType], BackReference{
|
||||
FieldName: name,
|
||||
Schema: &schema,
|
||||
Schema: schema,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if schema.SubContext != "" {
|
||||
s.schemasBySubContext[schema.SubContext] = &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
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Schemas) References(schema *Schema) []BackReference {
|
||||
refType := convert.ToFullReference(schema.Version.Path, schema.ID)
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
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 {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
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 {
|
||||
return s.doSchema(version, name, true)
|
||||
}
|
||||
|
||||
func (s *Schemas) doSchema(version *APIVersion, name string, lock bool) *Schema {
|
||||
var (
|
||||
path string
|
||||
)
|
||||
@@ -196,7 +316,13 @@ func (s *Schemas) Schema(version *APIVersion, name string) *Schema {
|
||||
path = "core"
|
||||
}
|
||||
|
||||
if lock {
|
||||
s.Lock()
|
||||
}
|
||||
schemas, ok := s.schemasByPath[path]
|
||||
if lock {
|
||||
s.Unlock()
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
@@ -79,6 +79,8 @@ type TypeScope string
|
||||
|
||||
type Schema struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Embed bool `json:"embed,omitempty"`
|
||||
EmbedType string `json:"embedType,omitempty"`
|
||||
CodeName string `json:"-"`
|
||||
CodeNamePlural string `json:"-"`
|
||||
PkgName string `json:"-"`
|
||||
|
Reference in New Issue
Block a user