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 }