Skip to content

Commit e8f91dc

Browse files
committed
feat: improved oneOf support
Thanks to @znewsham for initial work on this fixes #428 fixes #410 fixes #112 fixes #68
1 parent e356fcd commit e8f91dc

File tree

7 files changed

+1096
-415
lines changed

7 files changed

+1096
-415
lines changed

src/SimpleSchema.ts

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,33 @@ class SimpleSchema {
289289
return keySchema
290290
}
291291

292+
/**
293+
* @param key One specific or generic key for which to get all possible schemas.
294+
* @returns An potentially empty array of possible definitions for one key
295+
*
296+
* Note that this returns the raw, unevaluated definition object. Use `getDefinition`
297+
* if you want the evaluated definition, where any properties that are functions
298+
* have been run to produce a result.
299+
*/
300+
schemas (key: string): StandardSchemaKeyDefinition[] {
301+
const schemas: StandardSchemaKeyDefinition[] = []
302+
303+
const genericKey = MongoObject.makeKeyGeneric(key)
304+
const keySchema = genericKey == null ? null : this._schema[genericKey]
305+
if (keySchema != null) schemas.push(keySchema)
306+
307+
// See if it's defined in any subschema
308+
this.forEachAncestorSimpleSchema(
309+
key,
310+
(simpleSchema, ancestor, subSchemaKey) => {
311+
const keyDef = simpleSchema.schema(subSchemaKey)
312+
if (keyDef != null) schemas.push(keyDef)
313+
}
314+
)
315+
316+
return schemas
317+
}
318+
292319
/**
293320
* @returns {Object} The entire schema object with subschemas merged. This is the
294321
* equivalent of what schema() returned in SimpleSchema < 2.0
@@ -329,9 +356,45 @@ class SimpleSchema {
329356
propList?: string[] | null,
330357
functionContext: Record<string, unknown> = {}
331358
): StandardSchemaKeyDefinitionWithSimpleTypes | undefined {
332-
const defs = this.schema(key)
333-
if (defs == null) return
359+
const schemaKeyDefinition = this.schema(key)
360+
if (schemaKeyDefinition == null) return
361+
return this.resolveDefinitionForSchema(key, schemaKeyDefinition, propList, functionContext)
362+
}
363+
364+
/**
365+
* Returns the evaluated definition for one key in the schema
366+
*
367+
* @param key Generic or specific schema key
368+
* @param [propList] Array of schema properties you need; performance optimization
369+
* @param [functionContext] The context to use when evaluating schema options that are functions
370+
* @returns The schema definition for the requested key
371+
*/
372+
getDefinitions (
373+
key: string,
374+
propList?: string[] | null,
375+
functionContext: Record<string, unknown> = {}
376+
): StandardSchemaKeyDefinitionWithSimpleTypes[] {
377+
const schemaKeyDefinitions = this.schemas(key)
378+
return schemaKeyDefinitions.map((def) => {
379+
return this.resolveDefinitionForSchema(key, def, propList, functionContext)
380+
})
381+
}
334382

383+
/**
384+
* Resolves the definition for one key in the schema
385+
*
386+
* @param key Generic or specific schema key
387+
* @param schemaKeyDefinition Unresolved definition as returned from simpleSchema.schema()
388+
* @param [propList] Array of schema properties you need; performance optimization
389+
* @param [functionContext] The context to use when evaluating schema options that are functions
390+
* @returns The schema definition for the requested key
391+
*/
392+
resolveDefinitionForSchema (
393+
key: string,
394+
schemaKeyDefinition: StandardSchemaKeyDefinition,
395+
propList?: string[] | null,
396+
functionContext: Record<string, unknown> = {}
397+
): StandardSchemaKeyDefinitionWithSimpleTypes {
335398
const getPropIterator = (obj: Record<string, any>, newObj: Record<string, any>) => {
336399
return (prop: string): void => {
337400
if (Array.isArray(propList) && !propList.includes(prop)) return
@@ -362,11 +425,11 @@ class SimpleSchema {
362425
type: []
363426
}
364427

365-
Object.keys(defs).forEach(getPropIterator(defs, result))
428+
Object.keys(schemaKeyDefinition).forEach(getPropIterator(schemaKeyDefinition, result))
366429

367430
// Resolve all the types and convert to a normal array to make it easier to use.
368-
if (Array.isArray(defs.type?.definitions)) {
369-
result.type = defs.type.definitions.map((typeDef) => {
431+
if (Array.isArray(schemaKeyDefinition.type?.definitions)) {
432+
result.type = schemaKeyDefinition.type.definitions.map((typeDef) => {
370433
const newTypeDef: SchemaKeyDefinitionWithOneType = {
371434
type: String // will be overwritten
372435
}

src/ValidationContext.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import MongoObject from 'mongo-object'
33
import doValidation from './doValidation.js'
44
import { SimpleSchema } from './SimpleSchema.js'
55
import { CleanOptions, ObjectToValidate, ValidationError, ValidationOptions } from './types.js'
6+
import { looksLikeModifier } from './utility/index.js'
67

78
export default class ValidationContext {
89
public name?: string
@@ -88,6 +89,17 @@ export default class ValidationContext {
8889
upsert: isUpsert = false
8990
}: ValidationOptions = {}
9091
): boolean {
92+
// First do some basic checks of the object, and throw errors if necessary
93+
if (obj == null || (typeof obj !== 'object' && typeof obj !== 'function')) {
94+
throw new Error('The first argument of validate() must be an object')
95+
}
96+
97+
if (!isModifier && looksLikeModifier(obj)) {
98+
throw new Error(
99+
'When the validation object contains mongo operators, you must set the modifier option to true'
100+
)
101+
}
102+
91103
const validationErrors = doValidation({
92104
extendedCustomContext,
93105
ignoreTypes,

0 commit comments

Comments
 (0)