Skip to content

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

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions