Use fastify json schema validation in arbitrary functions - fastify

Fastify has some really awesome json schema support. (Link)
However, I now want to use the schemas which I added with fastify.addSchema(..) inside my business logic as well. For example (pseudo code):
schema = fastify.getSchema("schema1")
if (schema.validate(data)) {
console.log("ok");
} else {
console.log("not ok");
}
How can I achieve that?

Right now, in Fastify, one route has a set of validation functions.
These functions exist only because you set them in the { schema: {} } route
configuration.
So, in the first place, if you don't set those schemas in a route, you will be not able to access them.
The getSchema function retrieves the schema object, not che compiled function.
The relation is not 1:1 because a validation function may use more schemas via the $ref keyword.
The only way to archive what you need is to monkey patch the internal Fastify (highly discouraged)
Or open a feature request to the project.
Here an example, and as you can see, you are limited to get the route's validation functions inside the route's context.
So, it is far from being a flexible usage.
const fastify = require('fastify')({ logger: true })
const {
kSchemaBody: bodySchema
} = require('fastify/lib/symbols')
fastify.post('/', {
schema: {
body: {
$id: '#schema1',
type: 'object',
properties: {
bar: { type: 'number' }
}
}
}
}, async (request, reply) => {
const schemaValidator = request.context[bodySchema]
const result = schemaValidator({ bar: 'not a number' })
if (result) {
return true
}
return schemaValidator.errors
})
fastify.inject({
method: 'POST',
url: '/',
payload: {
bar: 33
}
}, (err, res) => {
console.log(res.json())
})

Related

I have a Joi schema and I want that when I do a POST method the id is included and required and with PUT method the id is not taken into account

