Can a JOI extension be applied to 'any()' so that it works for all types? - joi

I'm building a JOI extension to allow me to blacklist certain people from sending certain API values, if they are missing certain roles from their JWT scope.
So far I've go this:
const Joi = require('joi')
const { string, number, ref, required, only, lazy } = Joi.extend(joi => ({
name: 'string',
base: Joi.string(),
language: {
permits: 'you are not allowed to edit {{key}}'
},
pre (value, state, options) {
this.permissions = options.context.auth.credentials.scope
},
rules: [{
name: 'whitelist',
params: {
permits: Joi.array()
},
validate(params, value, state, options) {
const permitted = params.permits.find(value => this.permissions.includes(value))
return permitted ? value : this.createError('string.permits', {}, state, options)
}
}]
}))
Which works perfectly.
However, Note the name and base are set to 'string'. I want this to work for numbers, arrays, objects, you name it.
I've tried this:
name: 'any',
base: Joi.any()
but it doesn't seem to work:
/home/ant/Projects/roles-example/routes/validation.routes.js:55
reference: string().whitelist(['all-loans']),
^
TypeError: string(...).whitelist is not a function
I would assume that any would allow me to append the function to any other type within JOI. But it seems that I can't.
Does anyone have any pointers for me, before I have to start adding this to all of the JOI base types?

The way I solved this problem is by declaring the any rules separate and adding them all to each Joi type.
const Joi = require('joi')
const anyRules = j => [
{
name: 'whitelist',
params: {
permits: j.array()
},
validate(params, value, state, options) {
const permitted = params.permits.find(value => this.permissions.includes(value))
return permitted ? value : this.createError('string.permits', {}, state, options)
}
}
];
module.exports = Joi
.extend(j => ({
name: 'any',
base: j.any(),
rules: anyRules(j),
}))
.extend(j => ({
name: 'array',
base: j.array(),
rules: anyRules(j).concat([
// ...
]),
}))
.extend(j => ({
name: 'boolean',
base: j.boolean(),
rules: anyRules(j).concat([
// ...
]),
}))
.extend(j => ({
name: 'binary',
base: j.binary(),
rules: anyRules(j).concat([
// ...
]),
}))
.extend(j => ({
name: 'date',
base: j.date(),
rules: anyRules(j).concat([
// ...
]),
}))
.extend(j => ({
name: 'func',
base: j.func(),
rules: anyRules(j).concat([
// ...
]),
}))
.extend(j => ({
name: 'number',
base: j.number(),
rules: anyRules(j).concat([
// ...
]),
}))
.extend(j => ({
name: 'object',
base: j.object(),
rules: anyRules(j).concat([
// ...
]),
}))
.extend(j => ({
name: 'string',
base: j.string(),
rules: anyRules(j).concat([
// ...
]),
}))
.extend(j => ({
name: 'alternatives',
base: j.alternatives(),
rules: anyRules(j).concat([
// ...
]),
}))
;

Related

NextJS send data on redirect inside getServerSideProps

Is there a posibility to send data with redirect inside getServerSideProps function similar way as in next.config.js (you cannot pass hidden queries as far as I know inside next config file).
export const getServerSideProps = async (context) => {
const id = context.params.id;
return {
redirect: {
destination: '/my-work',
permanent: false,
has: [
{
type: 'query',
value: id
}
]
},
props: {
}
}
}
I want to pass hidden query to another page so this only works as middleware redirection as I am comming on this page from email template. But has object is not working in getServerSideProps function.
Is there any other ways to achieve that?
Thanks for your help!
This is from the official documentation.
module.exports = {
async redirects() {
return [
// if the header `x-redirect-me` is present,
// this redirect will be applied
{
source: '/:path((?!another-page$).*)',
has: [
{
type: 'header',
key: 'x-redirect-me',
},
],
permanent: false,
destination: '/another-page',
},
// if the source, query, and cookie are matched,
// this redirect will be applied
{
source: '/specific/:path*',
has: [
{
type: 'query',
key: 'page',
// the page value will not be available in the
// destination since value is provided and doesn't
// use a named capture group e.g. (?<page>home)
value: 'home',
},
{
type: 'cookie',
key: 'authorized',
value: 'true',
},
],
permanent: false,
destination: '/another/:path*',
},
// if the header `x-authorized` is present and
// contains a matching value, this redirect will be applied
{
source: '/',
has: [
{
type: 'header',
key: 'x-authorized',
value: '(?<authorized>yes|true)',
},
],
permanent: false,
destination: '/home?authorized=:authorized',
},
// if the host is `example.com`,
// this redirect will be applied
{
source: '/:path((?!another-page$).*)',
has: [
{
type: 'host',
value: 'example.com',
},
],
permanent: false,
destination: '/another-page',
},
]
},
}
You can compare the params with it. For more details, visit here

How to define array type for multiple or nested json data from REST API

