Closed nathan-rogers closed 1 month ago
Seems like a bug, anyOf works but not oneOf
Basic Test:
const fastify = require("fastify")({ logger: true });
const assert = require("assert");
// Define the route with the schema
fastify.get("/forms/form", {
schema: {
query: {
type: "object",
additionalProperties: false,
oneOf: [
{
properties: {
name: { type: "string" },
},
},
{
properties: {
id: { type: "string" },
},
},
],
},
},
handler: async (request, reply) => {
return { query: request.query };
},
});
fastify.setErrorHandler((error, request, reply) => {
console.error(error);
console.error(request.query);
});
// Test function
const runTest = async () => {
try {
await fastify.ready();
// Test with 'name' parameter
const response1 = await fastify.inject({
method: "GET",
url: "/forms/form?name=asdf",
});
assert.strictEqual(
response1.statusCode,
200,
"Expected status code 200 for name parameter",
);
console.log("Test passed: name parameter works as expected");
// Test with 'id' parameter
const response2 = await fastify.inject({
method: "GET",
url: "/forms/form?id=123",
});
assert.strictEqual(
response2.statusCode,
200,
"Expected status code 200 for id parameter",
);
console.log("Test passed: id parameter works as expected");
console.log("All tests passed successfully");
} catch (error) {
console.error("Test failed:", error);
} finally {
await fastify.close();
}
};
// Run the test
runTest();
It is more like the issue from ajv
. Note that the option here is not same with fastify
default, but it should success.
import Ajv from 'ajv'
const ajv = new Ajv()
const schema = {
oneOf: [
{ type: "object", properties: { name : { type: 'string' } } },
{ type: "object", properties: { id : { type: 'string' } } },
]
}
const schema2 = {
type: 'object',
properties: {
id : { type: 'string' },
name : { type: 'string' },
},
oneOf: [
{ required: ['id'] },
{ required: ['name'] },
]
}
const validate = ajv.compile(schema)
console.log(validate({ id: 'foo' })) // false
console.log(validate({ name: 'foo' })) // false
const validate2 = ajv.compile(schema2)
console.log(validate2({ id: 'foo' })) // true
console.log(validate2({ name: 'foo' })) // true
After digging the issue deeper, your schema inside oneOf
doesn't excluding each another.
The reason is that both schema ACCEPT extra properties which leads to failing the oneOf
check.
You should add additionalProperties
inside the sub-schema, and disable removeAdditional
.
const schema = {
oneOf: [
{ type: 'object', properties: { name: { type: 'string' } }, additionalProperties: false },
{ type: 'object', properties: { id: { type: 'string' } }, additionalProperties: false },
]
}
const fastify = Fastify({
ajv: {
customOptions: {
removeAdditional: false,
}
}
})
fastify.get('/', {
schema: {
querystring: schema
}
}, async function (request) {
return request.query
})
Or use required
properties as discriminator.
const schema = {
type: 'object',
properties: {
name: { type: 'string' },
id: { type: 'string' }
},
additionalProperties: false,
oneOf: [
{ required: ['name'] },
{ required: ['id'] }
]
}
const fastify = Fastify({})
fastify.get('/', {
schema: {
querystring: schema
}
}, async function (request) {
return request.query
})
Prerequisites
Fastify version
4.26.2
Plugin version
No response
Node.js version
20.11.1
Operating system
Linux
Operating system version (i.e. 20.04, 11.3, 10)
Ubuntu 22.04.4 LTS
Description
Schema
Request
http://localhost:3000/forms/form?name=asdf
Response
Link to code that reproduces the bug
No response
Expected Behavior
I expect the provided request example to pass validation, since I've provided
oneOf
eithername
orid
in thequerystring
. I don't know what else to say. If this isn't a bug, how do I do this correctly?