...

Source file src/github.com/go-openapi/validate/schema.go

Documentation: github.com/go-openapi/validate

     1  // Copyright 2015 go-swagger maintainers
     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 implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validate
    16  
    17  import (
    18  	"encoding/json"
    19  	"reflect"
    20  
    21  	"github.com/go-openapi/errors"
    22  	"github.com/go-openapi/spec"
    23  	"github.com/go-openapi/strfmt"
    24  	"github.com/go-openapi/swag"
    25  )
    26  
    27  // SchemaValidator validates data against a JSON schema
    28  type SchemaValidator struct {
    29  	Path         string
    30  	in           string
    31  	Schema       *spec.Schema
    32  	validators   [8]valueValidator
    33  	Root         interface{}
    34  	KnownFormats strfmt.Registry
    35  	Options      *SchemaValidatorOptions
    36  }
    37  
    38  // AgainstSchema validates the specified data against the provided schema, using a registry of supported formats.
    39  //
    40  // When no pre-parsed *spec.Schema structure is provided, it uses a JSON schema as default. See example.
    41  func AgainstSchema(schema *spec.Schema, data interface{}, formats strfmt.Registry, options ...Option) error {
    42  	res := NewSchemaValidator(schema, nil, "", formats,
    43  		append(options, WithRecycleValidators(true), withRecycleResults(true))...,
    44  	).Validate(data)
    45  	defer func() {
    46  		pools.poolOfResults.RedeemResult(res)
    47  	}()
    48  
    49  	if res.HasErrors() {
    50  		return errors.CompositeValidationError(res.Errors...)
    51  	}
    52  
    53  	return nil
    54  }
    55  
    56  // NewSchemaValidator creates a new schema validator.
    57  //
    58  // Panics if the provided schema is invalid.
    59  func NewSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, options ...Option) *SchemaValidator {
    60  	opts := new(SchemaValidatorOptions)
    61  	for _, o := range options {
    62  		o(opts)
    63  	}
    64  
    65  	return newSchemaValidator(schema, rootSchema, root, formats, opts)
    66  }
    67  
    68  func newSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, opts *SchemaValidatorOptions) *SchemaValidator {
    69  	if schema == nil {
    70  		return nil
    71  	}
    72  
    73  	if rootSchema == nil {
    74  		rootSchema = schema
    75  	}
    76  
    77  	if schema.ID != "" || schema.Ref.String() != "" || schema.Ref.IsRoot() {
    78  		err := spec.ExpandSchema(schema, rootSchema, nil)
    79  		if err != nil {
    80  			msg := invalidSchemaProvidedMsg(err).Error()
    81  			panic(msg)
    82  		}
    83  	}
    84  
    85  	if opts == nil {
    86  		opts = new(SchemaValidatorOptions)
    87  	}
    88  
    89  	var s *SchemaValidator
    90  	if opts.recycleValidators {
    91  		s = pools.poolOfSchemaValidators.BorrowValidator()
    92  	} else {
    93  		s = new(SchemaValidator)
    94  	}
    95  
    96  	s.Path = root
    97  	s.in = "body"
    98  	s.Schema = schema
    99  	s.Root = rootSchema
   100  	s.Options = opts
   101  	s.KnownFormats = formats
   102  
   103  	s.validators = [8]valueValidator{
   104  		s.typeValidator(),
   105  		s.schemaPropsValidator(),
   106  		s.stringValidator(),
   107  		s.formatValidator(),
   108  		s.numberValidator(),
   109  		s.sliceValidator(),
   110  		s.commonValidator(),
   111  		s.objectValidator(),
   112  	}
   113  
   114  	return s
   115  }
   116  
   117  // SetPath sets the path for this schema valdiator
   118  func (s *SchemaValidator) SetPath(path string) {
   119  	s.Path = path
   120  }
   121  
   122  // Applies returns true when this schema validator applies
   123  func (s *SchemaValidator) Applies(source interface{}, _ reflect.Kind) bool {
   124  	_, ok := source.(*spec.Schema)
   125  	return ok
   126  }
   127  
   128  // Validate validates the data against the schema
   129  func (s *SchemaValidator) Validate(data interface{}) *Result {
   130  	if s == nil {
   131  		return emptyResult
   132  	}
   133  
   134  	if s.Options.recycleValidators {
   135  		defer func() {
   136  			s.redeemChildren()
   137  			s.redeem() // one-time use validator
   138  		}()
   139  	}
   140  
   141  	var result *Result
   142  	if s.Options.recycleResult {
   143  		result = pools.poolOfResults.BorrowResult()
   144  		result.data = data
   145  	} else {
   146  		result = &Result{data: data}
   147  	}
   148  
   149  	if s.Schema != nil && !s.Options.skipSchemataResult {
   150  		result.addRootObjectSchemata(s.Schema)
   151  	}
   152  
   153  	if data == nil {
   154  		// early exit with minimal validation
   155  		result.Merge(s.validators[0].Validate(data)) // type validator
   156  		result.Merge(s.validators[6].Validate(data)) // common validator
   157  
   158  		if s.Options.recycleValidators {
   159  			s.validators[0] = nil
   160  			s.validators[6] = nil
   161  		}
   162  
   163  		return result
   164  	}
   165  
   166  	tpe := reflect.TypeOf(data)
   167  	kind := tpe.Kind()
   168  	for kind == reflect.Ptr {
   169  		tpe = tpe.Elem()
   170  		kind = tpe.Kind()
   171  	}
   172  	d := data
   173  
   174  	if kind == reflect.Struct {
   175  		// NOTE: since reflect retrieves the true nature of types
   176  		// this means that all strfmt types passed here (e.g. strfmt.Datetime, etc..)
   177  		// are converted here to strings, and structs are systematically converted
   178  		// to map[string]interface{}.
   179  		d = swag.ToDynamicJSON(data)
   180  	}
   181  
   182  	// TODO: this part should be handed over to type validator
   183  	// Handle special case of json.Number data (number marshalled as string)
   184  	isnumber := s.Schema.Type.Contains(numberType) || s.Schema.Type.Contains(integerType)
   185  	if num, ok := data.(json.Number); ok && isnumber {
   186  		if s.Schema.Type.Contains(integerType) { // avoid lossy conversion
   187  			in, erri := num.Int64()
   188  			if erri != nil {
   189  				result.AddErrors(invalidTypeConversionMsg(s.Path, erri))
   190  				result.Inc()
   191  
   192  				return result
   193  			}
   194  			d = in
   195  		} else {
   196  			nf, errf := num.Float64()
   197  			if errf != nil {
   198  				result.AddErrors(invalidTypeConversionMsg(s.Path, errf))
   199  				result.Inc()
   200  
   201  				return result
   202  			}
   203  			d = nf
   204  		}
   205  
   206  		tpe = reflect.TypeOf(d)
   207  		kind = tpe.Kind()
   208  	}
   209  
   210  	for idx, v := range s.validators {
   211  		if !v.Applies(s.Schema, kind) {
   212  			if s.Options.recycleValidators {
   213  				// Validate won't be called, so relinquish this validator
   214  				if redeemableChildren, ok := v.(interface{ redeemChildren() }); ok {
   215  					redeemableChildren.redeemChildren()
   216  				}
   217  				if redeemable, ok := v.(interface{ redeem() }); ok {
   218  					redeemable.redeem()
   219  				}
   220  				s.validators[idx] = nil // prevents further (unsafe) usage
   221  			}
   222  
   223  			continue
   224  		}
   225  
   226  		result.Merge(v.Validate(d))
   227  		if s.Options.recycleValidators {
   228  			s.validators[idx] = nil // prevents further (unsafe) usage
   229  		}
   230  		result.Inc()
   231  	}
   232  	result.Inc()
   233  
   234  	return result
   235  }
   236  
   237  func (s *SchemaValidator) typeValidator() valueValidator {
   238  	return newTypeValidator(
   239  		s.Path,
   240  		s.in,
   241  		s.Schema.Type,
   242  		s.Schema.Nullable,
   243  		s.Schema.Format,
   244  		s.Options,
   245  	)
   246  }
   247  
   248  func (s *SchemaValidator) commonValidator() valueValidator {
   249  	return newBasicCommonValidator(
   250  		s.Path,
   251  		s.in,
   252  		s.Schema.Default,
   253  		s.Schema.Enum,
   254  		s.Options,
   255  	)
   256  }
   257  
   258  func (s *SchemaValidator) sliceValidator() valueValidator {
   259  	return newSliceValidator(
   260  		s.Path,
   261  		s.in,
   262  		s.Schema.MaxItems,
   263  		s.Schema.MinItems,
   264  		s.Schema.UniqueItems,
   265  		s.Schema.AdditionalItems,
   266  		s.Schema.Items,
   267  		s.Root,
   268  		s.KnownFormats,
   269  		s.Options,
   270  	)
   271  }
   272  
   273  func (s *SchemaValidator) numberValidator() valueValidator {
   274  	return newNumberValidator(
   275  		s.Path,
   276  		s.in,
   277  		s.Schema.Default,
   278  		s.Schema.MultipleOf,
   279  		s.Schema.Maximum,
   280  		s.Schema.ExclusiveMaximum,
   281  		s.Schema.Minimum,
   282  		s.Schema.ExclusiveMinimum,
   283  		"",
   284  		"",
   285  		s.Options,
   286  	)
   287  }
   288  
   289  func (s *SchemaValidator) stringValidator() valueValidator {
   290  	return newStringValidator(
   291  		s.Path,
   292  		s.in,
   293  		nil,
   294  		false,
   295  		false,
   296  		s.Schema.MaxLength,
   297  		s.Schema.MinLength,
   298  		s.Schema.Pattern,
   299  		s.Options,
   300  	)
   301  }
   302  
   303  func (s *SchemaValidator) formatValidator() valueValidator {
   304  	return newFormatValidator(
   305  		s.Path,
   306  		s.in,
   307  		s.Schema.Format,
   308  		s.KnownFormats,
   309  		s.Options,
   310  	)
   311  }
   312  
   313  func (s *SchemaValidator) schemaPropsValidator() valueValidator {
   314  	sch := s.Schema
   315  	return newSchemaPropsValidator(
   316  		s.Path, s.in, sch.AllOf, sch.OneOf, sch.AnyOf, sch.Not, sch.Dependencies, s.Root, s.KnownFormats,
   317  		s.Options,
   318  	)
   319  }
   320  
   321  func (s *SchemaValidator) objectValidator() valueValidator {
   322  	return newObjectValidator(
   323  		s.Path,
   324  		s.in,
   325  		s.Schema.MaxProperties,
   326  		s.Schema.MinProperties,
   327  		s.Schema.Required,
   328  		s.Schema.Properties,
   329  		s.Schema.AdditionalProperties,
   330  		s.Schema.PatternProperties,
   331  		s.Root,
   332  		s.KnownFormats,
   333  		s.Options,
   334  	)
   335  }
   336  
   337  func (s *SchemaValidator) redeem() {
   338  	pools.poolOfSchemaValidators.RedeemValidator(s)
   339  }
   340  
   341  func (s *SchemaValidator) redeemChildren() {
   342  	for i, validator := range s.validators {
   343  		if validator == nil {
   344  			continue
   345  		}
   346  		if redeemableChildren, ok := validator.(interface{ redeemChildren() }); ok {
   347  			redeemableChildren.redeemChildren()
   348  		}
   349  		if redeemable, ok := validator.(interface{ redeem() }); ok {
   350  			redeemable.redeem()
   351  		}
   352  		s.validators[i] = nil // free up allocated children if not in pool
   353  	}
   354  }
   355  

View as plain text