1
0
mirror of https://github.com/rancher/norman.git synced 2025-05-07 15:46:42 +00:00
norman/types/schemas.go
2024-06-04 15:32:09 +05:30

408 lines
8.3 KiB
Go

package types
import (
"bytes"
"fmt"
"reflect"
"strings"
"sync"
"github.com/rancher/norman/types/convert"
"github.com/rancher/norman/types/definition"
"github.com/rancher/wrangler/v3/pkg/name"
)
type SchemaCollection struct {
Data []Schema
}
type SchemasInitFunc func(*Schemas) *Schemas
type SchemaHook func(*Schema)
type MappersFactory func() []Mapper
type BackReference struct {
FieldName string
Schema *Schema
}
type Schemas struct {
sync.Mutex
processingTypes map[reflect.Type]*Schema
typeNames map[reflect.Type]string
schemasByPath map[string]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
}
func NewSchemas() *Schemas {
return &Schemas{
processingTypes: map[reflect.Type]*Schema{},
typeNames: map[reflect.Type]string{},
schemasByPath: map[string]map[string]*Schema{},
mappers: map[string]map[string][]Mapper{},
references: map[string][]BackReference{},
embedded: map[string]*Schema{},
}
}
func (s *Schemas) Init(initFunc SchemasInitFunc) *Schemas {
return initFunc(s)
}
func (s *Schemas) Err() error {
return NewErrors(s.errors...)
}
func (s *Schemas) AddSchemas(schema *Schemas) *Schemas {
for _, schema := range schema.Schemas() {
s.AddSchema(*schema)
}
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)
if schema.Embed {
s.removeEmbed(&schema)
}
return s
}
func (s *Schemas) removeReferences(schema *Schema) {
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 {
s.Lock()
defer s.Unlock()
return s.doAddSchema(schema, false)
}
func (s *Schemas) ForceAddSchema(schema Schema) *Schemas {
s.Lock()
defer s.Unlock()
return s.doAddSchema(schema, true)
}
func (s *Schemas) doAddSchema(schema Schema, replace bool) *Schemas {
s.setupDefaults(&schema)
if s.AddHook != nil {
s.AddHook(&schema)
}
schemas, ok := s.schemasByPath[schema.Version.Path]
if !ok {
schemas = map[string]*Schema{}
s.schemasByPath[schema.Version.Path] = schemas
s.versions = append(s.versions, schema.Version)
}
if _, ok := schemas[schema.ID]; !ok ||
(replace && schema.DynamicSchemaVersion != schemas[schema.ID].DynamicSchemaVersion) {
schemas[schema.ID] = &schema
if replace {
for i, candidate := range s.schemas {
if candidate.ID == schema.ID {
s.schemas[i] = &schema
break
}
}
} else {
s.schemas = append(s.schemas, &schema)
}
if !schema.Embed {
s.addReferences(&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, false)
}
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 {
// We remove the dynamic fields off the existing schema in case
// they've been removed from the dynamic schema so they won't
// be accidentally left over
if !v.DynamicField {
newSchema.ResourceFields[k] = v
}
}
for k, v := range schema.ResourceFields {
newSchema.ResourceFields[k] = v
}
s.doRemoveSchema(*target)
s.doAddSchema(newSchema, false)
}
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 {
refType := convert.ToFullReference(schema.Version.Path, schema.ID)
s.Lock()
defer s.Unlock()
return s.references[refType]
}
func (s *Schemas) AddMapper(version *APIVersion, schemaID string, mapper Mapper) *Schemas {
mappers, ok := s.mappers[version.Path]
if !ok {
mappers = map[string][]Mapper{}
s.mappers[version.Path] = mappers
}
mappers[schemaID] = append(mappers[schemaID], mapper)
return s
}
func (s *Schemas) SchemasForVersion(version APIVersion) map[string]*Schema {
s.Lock()
defer s.Unlock()
return s.schemasByPath[version.Path]
}
func (s *Schemas) Versions() []APIVersion {
return s.versions
}
func (s *Schemas) Schemas() []*Schema {
return s.schemas
}
func (s *Schemas) mapper(version *APIVersion, name string) []Mapper {
var (
path string
)
if strings.Contains(name, "/") {
idx := strings.LastIndex(name, "/")
path = name[0:idx]
name = name[idx+1:]
} else if version != nil {
path = version.Path
} else {
path = "core"
}
mappers, ok := s.mappers[path]
if !ok {
return nil
}
mapper := mappers[name]
if mapper != nil {
return mapper
}
return nil
}
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
)
if strings.Contains(name, "/schemas/") {
parts := strings.SplitN(name, "/schemas/", 2)
path = parts[0]
name = parts[1]
} else if version != nil {
path = version.Path
} else {
path = "core"
}
if lock {
s.Lock()
defer s.Unlock()
}
schemas, ok := s.schemasByPath[path]
if !ok {
return nil
}
schema := schemas[name]
if schema != nil {
return schema
}
for _, check := range schemas {
if strings.EqualFold(check.ID, name) || strings.EqualFold(check.PluralName, name) {
return check
}
}
return nil
}
func (s *Schemas) SubContextVersionForSchema(schema *Schema) *APIVersion {
fullName := fmt.Sprintf("%s/schemas/%s", schema.Version.Path, schema.ID)
for _, version := range s.Versions() {
if version.SubContextSchema == fullName {
return &version
}
}
return nil
}
type MultiErrors struct {
Errors []error
}
type Errors struct {
errors []error
}
func (e *Errors) Add(err error) {
if err != nil {
e.errors = append(e.errors, err)
}
}
func (e *Errors) Err() error {
return NewErrors(e.errors...)
}
func NewErrors(inErrors ...error) error {
var errors []error
for _, err := range inErrors {
if err != nil {
errors = append(errors, err)
}
}
if len(errors) == 0 {
return nil
} else if len(errors) == 1 {
return errors[0]
}
return &MultiErrors{
Errors: errors,
}
}
func (m *MultiErrors) Error() string {
buf := bytes.NewBuffer(nil)
for _, err := range m.Errors {
if buf.Len() > 0 {
buf.WriteString(", ")
}
buf.WriteString(err.Error())
}
return buf.String()
}