I have a schema joi object:
const commonFields = {
id: joi.string().required(),
name: joi.string().required().min(5).max(50),
nif: joi.string().length(9).required(),
diet: joi.bool().required().strict(),
vegetarian: joi.bool().required().strict(),
};
const aluno = joi.object({
...commonFields,
num: joi
.string()
.required()
.regex(/^\d{1,4}\/\d{2}$/),
regime: joi.string().required().valid("externo", "interno"),
});
(...)
When i make a PUT request to update a user i want to ignore the field "id" but when i make a POST request i want to make it required.
I tried the following:
In schema I added alter() to the field "id":
id: joi.string().alter({
post: (schema) => schema.required(),
put: (schema) => schema.forbidden(),
}),
And in my functions i did this:
async function updateUser(req, res, type, db) {
try {
const { error } = requestValidation[type].validate(req.body, {
context: { method: req.method }
});
if (error) {
return res.status(400).send({ error: error.message });
}
const id = req.params.id;
const { name, nif, vegetarian, diet } = req.body;
(..)
But when i call this function in my PUT endpoint to update a user and add the field id into the requesition body it doesn't throw an error like it should throw. The response should be like this when i add the id to the body:
{
"error": "\"id\" is not allowed"
}
I want to ignore the id because I want to receive it by req.params.id.
I may not be doing the best way but I'm open to new suggestions!
The documentation has an example using tailor with alter
const { error } = requestValidation[type].tailor(req.method.toLowerCase()).validate(req.body, {
context: { method: req.method }
});

How to get random records from Strapi v4 ? (I answered this question)

Strapi doesn't have any endpoint to get random data for this purpose you should write some custom code for your endpoint
custom route for that endpoint you want
// path: ./src/api/[your-endpiont]/routes/[custom-route].js
module.exports = {
"routes": [
{
"method": "GET",
"path": "/[your-endpiont]/random", // you can define everything you want for url endpoint
"handler": "[your-endpiont].random", // random is defined as a method
"config": {
"policies": []
}
}
]
}
now you have to run yarn develop or npm ... to display a random method in your strapi panel
Save this setting and retry to reach the random endpoint.
create a function as a service for getting random data in your endpoint API services.
// path: ./src/api/[your-endpiont]/services/[your-endpiont].js
'use strict';
/**
* news-list service.
*/
const { createCoreService } = require('#strapi/strapi').factories;
module.exports = createCoreService('api::news-list.news-list', ({ strapi }) => ({
async serviceGetRandom({ locale, id_nin }) { // these parametrs come from query
function getRandomElementsFromArray(array, numberOfRandomElementsToExtract = 1) {
const elements = [];
function getRandomElement(arr) {
if (elements.length < numberOfRandomElementsToExtract) {
const index = Math.floor(Math.random() * arr.length)
const element = arr.splice(index, 1)[0];
elements.push(element)
return getRandomElement(arr)
} else {
return elements
}
}
return getRandomElement([...array])
}
const newsListArray = await strapi
.db
.query("api::news-list.news-list")
.findMany({
where: {
locale: locale, // if you have multi-language data
$not: {
id: id_nin, // depend on where this endpoint API use
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
},
//? filter object throws an error when you used populate object, everything you want to filter properly best write into where{}
// filters: {
// publishedAt: {
// $notNull: true,
// },
// locale: locale
// }
})
if (!newsListArray.length) {
return null
}
return getRandomElementsFromArray(newsListArray, 2)
}
}));
explain code:
Strapi provides a Query Engine API to interact with the database layer at a lower level
strapi.db.query("api::news-list.news-list").findMany({})
The Query Engine allows operations on database entries,
I wrote this for my purpose probably you should change based on what you needed
{
where: {
locale: locale,
$not: {
id: id_nin
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
}
}
when you get data from your query, passed it to that function getRandomElementsFromArray(newsListArray, 2) to get some random item (how many random items do you want ? pass the second parameter)
At least if your array is null return null otherwise return data
create the controller
Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested route so we going to call our services in this section
// path: ./src/api/[your-endpoint]/controllers/[your-endpoint].js
'use strict';
/**
* news-list controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::news-list.news-list', ({ strapi }) => ({
async random(ctx) { // name of this methods related to something we define in route ("handler": "[your-endpiont].random",)
const entity = await strapi.service('api::news-list.news-list').serviceGetRandom(ctx.query) // call our services, you can send all query you get from url endpoint (notice that you should write your endpoint api in strapi.service("your-endpoint"))
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return this.transformResponse(sanitizedEntity);
// console.log(entity);
}
}));
I call this endpoint in my project nextjs & stapi cms
export const getRandomNewsItem = (id, locale) => {
return API
.get(`/news-list/random?locale=${locale}&id_nin=${id}`)
.then(res => res.data);
};
That's it, I'll hope you all get what to do
all resources you need
https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#creating-custom-routers
https://docs.strapi.io/developer-docs/latest/development/backend-customization/services.html#implementation
https://docs.strapi.io/developer-docs/latest/development/backend-customization/controllers.html#adding-a-new-controller
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#and
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/populating.html

Gatsby: Inject a field in a graphql query before executing it within a plugin

So my use case seems very simple yet I'm struggling to figure out how can I do it.
In essence, I want to develop a gatsby-plugin that modifies all contentful-related graphQL queries to always insert the contentful_id so the returned data always contains that field. This way consumers of my plugin won't have to add contenful_id field in all of their grapqhQL queries.
Is this even doable? I'm not interested in creating fields as I believe they won't be part of the returned data unless you add that field explicitly.
The way to do it:
Use graphql SDK to visit the nodes. Define a visitor like this:
const { print, visit, parse } = require('graphql');
const visitor = {
SelectionSet(node, key, parent) {
if (!isQuery(parent) && !isFragment(parent)) {
node.selections.push({
kind: 'Field',
name: { kind: 'Name', value: 'yourFieldName' },
});
}
},
};
function isQuery(node) {
return node.kind === 'OperationDefinition' && node.operation === 'query';
}
function isFragment(node) {
return node.kind === 'FragmentDefinition';
}
Then you visit and print it back to a string:
const result = visit(parse(queryAST), { enter: visitor });
return print(result);
Last step is to add the desired field to all nodes (otherwise the field will not exist and your tampered queries won't work). You can achieve this attaching to this event:
exports.setFieldsOnGraphQLNodeType = () => {
return {
yourFieldName: {
type: GraphQLString,
resolve: (source) => {
return source.contentful_id || '';
},
},
};
};

How to test Validation pipe is throwing the expect error for improperly shaped request on NestJS

I'm using NestJS 7.0.2 and have globally enabled validation pipes via app.useGlobalPipes(new ValidationPipe());.
I'd like to be able to have a unit test that verifies that errors are being thrown if the improperly shaped object is provided, however the test as written still passes. I've seen that one solution is to do this testing in e2e via this post, but I'm wondering if there is anything I'm missing that would allow me to do this in unit testing.
I have a very simple controller with a very simple DTO.
Controller
async myApi(#Body() myInput: myDto): Promise<myDto | any> {
return {};
}
DTO
export class myDto {
#IsNotEmpty()
a: string;
#IsNotEmpty()
b: string | Array<string>
}
Spec file
describe('generate', () => {
it('should require the proper type', async () => {
const result = await controller.generate(<myDto>{});
// TODO: I expect a validation error to occur here so I can test against it.
expect(result).toEqual({})
})
})
It also fails if I do not coerce the type of myDto and just do a ts-ignore on a generic object.
Just test your DTO with ValidationPipe:
it('validate DTO', async() => {
let target: ValidationPipe = new ValidationPipe({ transform: true, whitelist: true });
const metadata: ArgumentMetadata = {
type: 'body',
metatype: myDto,
data: ''
};
await target.transform(<myDto>{}, metadata)
.catch(err => {
expect(err.getResponse().message).toEqual(["your validation error"])
})
});
You can find here complete test examples for ValidationPipe in Nestjs code repository
To test custom ValidationPipe:
let target = new ValidationDob();
const metadata: ArgumentMetadata = {
type: 'query',
metatype: GetAgeDto,
data: '',
};
it('should throw error when dob is invalid', async () => {
try {
await target.transform(<GetAgeDto>{ dob: 'null' }, metadata);
expect(true).toBe(false);
} catch (err) {
expect(err.getResponse().message).toEqual('Invalid dob timestamp');
}
});

Validation using an external function in Joi (Node.js)

I'm using Joi for validation in my Node.js Rest API. I have a function to validate a specific document and I'd like to know if it's possible to include that function in Joi's validation.
Example:
function validateDocument (document) {
return someCalculation;
}
const schema = Joi.object({
document: Joi.string().required().valid(validateDocument) // I want something like that
});
const { error, value } = schema.validate(something);
Yes. You can use Joi extension to call external function for validation. An example format is given below:
const customSchema = Joi.extend(joi => ({
base: joi.string(),
name: 'customValidation',
rules: [
{
name: 'validate',
validate(params, value, state, options) {
// call custom function and return the result
// for instance:
return validateDocument(value);
},
},
],
}));

Resources