@@ -169,47 +169,48 @@ function invalidClassNameMessage(className) {
169169 return 'Invalid classname: ' + className + ', classnames can only have alphanumeric characters and _, and must start with an alpha character ' ;
170170}
171171
172- // Returns { error: "message", code: ### } if the type could not be
173- // converted, otherwise returns a returns { result: "mongotype" }
174- // where mongotype is suitable for inserting into mongo _SCHEMA collection
175- function schemaAPITypeToMongoFieldType ( type ) {
176- var invalidJsonError = { error : "invalid JSON" , code : Parse . Error . INVALID_JSON } ;
177- if ( type . type == 'Pointer' ) {
178- if ( ! type . targetClass ) {
179- return { error : 'type Pointer needs a class name' , code : 135 } ;
180- } else if ( typeof type . targetClass !== 'string' ) {
181- return invalidJsonError ;
182- } else if ( ! classNameIsValid ( type . targetClass ) ) {
183- return { error : invalidClassNameMessage ( type . targetClass ) , code : Parse . Error . INVALID_CLASS_NAME } ;
184- } else {
185- return { result : '*' + type . targetClass } ;
186- }
187- }
188- if ( type . type == 'Relation' ) {
189- if ( ! type . targetClass ) {
190- return { error : 'type Relation needs a class name' , code : 135 } ;
191- } else if ( typeof type . targetClass !== 'string' ) {
192- return invalidJsonError ;
193- } else if ( ! classNameIsValid ( type . targetClass ) ) {
194- return { error : invalidClassNameMessage ( type . targetClass ) , code : Parse . Error . INVALID_CLASS_NAME } ;
195- } else {
196- return { result : 'relation<' + type . targetClass + '>' } ;
197- }
198- }
199- if ( typeof type . type !== 'string' ) {
200- return { error : "invalid JSON" , code : Parse . Error . INVALID_JSON } ;
201- }
202- switch ( type . type ) {
203- default : return { error : 'invalid field type: ' + type . type , code : Parse . Error . INCORRECT_TYPE } ;
204- case 'Number' : return { result : 'number' } ;
205- case 'String' : return { result : 'string' } ;
206- case 'Boolean' : return { result : 'boolean' } ;
207- case 'Date' : return { result : 'date' } ;
208- case 'Object' : return { result : 'object' } ;
209- case 'Array' : return { result : 'array' } ;
210- case 'GeoPoint' : return { result : 'geopoint' } ;
211- case 'File' : return { result : 'file' } ;
212- }
172+ const invalidJsonError = new Parse . Error ( Parse . Error . INVALID_JSON , "invalid JSON" ) ;
173+ const validNonRelationOrPointerTypes = [
174+ 'Number' ,
175+ 'String' ,
176+ 'Boolean' ,
177+ 'Date' ,
178+ 'Object' ,
179+ 'Array' ,
180+ 'GeoPoint' ,
181+ 'File' ,
182+ ] ;
183+ // Returns an error suitable for throwing if the type is invalid
184+ const fieldTypeIsInvalid = ( { type, targetClass } ) => {
185+ if ( type == 'Pointer' ) {
186+ if ( ! targetClass ) {
187+ return new Parse . Error ( 135 , 'type Pointer needs a class name' ) ;
188+ } else if ( typeof targetClass !== 'string' ) {
189+ return invalidJsonError ;
190+ } else if ( ! classNameIsValid ( targetClass ) ) {
191+ return new Parse . Error ( Parse . Error . INVALID_CLASS_NAME , invalidClassNameMessage ( targetClass ) ) ;
192+ } else {
193+ return undefined ;
194+ }
195+ }
196+ if ( type == 'Relation' ) {
197+ if ( ! targetClass ) {
198+ return new Parse . Error ( 135 , 'type Relation needs a class name' ) ;
199+ } else if ( typeof targetClass !== 'string' ) {
200+ return invalidJsonError ;
201+ } else if ( ! classNameIsValid ( targetClass ) ) {
202+ return new Parse . Error ( Parse . Error . INVALID_CLASS_NAME , invalidClassNameMessage ( targetClass ) ) ;
203+ } else {
204+ return undefined ;
205+ }
206+ }
207+ if ( typeof type !== 'string' ) {
208+ return invalidJsonError ;
209+ }
210+ if ( ! _ . includes ( validNonRelationOrPointerTypes , type ) ) {
211+ return new Parse . Error ( Parse . Error . INCORRECT_TYPE , `invalid field type: ${ type } ` ) ;
212+ }
213+ return undefined ;
213214}
214215
215216// Stores the entire schema of the app in a weird hybrid format somewhere between
@@ -244,9 +245,8 @@ class Schema {
244245 // createdAt and updatedAt are wacky and have legacy baggage
245246 parseFormatSchema . createdAt = { type : 'String' } ;
246247 parseFormatSchema . updatedAt = { type : 'String' } ;
247- this . data [ schema . className ] = _ . mapValues ( parseFormatSchema , parseField =>
248- schemaAPITypeToMongoFieldType ( parseField ) . result
249- ) ;
248+ //Necessary because we still use the mongo type internally here :(
249+ this . data [ schema . className ] = _ . mapValues ( parseFormatSchema , MongoSchemaCollection . _DONOTUSEparseFieldTypeToMongoFieldType ) ;
250250
251251 this . perms [ schema . className ] = schema . classLevelPermissions ;
252252 } ) ;
@@ -397,72 +397,75 @@ class Schema {
397397 // The className must already be validated.
398398 // If 'freeze' is true, refuse to update the schema for this field.
399399 validateField ( className , fieldName , type , freeze ) {
400- // Just to check that the fieldName is valid
401- transform . transformKey ( this , className , fieldName ) ;
402-
403- if ( fieldName . indexOf ( "." ) > 0 ) {
404- // subdocument key (x.y) => ok if x is of type 'object'
405- fieldName = fieldName . split ( "." ) [ 0 ] ;
406- type = 'object' ;
407- }
400+ return this . reloadData ( ) . then ( ( ) => {
401+ // Just to check that the fieldName is valid
402+ transform . transformKey ( this , className , fieldName ) ;
403+
404+ if ( fieldName . indexOf ( "." ) > 0 ) {
405+ // subdocument key (x.y) => ok if x is of type 'object'
406+ fieldName = fieldName . split ( "." ) [ 0 ] ;
407+ type = 'object' ;
408+ }
408409
409- let expected = this . data [ className ] [ fieldName ] ;
410- if ( expected ) {
411- expected = ( expected === 'map' ? 'object' : expected ) ;
412- if ( expected === type ) {
413- return Promise . resolve ( this ) ;
414- } else {
415- throw new Parse . Error (
416- Parse . Error . INCORRECT_TYPE ,
417- `schema mismatch for ${ className } .${ fieldName } ; expected ${ expected } but got ${ type } `
418- ) ;
410+ let expected = this . data [ className ] [ fieldName ] ;
411+ if ( expected ) {
412+ expected = ( expected === 'map' ? 'object' : expected ) ;
413+ if ( expected === type ) {
414+ return Promise . resolve ( this ) ;
415+ } else {
416+ throw new Parse . Error (
417+ Parse . Error . INCORRECT_TYPE ,
418+ `schema mismatch for ${ className } .${ fieldName } ; expected ${ expected } but got ${ type } `
419+ ) ;
420+ }
419421 }
420- }
421422
422- if ( freeze ) {
423- throw new Parse . Error ( Parse . Error . INVALID_JSON , `schema is frozen, cannot add ${ fieldName } field` ) ;
424- }
423+ if ( freeze ) {
424+ throw new Parse . Error ( Parse . Error . INVALID_JSON , `schema is frozen, cannot add ${ fieldName } field` ) ;
425+ }
425426
426- // We don't have this field, but if the value is null or undefined,
427- // we won't update the schema until we get a value with a type.
428- if ( ! type ) {
429- return Promise . resolve ( this ) ;
430- }
427+ // We don't have this field, but if the value is null or undefined,
428+ // we won't update the schema until we get a value with a type.
429+ if ( ! type ) {
430+ return Promise . resolve ( this ) ;
431+ }
431432
432- if ( type === 'geopoint' ) {
433- // Make sure there are not other geopoint fields
434- for ( var otherKey in this . data [ className ] ) {
435- if ( this . data [ className ] [ otherKey ] === 'geopoint' ) {
436- throw new Parse . Error (
437- Parse . Error . INCORRECT_TYPE ,
438- 'there can only be one geopoint field in a class' ) ;
433+ if ( type === 'geopoint' ) {
434+ // Make sure there are not other geopoint fields
435+ for ( var otherKey in this . data [ className ] ) {
436+ if ( this . data [ className ] [ otherKey ] === 'geopoint' ) {
437+ throw new Parse . Error (
438+ Parse . Error . INCORRECT_TYPE ,
439+ 'there can only be one geopoint field in a class' ) ;
440+ }
439441 }
440442 }
441- }
442443
443- // We don't have this field. Update the schema.
444- // Note that we use the $exists guard and $set to avoid race
445- // conditions in the database. This is important!
446- let query = { } ;
447- query [ fieldName ] = { '$exists' : false } ;
448- var update = { } ;
449- update [ fieldName ] = type ;
450- update = { '$set' : update } ;
451- return this . _collection . upsertSchema ( className , query , update ) . then ( ( ) => {
452- // The update succeeded. Reload the schema
453- return this . reloadData ( ) ;
454- } , ( ) => {
455- // The update failed. This can be okay - it might have been a race
456- // condition where another client updated the schema in the same
457- // way that we wanted to. So, just reload the schema
458- return this . reloadData ( ) ;
459- } ) . then ( ( ) => {
460- // Ensure that the schema now validates
461- return this . validateField ( className , fieldName , type , true ) ;
462- } , ( error ) => {
463- // The schema still doesn't validate. Give up
464- throw new Parse . Error ( Parse . Error . INVALID_JSON ,
465- 'schema key will not revalidate' ) ;
444+ // We don't have this field. Update the schema.
445+ // Note that we use the $exists guard and $set to avoid race
446+ // conditions in the database. This is important!
447+ let query = { } ;
448+ query [ fieldName ] = { '$exists' : false } ;
449+ var update = { } ;
450+ update [ fieldName ] = type ;
451+ update = { '$set' : update } ;
452+ return this . _collection . upsertSchema ( className , query , update ) . then ( ( ) => {
453+ // The update succeeded. Reload the schema
454+ return this . reloadData ( ) ;
455+ } , ( ) => {
456+ // The update failed. This can be okay - it might have been a race
457+ // condition where another client updated the schema in the same
458+ // way that we wanted to. So, just reload the schema
459+ return this . reloadData ( ) ;
460+ } ) . then ( ( ) => {
461+ // Ensure that the schema now validates
462+ return this . validateField ( className , fieldName , type , true ) ;
463+ } , ( error ) => {
464+ // The schema still doesn't validate. Give up
465+ console . log ( error )
466+ throw new Parse . Error ( Parse . Error . INVALID_JSON ,
467+ 'schema key will not revalidate' ) ;
468+ } ) ;
466469 } ) ;
467470 }
468471
@@ -551,7 +554,6 @@ class Schema {
551554 // Every object has ACL implicitly.
552555 continue ;
553556 }
554-
555557 promise = thenValidateField ( promise , className , fieldName , expected ) ;
556558 }
557559 promise = thenValidateRequiredColumns ( promise , className , object , query ) ;
@@ -643,7 +645,7 @@ function load(collection) {
643645}
644646
645647// Returns { code, error } if invalid, or { result }, an object
646- // suitable for inserting into _SCHEMA collection, otherwise
648+ // suitable for inserting into _SCHEMA collection, otherwise.
647649function mongoSchemaFromFieldsAndClassNameAndCLP ( fields , className , classLevelPermissions ) {
648650 if ( ! classNameIsValid ( className ) ) {
649651 return {
@@ -652,7 +654,7 @@ function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPe
652654 } ;
653655 }
654656
655- for ( var fieldName in fields ) {
657+ for ( let fieldName in fields ) {
656658 if ( ! fieldNameIsValid ( fieldName ) ) {
657659 return {
658660 code : Parse . Error . INVALID_KEY_NAME ,
@@ -665,6 +667,8 @@ function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPe
665667 error : 'field ' + fieldName + ' cannot be added' ,
666668 } ;
667669 }
670+ const error = fieldTypeIsInvalid ( fields [ fieldName ] ) ;
671+ if ( error ) return { code : error . code , error : error . message } ;
668672 }
669673
670674 var mongoObject = {
@@ -675,19 +679,11 @@ function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPe
675679 } ;
676680
677681 for ( var fieldName in defaultColumns [ className ] ) {
678- var validatedField = schemaAPITypeToMongoFieldType ( defaultColumns [ className ] [ fieldName ] ) ;
679- if ( ! validatedField . result ) {
680- return validatedField ;
681- }
682- mongoObject [ fieldName ] = validatedField . result ;
682+ mongoObject [ fieldName ] = MongoSchemaCollection . _DONOTUSEparseFieldTypeToMongoFieldType ( defaultColumns [ className ] [ fieldName ] ) ;
683683 }
684684
685685 for ( var fieldName in fields ) {
686- var validatedField = schemaAPITypeToMongoFieldType ( fields [ fieldName ] ) ;
687- if ( ! validatedField . result ) {
688- return validatedField ;
689- }
690- mongoObject [ fieldName ] = validatedField . result ;
686+ mongoObject [ fieldName ] = MongoSchemaCollection . _DONOTUSEparseFieldTypeToMongoFieldType ( fields [ fieldName ] ) ;
691687 }
692688
693689 var geoPoints = Object . keys ( mongoObject ) . filter ( key => mongoObject [ key ] === 'geopoint' ) ;
@@ -845,7 +841,6 @@ export {
845841 load ,
846842 classNameIsValid ,
847843 invalidClassNameMessage ,
848- schemaAPITypeToMongoFieldType ,
849844 buildMergedSchemaObject ,
850845 systemClasses ,
851846 defaultColumns ,
0 commit comments