I'm new to GraphQL thingy. I have a problem in fetching array data from API using express-graphql server and I find to hard the solution somewhere. Here is the scenario.
I have a GET data from REST API which has the response that similar like this :
{
"id": 1,
"name": "billy",
"foods": [
{
"food":{
"name":"crepes",
"taste": "crunchy"
}
},
{
"food":{
"name":"noodle",
"taste":"spicy"
}
},
]
}
In my schema, I have successfully gets the id and name which I implement like this :
const FoodsType= new GraphQLObjectType({
name: 'foods',
fields: () => ({
id:{ type: GraphQLInt },
name:{ type: GraphQLString},
foods: { type: GraphQLArray}
})
});
As you can see I with my code above, I failed fetching foods data which contains array data because there is no scalar type like GraphQLArray.
My question is how do we get the foods data with containing multiple json data food inside it ?
const ResType= new GraphQLObjectType({
name: 'response',
field:() => ({
name: {type: GraphQLString},
taste: {type: GraphQLString}
})
});
const FoodsType= new GraphQLObjectType({
name: 'foods',
fields: () => ({
id:{ type: GraphQLInt },
name:{ type: GraphQLString},
foods: { type: new GraphQLList(ResType)}
})
});
After some attempt, I finally manage to solve this by define the types of every depth level. Here is my answer for schema file.
const FoodsType = new GraphQLObjectType({
name: 'foods',
fields: () => ({
id:{ type: GraphQLInt },
name: { type: GraphQLString },
foods:{ type: new GraphQLList(FoodType) }
})
});
const FoodType = new GraphQLObjectType({
name: 'food',
fields: () => ({
food: {type: FoodDetail}
})
});
const FoodDetail = new GraphQLObjectType({
name: 'fooddetail',
fields: () => ({
name:{ type: GraphQLString },
taste:{ type: GraphQLString}
})
});
And here is the resolver that I get from API
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
foods: {
type: FoodsType,
args: {
id: { type: GraphQLInt }
},
resolve(parent, args) {
return axios
.get(`https://hereisanapi/${args.id}`)
.then(res => {
return res.data;
});
}
}
}
});
Then in GraphQL Query I put this.
{
foods(id:1){
food{
name,
taste
}
}
}

Graphql create relations between two queries.Error cannot access before initialization

I have this code:
const ProductType = new GraphQLObjectType({
name: 'Product',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
category: {
type: CategoryType,
resolve: async (parent) => {
return await Category.findOne({_id: parent.category});
}
}
}
});
const CategoryType = new GraphQLObjectType({
name: 'Category',
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString },
products: {
type: ProductType,
resolve: async (parent, args) => {
return await Product.find({category: parent._id});
}
}
}
});
const Query = new GraphQLObjectType({
name: 'Query',
fields: {
Categories: {
type: new GraphQLList(CategoryType),
resolve: async () => {
return await Category.find();
}
}
}
});
When i try to compile i get ReferenceError: Cannot access 'CategoryType' before initialization.
I understand that first of all I should declare and only after that use, but I saw a similar code in one lesson on YouTube, and I think that it should work, but it’s not.
fields can take a function instead of an object. This way the code inside the function won't be evaluated immediately:
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
category: {
type: CategoryType,
resolve: (parent) => Category.findOne({_id: parent.category}),
}
})

GraphQL Schema for mongoose Mixed type (Schema.Types.Mixed)

I have a Mongoose schema with the following structure:
import mongoose from 'mongoose';
const PropertySchema = new mongoose.Schema({
name: {
type: String
},
description: {
type: String
},
value: {
type: mongoose.Schema.Types.Mixed
},
unit: {
type: String
},
});
export default mongoose.model('Property', PropertySchema);
I need to build a GraphQL query for the given data. How do I handle the Mixed type for the value property ?
Here is my try:
import NodeInterface from '../interfaces';
import PropertyModel from '../../models/Property';
const fields = {
id: {
type: new GraphQLNonNull(GraphQLID),
resolve: (obj) => dbIdToNodeId(obj._id, "Property")
},
name: {
type: GraphQLString
},
description: {
type: GraphQLString
},
value: {
type: <<< What to use here ?
},
unit: {
type: GraphQLString
}
};
export const PropertyType = new GraphQLObjectType({
name: 'Property',
description: 'Property',
interfaces: () => [NodeInterface],
isTypeOf: (value) => value instanceof PropertyModel,
fields: fields
});

Graphql don't map data from resolve function in to the server answer

I have express server with running GraphQL on it.
There is a schema with Foo type
FooType = new GraphQLObjectType({
name: 'Foo',
fields: () => ({
name: {
type: GraphQLString,
},
surname: {
type: GraphQLString,
},
}),
});
schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
foo: {
type: FooType,
resolve: () => fooResolver
}
}
})
})
Resolver for foo returns object { name: 'bar', surname: 'boo' }, but in response from graphQL server I got an object { name: 'bar', surname: null }
The question is -
Why could it be and how could it be possible?
I thought GraphQL just maps the result object from resolver into
response object, doesn't it?