Skip to content

keywordObjectId throws instead of returning false, breaking anyOf / union validation #3685

@max-sistemich-kisters

Description

@max-sistemich-kisters

Package

@feathersjs/mongodb — specifically src/converters.ts, the keywordObjectId export.

Description

The keywordObjectId AJV keyword throws an Error when the value is not a valid ObjectId string:

return function (value: string, obj: any) {
  const { parentData, parentDataProperty } = obj
  try {
    parentData[parentDataProperty] = new ObjectId(value)
    return true
  } catch (error) {
    throw new Error(`invalid objectid for property "${parentDataProperty}"`)
  }
}

This breaks AJV's anyOf / oneOf evaluation. When a schema uses a union type like:

{
  "anyOf": [
    { "type": "string", "objectid": true },
    { "type": "null" }
  ]
}

AJV is supposed to try each branch and succeed if any passes. But because the keyword throws instead of returning false, AJV cannot gracefully fall through to the { "type": "null" } branch — the exception propagates up the call stack.

Steps to reproduce

  1. Register keywordObjectId on an AJV instance with coerceTypes: true and useDefaults: true
  2. Define a schema with a nullable objectid field:
    const schema = Type.Object({
      refId: Type.Union([Type.String({ objectid: true }), Type.Null()], { default: null }),
    })
  3. Validate an object where refId is missing (so the default null is applied)
  4. Expected: Validation passes — null matches the Type.Null() branch
  5. Actual: Throws Error: invalid objectid for property "refId"

Root cause

With coerceTypes: true, AJV attempts to coerce null into a string when evaluating the first anyOf branch. The coerced value (empty string) reaches the objectid keyword, which calls new ObjectId(""), catches the error, and re-throws instead of returning false.

AJV keyword validators must return false (not throw) to signal failure, otherwise AJV cannot continue evaluating alternative branches.

Suggested fix

 return function (value: string, obj: any) {
   const { parentData, parentDataProperty } = obj
   try {
     parentData[parentDataProperty] = new ObjectId(value)
     return true
   } catch (error) {
-    throw new Error(`invalid objectid for property "${parentDataProperty}"`)
+    return false
   }
 }

This allows AJV to produce a standard validation error (referencing the keyword) rather than an uncaught exception, and correctly supports anyOf / oneOf unions with nullable objectid fields.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions