diff --git a/docs/api/databases/mongodb.md b/docs/api/databases/mongodb.md
index 341cf1c6fb..542f1dd3db 100644
--- a/docs/api/databases/mongodb.md
+++ b/docs/api/databases/mongodb.md
@@ -404,25 +404,30 @@ import { keywordObjectId } from '@feathersjs/mongodb'
const validator = new Ajv()
validator.addKeyword(keywordObjectId)
+```
+
+### ObjectIdSchema
+
+Both, `@feathersjs/typebox` and `@feathersjs/schema` export an `ObjectIdSchema` helper that creates a schema which can be both, a MongoDB ObjectId or a string that will be converted with the `objectid` keyword:
+
+```ts
+import { ObjectIdSchema } from '@feathersjs/typebox' // or '@feathersjs/schema'
const typeboxSchema = Type.Object({
- userId: Type.String({ objectid: true })
+ userId: ObjectIdSchema()
})
const jsonSchema = {
type: 'object',
properties: {
- userId: {
- type: 'string',
- objectid: true
- }
+ userId: ObjectIdSchema()
}
}
```
-Usually a converted object id property can be treated like a string but in some cases when working with it on the server you may have to call `toString()` to get the proper type.
+The `ObjectIdSchema` helper will only work when the [`objectid` AJV keyword](#ajv-keyword) is registered.
diff --git a/packages/generators/src/authentication/templates/schema.json.tpl.ts b/packages/generators/src/authentication/templates/schema.json.tpl.ts
index b4dc41f252..da7f2aa3c1 100644
--- a/packages/generators/src/authentication/templates/schema.json.tpl.ts
+++ b/packages/generators/src/authentication/templates/schema.json.tpl.ts
@@ -11,7 +11,12 @@ const template = ({
type,
relative
}: AuthenticationGeneratorContext) => /* ts */ `// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html
-import { resolve, querySyntax, getValidator } from '@feathersjs/schema'
+import { resolve, querySyntax, getValidator } from '@feathersjs/schema'${
+ type === 'mongodb'
+ ? `
+import { ObjectIdSchema } from '@feathersjs/schema'`
+ : ''
+}
import type { FromSchema } from '@feathersjs/schema'
${localTemplate(authStrategies, `import { passwordHash } from '@feathersjs/authentication-local'`)}
@@ -27,16 +32,7 @@ export const ${camelName}Schema = {
additionalProperties: false,
required: [ '${type === 'mongodb' ? '_id' : 'id'}'${localTemplate(authStrategies, ", 'email'")} ],
properties: {
- ${
- type === 'mongodb'
- ? `_id: {
- type: 'string',
- objectid: true
- },`
- : `id: {
- type: 'number'
- },`
- }
+ ${type === 'mongodb' ? `_id: ObjectIdSchema(),` : `id: { type: 'number' },`}
${authStrategies
.map((name) =>
name === 'local'
diff --git a/packages/generators/src/authentication/templates/schema.typebox.tpl.ts b/packages/generators/src/authentication/templates/schema.typebox.tpl.ts
index 0c80f0d527..909ec6ae5a 100644
--- a/packages/generators/src/authentication/templates/schema.typebox.tpl.ts
+++ b/packages/generators/src/authentication/templates/schema.typebox.tpl.ts
@@ -12,7 +12,12 @@ export const template = ({
relative
}: AuthenticationGeneratorContext) => /* ts */ `// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html
import { resolve } from '@feathersjs/schema'
-import { Type, getValidator, querySyntax } from '@feathersjs/typebox'
+import { Type, getValidator, querySyntax } from '@feathersjs/typebox'${
+ type === 'mongodb'
+ ? `
+import { ObjectIdSchema } from '@feathersjs/typebox'`
+ : ''
+}
import type { Static } from '@feathersjs/typebox'
${localTemplate(authStrategies, `import { passwordHash } from '@feathersjs/authentication-local'`)}
@@ -23,7 +28,7 @@ import { dataValidator, queryValidator } from '${relative}/${
// Main data model schema
export const ${camelName}Schema = Type.Object({
- ${type === 'mongodb' ? '_id: Type.String({ objectid: true })' : 'id: Type.Number()'},
+ ${type === 'mongodb' ? '_id: ObjectIdSchema()' : 'id: Type.Number()'},
${authStrategies
.map((name) =>
name === 'local'
diff --git a/packages/generators/src/service/templates/schema.json.tpl.ts b/packages/generators/src/service/templates/schema.json.tpl.ts
index 272ffcaf3b..20951e9d06 100644
--- a/packages/generators/src/service/templates/schema.json.tpl.ts
+++ b/packages/generators/src/service/templates/schema.json.tpl.ts
@@ -10,7 +10,12 @@ const template = ({
cwd,
lib
}: ServiceGeneratorContext) => /* ts */ `// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html
-import { resolve, getValidator, querySyntax } from '@feathersjs/schema'
+import { resolve, getValidator, querySyntax } from '@feathersjs/schema'${
+ type === 'mongodb'
+ ? `
+import { ObjectIdSchema } from '@feathersjs/schema'`
+ : ''
+}
import type { FromSchema } from '@feathersjs/schema'
import type { HookContext } from '${relative}/declarations'
@@ -25,19 +30,8 @@ export const ${camelName}Schema = {
additionalProperties: false,
required: [ '${type === 'mongodb' ? '_id' : 'id'}', 'text' ],
properties: {
- ${
- type === 'mongodb'
- ? `_id: {
- type: 'string',
- objectid: true
- },`
- : `id: {
- type: 'number'
- },`
- }
- text: {
- type: 'string'
- }
+ ${type === 'mongodb' ? `_id: ObjectIdSchema(),` : `id: { type: 'number' },`}
+ text: { type: 'string' }
}
} as const
export type ${upperName} = FromSchema
diff --git a/packages/generators/src/service/templates/schema.typebox.tpl.ts b/packages/generators/src/service/templates/schema.typebox.tpl.ts
index 7e8b90a5d8..66b6c38e26 100644
--- a/packages/generators/src/service/templates/schema.typebox.tpl.ts
+++ b/packages/generators/src/service/templates/schema.typebox.tpl.ts
@@ -11,7 +11,12 @@ const template = ({
lib
}: ServiceGeneratorContext) => /* ts */ `// // For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html
import { resolve } from '@feathersjs/schema'
-import { Type, getValidator, querySyntax } from '@feathersjs/typebox'
+import { Type, getValidator, querySyntax } from '@feathersjs/typebox'${
+ type === 'mongodb'
+ ? `
+import { ObjectIdSchema } from '@feathersjs/typebox'`
+ : ''
+}
import type { Static } from '@feathersjs/typebox'
import type { HookContext } from '${relative}/declarations'
@@ -21,7 +26,7 @@ import { dataValidator, queryValidator } from '${relative}/${
// Main data model schema
export const ${camelName}Schema = Type.Object({
- ${type === 'mongodb' ? '_id: Type.String({ objectid: true })' : 'id: Type.Number()'},
+ ${type === 'mongodb' ? '_id: ObjectIdSchema()' : 'id: Type.Number()'},
text: Type.String()
}, { $id: '${upperName}', additionalProperties: false })
export type ${upperName} = Static
diff --git a/packages/schema/src/hooks/validate.ts b/packages/schema/src/hooks/validate.ts
index 2ca30e7900..7b76a8c8ab 100644
--- a/packages/schema/src/hooks/validate.ts
+++ b/packages/schema/src/hooks/validate.ts
@@ -22,7 +22,7 @@ export const validateQuery = (schema: Schema | Valid
} catch (error: any) {
throw error.ajv ? new BadRequest(error.message, error.errors) : error
}
-
+
if (typeof next === 'function') {
return next()
}
diff --git a/packages/schema/src/json-schema.ts b/packages/schema/src/json-schema.ts
index b951e68e21..78771e6e5d 100644
--- a/packages/schema/src/json-schema.ts
+++ b/packages/schema/src/json-schema.ts
@@ -144,11 +144,6 @@ export const queryProperties = <
Object.keys(definitions).reduce((res, key) => {
const result = res as any
const definition = definitions[key]
- const { $ref } = definition as any
-
- if ($ref) {
- throw new Error(`Can not create query syntax schema for reference property '${key}'`)
- }
result[key] = queryProperty(definition as JSONSchemaDefinition, extensions[key as keyof T])
@@ -227,3 +222,11 @@ export const querySyntax = <
...props
} as const
}
+
+export const ObjectIdSchema = () =>
+ ({
+ anyOf: [
+ { type: 'string', objectid: true },
+ { type: 'object', properties: {}, additionalProperties: false }
+ ]
+ } as const)
diff --git a/packages/schema/test/json-schema.test.ts b/packages/schema/test/json-schema.test.ts
index 2e4450d925..a0bd2a7be5 100644
--- a/packages/schema/test/json-schema.test.ts
+++ b/packages/schema/test/json-schema.test.ts
@@ -1,23 +1,10 @@
import Ajv from 'ajv'
import assert from 'assert'
+import { ObjectId as MongoObjectId } from 'mongodb'
import { FromSchema } from '../src'
-import { queryProperties, querySyntax } from '../src/json-schema'
+import { querySyntax, ObjectIdSchema } from '../src/json-schema'
describe('@feathersjs/schema/json-schema', () => {
- it('queryProperties errors for unsupported query types', () => {
- assert.throws(
- () =>
- queryProperties({
- something: {
- $ref: 'something'
- }
- }),
- {
- message: "Can not create query syntax schema for reference property 'something'"
- }
- )
- })
-
it('querySyntax works with no properties', async () => {
const schema = {
type: 'object',
@@ -69,4 +56,27 @@ describe('@feathersjs/schema/json-schema', () => {
assert.ok(validator(q))
})
+
+ // Test ObjectId validation
+ it('ObjectId', async () => {
+ const schema = {
+ type: 'object',
+ properties: {
+ _id: ObjectIdSchema()
+ }
+ }
+
+ const validator = new Ajv({
+ strict: false
+ }).compile(schema)
+ const validated = await validator({
+ _id: '507f191e810c19729de860ea'
+ })
+ assert.ok(validated)
+
+ const validated2 = await validator({
+ _id: new MongoObjectId()
+ })
+ assert.ok(validated2)
+ })
})
diff --git a/packages/typebox/src/index.ts b/packages/typebox/src/index.ts
index eaabd2b907..3a0d90fb23 100644
--- a/packages/typebox/src/index.ts
+++ b/packages/typebox/src/index.ts
@@ -129,10 +129,6 @@ export const queryProperties = <
const result = res as any
const value = definition.properties[key]
- if (value.$ref) {
- throw new Error(`Can not create query syntax schema for reference property '${key}'`)
- }
-
result[key] = queryProperty(value, extensions[key])
return result
@@ -182,3 +178,6 @@ export const querySyntax = <
options
)
}
+
+export const ObjectIdSchema = () =>
+ Type.Union([Type.String({ objectid: true }), Type.Object({}, { additionalProperties: false })])
diff --git a/packages/typebox/test/index.test.ts b/packages/typebox/test/index.test.ts
index 5cc19f58ac..6d70a6bc19 100644
--- a/packages/typebox/test/index.test.ts
+++ b/packages/typebox/test/index.test.ts
@@ -1,4 +1,5 @@
import assert from 'assert'
+import { ObjectId as MongoObjectId } from 'mongodb'
import { Ajv } from '@feathersjs/schema'
import {
querySyntax,
@@ -7,7 +8,7 @@ import {
defaultAppConfiguration,
getDataValidator,
getValidator,
- queryProperties
+ ObjectIdSchema
} from '../src'
describe('@feathersjs/schema/typebox', () => {
@@ -39,20 +40,6 @@ describe('@feathersjs/schema/typebox', () => {
assert.ok(!validated)
})
- it('queryProperties errors for unsupported query types', () => {
- assert.throws(
- () =>
- queryProperties(
- Type.Object({
- something: Type.Ref(Type.Object({}, { $id: 'something' }))
- })
- ),
- {
- message: "Can not create query syntax schema for reference property 'something'"
- }
- )
- })
-
it('querySyntax works with no properties', async () => {
const schema = querySyntax(Type.Object({}))
@@ -113,6 +100,26 @@ describe('@feathersjs/schema/typebox', () => {
assert.ok(validated)
})
+ // Test ObjectId validation
+ it('ObjectId', async () => {
+ const schema = Type.Object({
+ _id: ObjectIdSchema()
+ })
+
+ const validator = new Ajv({
+ strict: false
+ }).compile(schema)
+ const validated = await validator({
+ _id: '507f191e810c19729de860ea'
+ })
+ assert.ok(validated)
+
+ const validated2 = await validator({
+ _id: new MongoObjectId()
+ })
+ assert.ok(validated2)
+ })
+
it('validators', () => {
assert.strictEqual(typeof getDataValidator(Type.Object({}), new Ajv()), 'object')
assert.strictEqual(typeof getValidator(Type.Object({}), new Ajv()), 'function')