github.com/jbking/gohan@v0.0.0-20151217002006-b41ccf1c2a96/schema/schema.go (about)

     1  // Copyright (C) 2015 NTT Innovation Institute, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    12  // implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package schema
    17  
    18  import (
    19  	"fmt"
    20  
    21  	"github.com/xeipuuv/gojsonschema"
    22  )
    23  
    24  //Schema type for defining data type
    25  type Schema struct {
    26  	ID, Plural, Title, Description string
    27  	ParentSchema                   *Schema
    28  	Parent                         string
    29  	NamespaceID                    string
    30  	Namespace                      *Namespace
    31  	Tags                           Tags
    32  	Metadata                       map[string]interface{}
    33  	Policy                         []interface{}
    34  	Prefix                         string
    35  	Properties                     []Property
    36  	Required                       []string
    37  	JSONSchema                     map[string]interface{}
    38  	JSONSchemaOnCreate             map[string]interface{}
    39  	JSONSchemaOnUpdate             map[string]interface{}
    40  	Actions                        []Action
    41  	Singular                       string
    42  	URL                            string
    43  	URLWithParents                 string
    44  	RawData                        interface{}
    45  }
    46  
    47  //Schemas is a list of schema
    48  //This struct is needed for json decode
    49  type Schemas struct {
    50  	Schemas []*Schema
    51  }
    52  
    53  //Map is a map of schema
    54  type Map map[string]*Schema
    55  
    56  type typeAssertionError struct {
    57  	field string
    58  }
    59  
    60  func (e *typeAssertionError) Error() string {
    61  	return fmt.Sprintf("Type Assertion Error: invalid schema %v field", e.field)
    62  }
    63  
    64  //NewSchema is a constructor for a schema
    65  func NewSchema(id, plural, title, description, singular string) *Schema {
    66  	schema := &Schema{
    67  		ID:          id,
    68  		Title:       title,
    69  		Plural:      plural,
    70  		Description: description,
    71  		Singular:    singular,
    72  	}
    73  	schema.Tags = make(Tags)
    74  	schema.Policy = make([]interface{}, 0)
    75  	schema.Properties = make([]Property, 0)
    76  	schema.Required = make([]string, 0)
    77  	return schema
    78  }
    79  
    80  //NewSchemaFromObj is a constructor for a schema by obj
    81  func NewSchemaFromObj(rawTypeData interface{}) (*Schema, error) {
    82  	typeData := rawTypeData.(map[string]interface{})
    83  
    84  	metaschema, ok := GetManager().Schema("schema")
    85  	if ok {
    86  		err := metaschema.Validate(metaschema.JSONSchema, typeData)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  
    92  	id, ok := typeData["id"].(string)
    93  	if !ok {
    94  		return nil, &typeAssertionError{"id"}
    95  	}
    96  	plural, ok := typeData["plural"].(string)
    97  	if !ok {
    98  		return nil, &typeAssertionError{"plural"}
    99  	}
   100  	title, ok := typeData["title"].(string)
   101  	if !ok {
   102  		return nil, &typeAssertionError{"title"}
   103  	}
   104  	prefix, _ := typeData["prefix"].(string)
   105  	url, _ := typeData["url"].(string)
   106  	description, ok := typeData["description"].(string)
   107  	if !ok {
   108  		return nil, &typeAssertionError{"description"}
   109  	}
   110  	parent, _ := typeData["parent"].(string)
   111  	namespaceID, _ := typeData["namespace"].(string)
   112  	jsonSchema, ok := typeData["schema"].(map[string]interface{})
   113  	if !ok {
   114  		return nil, &typeAssertionError{"schema"}
   115  	}
   116  
   117  	actions, ok := typeData["actions"].(map[string]interface{})
   118  	actionList := []Action{}
   119  	for actionID, actionBody := range actions {
   120  		action, err := NewActionFromObject(actionID, actionBody)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		actionList = append(actionList, action)
   125  	}
   126  
   127  	required, _ := jsonSchema["required"]
   128  	if required == nil {
   129  		required = []interface{}{}
   130  	}
   131  	if parent != "" && jsonSchema["properties"].(map[string]interface{})[FormatParentID(parent)] == nil {
   132  		jsonSchema["properties"].(map[string]interface{})[FormatParentID(parent)] = getParentPropertyObj(parent, parent)
   133  		jsonSchema["propertiesOrder"] = append(jsonSchema["propertiesOrder"].([]interface{}), FormatParentID(parent))
   134  		required = append(required.([]interface{}), FormatParentID(parent))
   135  	}
   136  
   137  	jsonSchema["required"] = required
   138  
   139  	requiredStrings := []string{}
   140  	for _, req := range required.([]interface{}) {
   141  		requiredStrings = append(requiredStrings, req.(string))
   142  	}
   143  
   144  	metadata, _ := typeData["metadata"].(map[string]interface{})
   145  	properties, _ := jsonSchema["properties"].(map[string]interface{})
   146  
   147  	policy, _ := typeData["policy"].([]interface{})
   148  	singular, ok := typeData["singular"].(string)
   149  	if !ok {
   150  		return nil, &typeAssertionError{"singular"}
   151  	}
   152  
   153  	schema := &Schema{
   154  		ID:                 id,
   155  		Title:              title,
   156  		Parent:             parent,
   157  		NamespaceID:        namespaceID,
   158  		Prefix:             prefix,
   159  		Plural:             plural,
   160  		Policy:             policy,
   161  		Description:        description,
   162  		JSONSchema:         jsonSchema,
   163  		JSONSchemaOnCreate: filterSchemaByPermission(jsonSchema, "create"),
   164  		JSONSchemaOnUpdate: filterSchemaByPermission(jsonSchema, "update"),
   165  		Actions:            actionList,
   166  		Metadata:           metadata,
   167  		RawData:            rawTypeData,
   168  		Singular:           singular,
   169  		URL:                url,
   170  		Required:           requiredStrings,
   171  	}
   172  	//TODO(nati) load tags
   173  	schema.Tags = make(Tags)
   174  	schema.Properties = make([]Property, 0)
   175  	for id, property := range properties {
   176  		required := false
   177  		for _, req := range schema.Required {
   178  			if req == id {
   179  				required = true
   180  				break
   181  			}
   182  		}
   183  		propertyObj, err := NewPropertyFromObj(id, property, required)
   184  		if err != nil {
   185  			return nil, fmt.Errorf("Invalid schema: Properties is missing %v", err)
   186  		}
   187  		schema.Properties = append(schema.Properties, *propertyObj)
   188  	}
   189  	return schema, nil
   190  }
   191  
   192  // ParentID returns parent property ID
   193  func (schema *Schema) ParentID() string {
   194  	if schema.Parent == "" {
   195  		return ""
   196  	}
   197  	return FormatParentID(schema.Parent)
   198  }
   199  
   200  // GetSingleURL returns a URL for access to a single schema object
   201  func (schema *Schema) GetSingleURL() string {
   202  	return fmt.Sprintf("%s/:id", schema.URL)
   203  }
   204  
   205  // GetActionURL returns a URL for access to resources actions
   206  func (schema *Schema) GetActionURL(path string) string {
   207  	return schema.URL + path
   208  }
   209  
   210  // GetPluralURL returns a URL for access to all schema objects
   211  func (schema *Schema) GetPluralURL() string {
   212  	return schema.URL
   213  }
   214  
   215  // GetSingleURLWithParents returns a URL for access to a single schema object
   216  func (schema *Schema) GetSingleURLWithParents() string {
   217  	return fmt.Sprintf("%s/:id", schema.URLWithParents)
   218  }
   219  
   220  // GetPluralURLWithParents returns a URL for access to all schema objects
   221  func (schema *Schema) GetPluralURLWithParents() string {
   222  	return schema.URLWithParents
   223  }
   224  
   225  // GetDbTableName returns a name of DB table used for storing schema instances
   226  func (schema *Schema) GetDbTableName() string {
   227  	return schema.ID + "s"
   228  }
   229  
   230  // GetParentURL returns Parent URL
   231  func (schema *Schema) GetParentURL() string {
   232  	if schema.Parent == "" {
   233  		return ""
   234  	}
   235  
   236  	return schema.ParentSchema.GetParentURL() + "/" + schema.ParentSchema.Plural + "/:" + schema.Parent
   237  }
   238  
   239  func filterSchemaByPermission(schema map[string]interface{}, permission string) map[string]interface{} {
   240  	filteredSchema := map[string]interface{}{"type": "object"}
   241  	filteredProperties := map[string]map[string]interface{}{}
   242  	for id, property := range schema["properties"].(map[string]interface{}) {
   243  		propertyMap, ok := property.(map[string]interface{})
   244  		if ok == false {
   245  			continue
   246  		}
   247  		allowedList, ok := propertyMap["permission"]
   248  		if ok == false {
   249  			continue
   250  		}
   251  		allowedStringList, ok := allowedList.([]interface{})
   252  		if ok == false {
   253  			continue
   254  		}
   255  		for _, allowed := range allowedStringList {
   256  			if allowed == permission {
   257  				filteredProperties[id] = propertyMap
   258  			}
   259  		}
   260  	}
   261  	filteredSchema["properties"] = filteredProperties
   262  	if required, ok := schema["required"]; ok {
   263  		filteredSchema["required"] = required
   264  	} else {
   265  		filteredSchema["required"] = []string{}
   266  	}
   267  	if permission != "create" {
   268  		// in case of updates or deletes, we don't expect all required attributes
   269  		filteredSchema["required"] = []string{}
   270  	}
   271  	filteredSchema["additionalProperties"] = false
   272  	return filteredSchema
   273  }
   274  
   275  func getParentPropertyObj(title, parent string) map[string]interface{} {
   276  	return map[string]interface{}{
   277  		"type":        "string",
   278  		"relation":    parent,
   279  		"title":       title,
   280  		"description": "parent object",
   281  		"unique":      false,
   282  		"permission":  []interface{}{"create"},
   283  	}
   284  }
   285  
   286  //ValidateOnCreate validates json object using jsoncschema on object creation
   287  func (schema *Schema) ValidateOnCreate(object interface{}) error {
   288  	return schema.Validate(schema.JSONSchemaOnCreate, object)
   289  }
   290  
   291  //ValidateOnUpdate validates json object using jsoncschema on object update
   292  func (schema *Schema) ValidateOnUpdate(object interface{}) error {
   293  	return schema.Validate(schema.JSONSchemaOnUpdate, object)
   294  }
   295  
   296  //Validate validates json object using jsoncschema
   297  func (schema *Schema) Validate(jsonSchema interface{}, object interface{}) error {
   298  	schemaLoader := gojsonschema.NewGoLoader(jsonSchema)
   299  	documentLoader := gojsonschema.NewGoLoader(object)
   300  	result, err := gojsonschema.Validate(schemaLoader, documentLoader)
   301  	if err != nil {
   302  		return err
   303  	}
   304  	if result.Valid() {
   305  		return nil
   306  	}
   307  	errDescription := "Json validation error:"
   308  	for _, err := range result.Errors() {
   309  		errDescription += fmt.Sprintf("\n\t%v,", err)
   310  	}
   311  	return fmt.Errorf(errDescription)
   312  }
   313  
   314  //SetParentSchema sets parent schema
   315  func (schema *Schema) SetParentSchema(parentSchema *Schema) {
   316  	schema.ParentSchema = parentSchema
   317  }
   318  
   319  // SetNamespace sets namespace
   320  func (schema *Schema) SetNamespace(namespace *Namespace) {
   321  	schema.Namespace = namespace
   322  }
   323  
   324  //ParentSchemaPropertyID get property id for parent relation
   325  func (schema *Schema) ParentSchemaPropertyID() string {
   326  	if schema.Parent == "" {
   327  		return ""
   328  	}
   329  	return FormatParentID(schema.Parent)
   330  }
   331  
   332  //GetPropertyByID get a property object using ID
   333  func (schema *Schema) GetPropertyByID(id string) (*Property, error) {
   334  	for _, p := range schema.Properties {
   335  		if p.ID == id {
   336  			return &p, nil
   337  		}
   338  	}
   339  	return nil, fmt.Errorf("Property with ID %s not found", id)
   340  }
   341  
   342  //StateVersioning whether resources created from this schema should track state and config versions
   343  func (schema *Schema) StateVersioning() bool {
   344  	statefulRaw, ok := schema.Metadata["state_versioning"]
   345  	if !ok {
   346  		return false
   347  	}
   348  	stateful, ok := statefulRaw.(bool)
   349  	if !ok {
   350  		return false
   351  	}
   352  	return stateful
   353  }
   354  
   355  // FormatParentID ...
   356  func FormatParentID(parent string) string {
   357  	return fmt.Sprintf("%s_id", parent)
   358  }
   359  
   360  func (schema *Schema) relatedSchemas() []string {
   361  	schemas := []string{}
   362  	for _, p := range schema.Properties {
   363  		if p.Relation != "" {
   364  			schemas = append(schemas, p.Relation)
   365  		}
   366  	}
   367  	return schemas
   368  }