Validate request body separately from request as a whole - jsonschema

I have a question for validating a PUT request. The body of the request is an array of objects. I want the request to succeed if the body contains an array of at least length one, but I also need to do a separate validation on each object in the array and pass that back in the response. So my put body would be:
[1, 2, {id: "thirdObject"}]
The response should be 200 even though the first two items are not even objects. The request just needs to succeed if an array of length 1 is passed in the body. The response needs to be something like:
[{id: firstObject, status: 400, error: should be object}, {id: secondObject, status: 400, error: should be object}, { id: thirdObject, status: 204 }]
Currently I am validating the body as such with fluent schema:
body: S.array().items(myObjectSchema)
.minItems(1)
Which will result in a 400 if any of the items in the body don’t match the myObjectSchema. Was wondering if you have any idea how to achieve this?

The validation doesn't tell you if a schema is successful (eg { id: thirdObject, status: 204 }), so you need to manage it by yourself.
To do that, you need to create an error handler to read the validation error and merge with the request body:
const fastify = require('fastify')()
const S = require('fluent-schema')
fastify.put('/', {
handler: () => { /** this will never executed if the schema validation fail */ },
schema: {
body: S.array().items(S.object()).minItems(1)
}
})
const errorHandler = (error, request, reply) => {
const { validation, validationContext } = error
// check if we have a validation error
if (validation) {
// here the validation error
console.log(validation)
// here the body
console.log(request.body)
reply.send(validation)
} else {
reply.send(error)
}
}
fastify.setErrorHandler(errorHandler)
fastify.inject({
method: 'PUT',
url: '/',
payload: [1, 2, { id: 'thirdObject' }]
}, (_, res) => {
console.log(res.json())
})
This will log:
[
{
keyword: 'type',
dataPath: '[0]',
schemaPath: '#/items/type',
params: { type: 'object' },
message: 'should be object'
},
{
keyword: 'type',
dataPath: '[1]',
schemaPath: '#/items/type',
params: { type: 'object' },
message: 'should be object'
}
]
[ 1, 2, { id: 'thirdObject' } ]
As you can see, thanks to validation[].dataPath you are able to understand which elements of the body array is not valid and merge the data to return your info.
Consider that the handler will be not executed in this scenario. If you need to execute it regardless the validation, you should do the validation job in a preHandler hook and avoid the default schema validation checks (since it is blocking)
edit
const fastify = require('fastify')()
const S = require('fluent-schema')
let bodyValidator
fastify.decorateRequest('hasError', function () {
if (!bodyValidator) {
bodyValidator = fastify.schemaCompiler(S.array().items(S.object()).minItems(1).valueOf())
}
const valid = bodyValidator(this.body)
if (!valid) {
return bodyValidator.errors
}
return true
})
fastify.addHook('preHandler', (request, reply, done) => {
const errors = request.hasError()
if (errors) {
console.log(errors)
// show the same errors as before
// you can merge here or set request.errors = errors to let the handler read them
reply.send('here merge errors and request.body')
return
}
done() // needed to continue if you don't reply.send
})
fastify.put('/', { schema: { body: S.array() } }, (req, reply) => {
console.log('handler')
reply.send('handler')
})
fastify.inject({
method: 'PUT',
url: '/',
payload: [1, 2, { id: 'thirdObject' }]
}, (_, res) => {
console.log(res.json())
})

I don't know the schema syntax you are using, but using draft 7 of the JSON Schema (https://json-schema.org/specification-links.html, and see also https://json-schema.org/understanding-json-schema for some reference material), you can do:
{
"type": "array",
"minItems": 1
}
If you want to ensure that at least one, but not necessarily all items match your object type, then add the "contains" keyword:
{
...,
"contains": ... reference to your object schema here
}

Related

Database error handling with Sequelize when column doesn't exist?

