import * as Promise from 'bluebird' ; import { ISpec , ISchema } from '../specs' ; import { IValidatorConfig } from '../validator-config' ; import { extendAllAllOfs } from '../helpers/allOf' ; import { loadSchema } from '../helpers/loader' ; import { pushError } from '../helpers/pushError' ; import { getTraceString , ITraceStep , ITypeValidationError , IValidationError , ValidationErrorType } from '../result' ; import { validateModel } from './ModelValidator' ; interface ISwaggerProperties { [ propertyName ] : ISchema ; } interface INamedSchema { name : string ; schema : ISchema ; } export interface ISchemaWithNullable extends ISchema { 'x-nullable' ? : boolean ; } export function validateObject ( test , schema , spec , config , trace ) { if ( schema === undefined || spec === undefined ) { throw new Error ( 'Schema or spec is not defined' ) ; } if ( config . allowXNullable === true && test === null && schema [ 'x-nullable' ] === true ) { return Promise . resolve ( [ ] ) ; } if ( ! test || ! ( test instanceof Object ) ) { let typeIs = typeof ( test ) ; if ( Array . isArray ( test ) ) { typeIs = 'array' ; } if ( test === null ) { typeIs = 'null' ; } let typeShouldBe = schema . title ? schema . title : 'object' ; return Promise . resolve ( [ < ITypeValidationError > { trace : trace , errorType : ValidationErrorType . TYPE_MISMATCH , typeIs : typeIs , typeShouldBe : typeShouldBe , } ] ) ; } let childValidation = [ ] ; let errors = [ ] ; const poly = schema . discriminator && schema . properties [ schema . discriminator ] ; let concreteClass = poly ? findPolymorphicConcreteClass ( schema , trace , test , config , spec ) : extendAllAllOfs ( schema , config , spec ) ; return concreteClass . then ( schema => { if ( schema . required ) { schema . required . forEach ( ( propertyName ) => { if ( test [ propertyName ] === undefined ) { let newTrace = JSON . parse ( JSON . stringify ( trace ) ) ; newTrace . push ( { stepName : propertyName } ) ; pushError ( { errorType : ValidationErrorType . MISSING_REQUIRED_PROPERTY , trace : newTrace } , errors , test , schema , spec , config ) ; } } ) ; } for ( let propertyName in test ) { if ( test . hasOwnProperty ( propertyName ) ) { let currentPropertyTest = test [ propertyName ] ; let currentPropertySchema ; if ( schema . properties && schema . properties [ propertyName ] ) { currentPropertySchema = schema . properties [ propertyName ] ; } else if ( schema . additionalProperties ) { let properties = < ISwaggerProperties > ( < any > schema . additionalProperties ) ; currentPropertySchema = < ISchema > properties [ propertyName ] ; } let newTrace = JSON . parse ( JSON . stringify ( trace ) ) ; newTrace . push ( { stepName : propertyName } ) ; if ( ! currentPropertySchema ) { if ( config . allowAdditionalProperties === true ) { } else if ( ! schema . additionalProperties ) { pushError ( { errorType : ValidationErrorType . ADDITIONAL_PROPERTY , trace : newTrace } , errors , test , schema , spec , config ) ; } else { childValidation . push ( validateModel ( currentPropertyTest , schema . additionalProperties , spec , config , newTrace ) ) ; } } else { childValidation . push ( validateModel ( currentPropertyTest , currentPropertySchema , spec , config , newTrace ) ) ; } } } if ( childValidation ) { return Promise . all ( childValidation ) . then ( childErrors => childErrors . reduce ( ( a , b ) => { return a . concat ( b ) ; } , errors ) ) ; } return Promise . resolve ( errors ) ; } ) ; } function isDerived ( possibleChild , parent , config , spec ) { let allOf = ( < any > possibleChild ) . _allOf ; if ( ! allOf ) { return Promise . resolve ( false ) ; } return Promise . all ( allOf . map ( ( childParent ) => { return loadSchema ( childParent , spec , config ) . then ( childParent => parent === childParent ) ; } ) ) . then ( isOneDerived => isOneDerived . filter ( derived => derived ) . length !== 0 ) ; } function findDerivedObjects ( abstractClass , config , spec ) { const discriminatingFeature = abstractClass . discriminator ; return Promise . resolve ( Object . getOwnPropertyNames ( spec . definitions ) ) . map ( ( name ) => { return < INamedSchema > { name : name , schema : spec . definitions [ name ] } ; } ) . map ( ( namedSchema ) => { return extendAllAllOfs ( namedSchema . schema , config , spec ) . then ( schema => { return < INamedSchema > { name : namedSchema . name , schema : schema } ; } ) ; } ) . filter ( ( namedSchema ) => { const schema = namedSchema . schema ; const name = namedSchema . name ; if ( ! schema . allOf || ! schema . properties [ discriminatingFeature ] ) { return false ; } const enumPoly = schema . properties [ discriminatingFeature ] . enum ; if ( enumPoly ) { return schema . properties [ discriminatingFeature ] . enum . filter ( e => abstractClass . properties [ discriminatingFeature ] . enum . indexOf ( '' + e ) !== - 1 ) . length > 0 ; } return true ; } ) . filter ( ( namedSchema ) => { return Promise . resolve ( namedSchema . schema . allOf ) . map ( ref => loadSchema ( ref , spec , config ) ) . filter ( ( parent ) => { return JSON . stringify ( parent ) === JSON . stringify ( abstractClass ) ; } ) . then ( parents => parents . length > 0 ) ; } ) ; } function findPolymorphicConcreteClass ( abstractClass , trace , test , config , spec ) { let derivedObjects = findDerivedObjects ( abstractClass , config , spec ) ; return derivedObjects . then ( derivedObjects => { if ( derivedObjects . length === 0 ) { return Promise . reject ( `Confusing discriminator - Schema uses discriminator but there are no other Schemas extending it via allOf and using this discriminator property. Trace: ${ getTraceString ( trace ) }` ) ; } return derivedObjects ; } ) . filter ( ( derivedObject ) => { if ( derivedObject . schema . properties [ abstractClass . discriminator ] . enum && derivedObject . schema . properties [ abstractClass . discriminator ] . enum . length === 1 && derivedObject . schema . properties [ abstractClass . discriminator ] . enum [ 0 ] === test [ abstractClass . discriminator ] ) { return true ; } return test [ abstractClass . discriminator ] === derivedObject . name ; } ) . then ( derivedObjects => { if ( derivedObjects . length === 0 ) { return Promise . reject ( `Polymorphism Error: No concrete object found. Trace: ${ getTraceString ( trace ) }` ) ; } if ( derivedObjects . length > 1 ) { return Promise . reject ( `Polymorphism Error: More than one concrete object found. Trace: ${ getTraceString ( trace ) }` ) ; } const derivedObject = derivedObjects [ 0 ] ; trace [ trace . length - 1 ] . concreteModel = derivedObject . name ; if ( derivedObject . schema . discriminator ) { trace . push ( { stepName : derivedObject . name } ) ; return findPolymorphicConcreteClass ( derivedObject . schema , trace , test , config , spec ) ; } return derivedObject . schema ; } ) ; }