Skip to content

Commit

Permalink
Support schema references
Browse files Browse the repository at this point in the history
  • Loading branch information
greguz committed Oct 3, 2024
1 parent e9a6928 commit 71854fb
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 31 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ jobs:

strategy:
matrix:
node-version: [16, 18, 20]
node-version: [18, 20, 22]
os: [ubuntu-latest, windows-latest, macOS-latest]

steps:
- uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License

Copyright 2023 Giacomo Gregoletto
Copyright 2024 Giacomo Gregoletto

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ fastify.register(noAdditionalProperties, {
* If true, update all response schemas.
* @default false
*/
response: false
response: false,
/**
* If true, update schemas registered **AFTER** this plugin registration.
* @default false
*/
ref: false
})

// From now on, all registered routes will have additionalProperties: false by default.
Expand Down
5 changes: 5 additions & 0 deletions fnap.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export interface FastifyNoAdditionalPropertiesOptions {
* @default false
*/
response?: boolean
/**
* If true, update schemas registered **AFTER** this plugin registration.
* @default false
*/
ref?: boolean
}

declare const plugin: FastifyPluginCallback<FastifyNoAdditionalPropertiesOptions>
Expand Down
57 changes: 37 additions & 20 deletions fnap.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import plugin from 'fastify-plugin'

function isObjectLike (value) {
return typeof value === 'object' && value !== null
}

function mapValues (object, iteratee) {
const result = {}
for (const key of Object.keys(object)) {
Expand All @@ -11,7 +15,7 @@ function mapValues (object, iteratee) {
function updateSchema (data) {
if (Array.isArray(data)) {
return data.map(updateSchema)
} else if (typeof data === 'object' && data !== null) {
} else if (isObjectLike(data)) {
const result = mapValues(data, updateSchema)
if (isObjectType(result)) {
result.additionalProperties = result.additionalProperties || false
Expand All @@ -28,35 +32,48 @@ function isObjectType ({ type }) {
: type === 'object'
}

function fnap (fastify, options, callback) {
options = Object.assign(
{
body: true,
headers: false,
params: false,
query: true,
response: false
},
options
)
function fnap (fastify, options, done) {
const opts = {
body: true,
headers: false,
params: false,
query: true,
ref: false,
response: false,
...options
}

if (opts.ref) {
// This will lead to a bad idea :D
const addSchemaBound = fastify.addSchema.bind(fastify)

// ...return to monke
fastify.addSchema = function fnapAddSchema (schema) {
return addSchemaBound(
isObjectLike(schema)
? updateSchema(schema.valueOf())
: schema
)
}
}

fastify.addHook('onRoute', route => {
if (route.schema) {
if (route.schema.body && options.body) {
if (isObjectLike(route.schema)) {
if (opts.body && isObjectLike(route.schema.body)) {
route.schema.body = updateSchema(route.schema.body.valueOf())
}
if (route.schema.headers && options.headers) {
if (opts.headers && isObjectLike(route.schema.headers)) {
route.schema.headers = updateSchema(route.schema.headers.valueOf())
}
if (route.schema.params && options.params) {
if (opts.params && isObjectLike(route.schema.params)) {
route.schema.params = updateSchema(route.schema.params.valueOf())
}
if (route.schema.querystring && options.query) {
if (opts.query && isObjectLike(route.schema.querystring)) {
route.schema.querystring = updateSchema(
route.schema.querystring.valueOf()
)
}
if (route.schema.response && options.response) {
if (opts.response && isObjectLike(route.schema.response)) {
route.schema.response = mapValues(
route.schema.response,
schema => updateSchema(schema.valueOf())
Expand All @@ -65,10 +82,10 @@ function fnap (fastify, options, callback) {
}
})

callback()
done()
}

export default plugin(fnap, {
fastify: '>=3.0.0',
fastify: '>=5.0.0',
name: 'fastify-no-additional-properties'
})
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@
"fnap.d.ts"
],
"dependencies": {
"fastify-plugin": "^4.5.1"
"fastify-plugin": "^5.0.1"
},
"devDependencies": {
"ava": "^6.0.0",
"c8": "^8.0.1",
"fastify": "^4.24.3",
"fluent-json-schema": "^4.2.1",
"rollup": "^4.6.1",
"standard": "^17.1.0"
"ava": "^6.1.3",
"c8": "^10.1.2",
"fastify": "^5.0.0",
"fluent-json-schema": "^5.0.0",
"rollup": "^4.24.0",
"standard": "^17.1.2"
},
"ava": {
"environmentVariables": {
Expand Down
50 changes: 50 additions & 0 deletions test/ref.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import test from 'ava'
import Fastify from 'fastify'

import noAdditionalProperties from '../fnap.mjs'

test('ref', async t => {
t.plan(2)

const fastify = Fastify()
t.teardown(() => fastify.close())

await fastify.register(noAdditionalProperties, {
body: true,
ref: true
})

fastify.addSchema({
$id: 'http://example.com/',
type: 'object',
properties: {
value: {
type: 'integer'
}
}
})

fastify.route({
method: 'POST',
url: '/foo',
schema: {
body: {
$ref: 'http://example.com/#'
}
},
handler (request, reply) {
reply.send(request.body)
}
})

const response = await fastify.inject({
method: 'POST',
url: '/foo',
payload: {
value: 42,
hello: 'world'
}
})
t.like(response, { statusCode: 200 })
t.deepEqual(JSON.parse(response.payload), { value: 42 })
})

0 comments on commit 71854fb

Please sign in to comment.