I'm trying to handle different errors that might show up when inserting into a MYSQL database.
Using Sequelize with express.
My foo.js model file looks like this:
module.exports = (sequelize, type) => {
return sequelize.define('event', {
id: {
type: type.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: type.STRING,
}
},{
freezeTableName: true,
rejectOnEmpty: true,
})
}
and my route file (or whatever you wanna call it), looks like this.
const Sequelize = require('sequelize')
const fooModel = require('../../models/Foo')
const router = require('express').Router();
const auth = require('../auth');
const bodyParser = require('body-parser');
const sequelize = new Sequelize('username', 'password', 'db', {
host: 'localhost',
dialect: 'mysql'
})
const Foo = fooModel(sequelize, Sequelize);
router.use(bodyParser.json({limit: '100mb'}));
router.use(bodyParser.urlencoded({ extended: true, limit: '100mb', parameterLimit: 1000000 }));
sequelize.sync({force: true})
.then(() => {
console.log('Worked');
});
router.post('/', (req,res,next) => {
if(Object.keys(req.body).length > 0){
return Foo.create({
Name: req.body.Name
}).then((result) => {
if(result){
return res.status(200).json(result);
}else{
return res.status(400).json({'error': 'Could not create record.'});
}
}).catch(Sequelize.DatabaseError, function(err){
return res.status(400).json(err);
}).catch(function(err){
res.send(err);
})
}else{
return res.status(400).json({'error': 'error'});
}
});
module.exports = router;
Whenever I try to post to the route with something like:
{
"name": "test",
"foo": "bar"
}
Sequelize accepts the body and puts "test" in the ”name” column, and ignores the "foo" column, because the "foo" column does not exist. Meaning, all I get back once it's posted is:
{"id": "123",
createdAt: 2020-01-23 13:337:00
updatedAt: 2020-01-23 13:337:00
}
And not an error as I expect.
What Im trying to do, is catch that error (that I today ain't recieving) whenever I try to post to a column that doens't exist, basically replicate a normal MYSQL error behaviour.
Could someone point me in the right direction on what I'm missing?
In my experience, it would be better to avoid this particular problem by validating the fields on the client side.
But, you can trap such a condition in js. You'll not get a DB exception because Sequelize isn't sending your unrecognized attributes to the database.
if (!Foo.attributes.hasOwnProperty('foo')) {
// some error handing here, for invalid field.
}
You could write a utility function to iterate through the attributes of req.body and send an appropriate error to the response.
FWIW, you'll find that Name is also invalid, because your model specifies(lower case) name
hth

How to update nested Objects

I want to use the $push method to push an object into a nested array. But i cant get it to work that you can dynamically get the right object inside of the array. Let me explain better by showing the code.
This is my Schema:
var StartedRaceSchema = new mongoose.Schema({
waypoints: {
type: Object,
name: String,
check_ins: {
type: Object,
user: {
type: Object,
ref: 'User'
}
}
}
});
When you check in on a waypoint, it has to be pushed in the correct waypoints nested Check_ins
This is the code for the update:
StartedRace.findByIdAndUpdate(req.params.id,
{ $push: { 'waypoints.1.check_ins': req.body.user } },
function (error) {
if (error) {
console.log(error)
res.send({
success: false,
error: error
})
} else {
res.send({
success: true
})
}
}
)
As you can see i can only get it to work with fields like:
'waypoints.1.check_ins'
That 1 needs to be dynamically because it gets send within the parameters.
But i can not get it to work dynamically, only hard coded.
Does anyone know how to do this?
Populate the collection with a list of check_ins enumerated by their ids.
waypoints.check_ins = {
...waypoints.check_ins,
[response.id]: response
}
You'd then have a list of check_ins that can referenced by their ids.
You could try this syntax instead of the dot notation:
let id = req.params.id;
StartedRace.findByIdAndUpdate(req.params.id,
{ $push: { waypoints: { id: { check_ins: req.body.user } } } }, { new : true } )
.exec()
.then(race => console.log(race))
.catch(err => err);
I used a Promise, but it's the same with a callback.

Axios post request with multiple parameters in vuex action

Fetching API data with axios in vuex action:
actions: {
login ({commit}, payload) {
axios.post(globalConfig.TOKEN_URL, {
payload
})
.then((resp) => {
commit('auth_success', resp.data)
})
.catch((err) => {
console.log(err)
})
},
}
Component's method for sending the data:
methods: {
authChatClient () {
let payload = {
name: this.clientFio,
number: this.clientNumber
}
this.$store.dispatch('login', payload)
},
}
However it won't work, since payload is an object, wrapped in a payload object. Is it possible to send multiple parameters from component's method to a vuex action?
Post request is looking like this: payload: {name: "aaa", number: "111"}
Vuex only allows the use of 1 parameter to an action. However, if I understand your question correctly, you can send multiple parameters to a vuex action if they are wrapped in an object. Example:
login({commit}, {name, number /*, ...more here*/}) {
axios.post(globalConfig.TOKEN_URL, {
name: name,
number: number,
/* more parameters here */
})
/* ... */
}
And you can call it with:
methods: {
authChatClient () {
let payload = {
name: this.clientFio,
number: this.clientNumber,
/* more parameters */
}
this.$store.dispatch('login', payload)
},
}

Best practice to fetch sync

Im trying to get the model User which have a relation with Projects.
Here is my models/user.js
*fetchUser({ payload, callback }, { call, put }) {
const user = yield call(queryUser, payload);
yield put({
type: 'setCurrentUser',
payload: user,
});
if (callback) callback();
},
What is the best practice? Where should I call the function queryProjects?
Maybe below this line?
const user = yield call(queryUser, payload);
const projects = yield call(queryProjects, user); // new api call
First Approach
Make two models, users & projects
Then you can do this
*fetchUser({ payload, callback }, { call, put }) {
const user = yield call(queryUser, payload);
yield put({
type: 'setCurrentUser',
payload: user,
});
yield put({type: 'projects/fetchByUserId', payload: user.id});
},
Inside your `projects` model.
*fetchByUserId({payload){
// do ajax call
}
Once you have data for projects you can filter the project array with user id (considering your project has something like project.user_id) and show them into the app.
Second Approach
You can have your backend server send the project list for user in the user object.
{
id: 1,
username: "foo",
projects: [
{id: 1},
{id: 2}
{id: 3}
]
}
I hope it makes sense.

adding a query to an axios get request

I am trying to restrict what gets returned by my axios get request.
I have a firebase backend and in my data i have a field called case_name, I have added a few records with the name Test and thought i could run the following in a get request to restrict my results to just those where case_name is equal to test, but it still returns all records
loadCase ({ commit, context }) {
return axios.get('http', {
params: {
case_name: 'Test'
}
})
.then(res => {
const convertcase = []
for (const key in res.data) {
convertcase.push({ ...res.data[key], id: key })
}
commit('listcase', convertcase)
})
.catch(e => context.error(e));
},
Can anyone tell me what im doing wrong please as cant find anything to help me at the moment
my returned object is
data: {…}
​​
"-LFXvk9yY5c-O8yIdf8k": Object { case_name: "Test", case_status: "live", case_summary: "This is some summary content", … }
​​
"-LFXwmv6eHqZs8jndNay": Object { case_name: "case 2", case_status: "live", case_summary: "dasdasdasdasd\nasd\ndasd\na\nsdasdasd", … }
​​
"-LFc2t9V7LVqnLAlIjoU": Object { case_name: "Test", case_status: "live", case_summary: "this is just another summary", … }
Thanks