How to make Swagger UI accepts request body with optional field - node.js

Well, the title is very easy to understande, but to make things detailed:
I have an expressjs application, written with TypeScript. The app receives a request, where the JSON body is like this:
{
name: "name",
description: "description",
github: "github",
logo: "logo",
app: "app"
}
But the thing is: The app property is optional, since I will insert the object in the database WITH that property only if the request was made with it declared.
I'd like to know if it's possible to make the Swagger accept that optional thing, and if so, how to do that?
EDIT:
As requested by #Anatoly, this is the swagger route definition:
post: {
summary: 'Create a new project',
tags: ['projects'],
requestBody: {
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Project'
}
}
}
}
},
components: {
schemas: {
Project: {
type: 'object',
properties: {
name: {
type: 'string'
},
description: {
type: 'string'
},
github: {
type: 'string'
},
logo: {
type: 'string'
},
app: {
type: 'string'
}
}
}
}
}

All params except path ones are optional unless they have the required attribute as true
app:
type:string
required:true
Refer the heading Required and Optional Parameters at https://swagger.io/docs/specification/describing-parameters/

Related

How to insert post method with mongoose having a reference in node.js

I'm using mongoose and have two schema models and one of them "control" has a reference in the fist model which is "framework" model.
With node.js I'm trying to create a post method and testing it in the postman which is not successful. Not sure if I'm approaching this the right way:
Framework Schema:
const FrameworkSchema = new Schema({
name: {
type: String,
trim: true
},
slug: {
type: String,
slug: 'name',
unique: true
},
image: {
data: Buffer,
contentType: String
},
description: {
type: String,
trim: true
},
isActive: {
type: Boolean,
default: true
},
control:
{
type: Schema.Types.ObjectId,
ref: 'Control'
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
My Control schema:
const ControlSchema = new Schema({
_id: {
type: Schema.ObjectId,
auto: true
},
mControlNo: {
type: String
},
sControlNo: {
type: String
},
name: {
type: String,
trim: true
},
slug: {
type: String,
slug: 'name',
unique: true
},
description: {
type: String,
trim: true
},
isApplicable: {
type: Boolean,
default: true
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
My router api:
router.post(
'/add',
auth,
role.checkRole(role.ROLES.Admin),
async (req, res) => {
try {
const name = req.body.name;
const description = req.body.description;
const isActive = req.body.isActive;
const control = {
mControlNo: req.body.control.mControlNo,
sControlNo: req.body.control.sControlNo,
name: req.body.control.name,
description: req.body.control.description,
isApplicable: req.body.control.isApplicable
};
const framework = new Framework({
name,
description,
isActive,
control
});
const frameworkDoc = await framework.save();
res.status(200).json({
success: true,
message: `Framework has been added successfully!`,
framework: frameworkDoc
});
} catch (error) {
res.status(400).json({
error
// error: 'Your request could not be processed. Please try again.'
});
}
}
);
My json document when I tested it in postman:
{
"name": "NCA2",
"description": "testdescription",
"isActive": true,
"control":
{
"mControlNo": "1",
"sControlNo": "2",
"name": "controltest",
"description": "controldescription",
"isApplicable": true
}
}
Response I'm getting:
{
"error": {
"errors": {
"control": {
"stringValue": "\"{\n mControlNo: '1',\n sControlNo: '2',\n name: 'controltest',\n description: 'controldescription',\n isApplicable: true\n}\"",
"kind": "ObjectId",
"value": {
"mControlNo": "1",
"sControlNo": "2",
"name": "controltest",
"description": "controldescription",
"isApplicable": true
},
"path": "control",
"reason": {}
}
},
"_message": "Framework validation failed",
"message": "Framework validation failed: control: Cast to ObjectId failed for value \"{\n mControlNo: '1',\n sControlNo: '2',\n name: 'controltest',\n description: 'controldescription',\n isApplicable: true\n}\" at path \"control\""
}
}
The whole point of using a framework like mongoose is to write models and let the framework check for you if the body you're sending it is right or wrong. You don't have to assign each variable from your body to your model. You can simply write the following, which will save lines:
const framework = new Framework(req.body);
(Of course, granted that the body has been correctly parsed to JSON via body-parser or another parser).
Then, you check for description or name to be present:
if (!description || !name)
but none of them exist. req.body.description and req.body.name do exist, possibly framework.description and framework.name do as well, but description or name are undefined variables.
The rest of the code looks good, if the error persists, please print out the error in the catch clause as others have suggested in the comments.
Following the code added in the question and the comments the OP made, we now have more elements to answer.
You have a ValidationError coming from mongoose, meaning one of the field you entered is not right. You can also see that it's coming from the field control.
In your Framework schema, you declare a field control that looks like that:
control:
{
type: Schema.Types.ObjectId,
ref: 'Control'
},
That means that Framework is taking an ObjectID for this field, and not an object like you send it here:
const framework = new Framework({
name,
description,
isActive,
control
});
The error is explicit on its own: Mongoose tried to cast your control object to an ObjectID and of course, it fails.
You have 2 solutions:
Either you implement your Control schema directly in your Framework schema as an object field
Or you create a separate Schema object in your route, save it, and give the ID to control when creating the Framework object.
The second solution could look something like this:
const control = new Control(req.body.control);
const controlDoc = await control.save();
const framework = new Framework({...req.body, control: controlDoc._id});
const frameworkDoc = await framework.save();
The first could look something like this:
const FrameworkSchema = new Schema({
// all your Framework schema
control:
{
mControlNo: { type: String },
sControlNo: { type: String },
name: { type: String, trim: true},
// and so on....
},
});

Fastify schema fails to validate type of a property of a nested object

I am new in using Fastify Js. I have this route that fails to validate the type of properties inside the nested object from inside the body schema.
fastify.put('/comment', {
schema: {
body: {
type: 'object',
required: ['from', 'body', 'courseCode'],
properties: {
from: {
type: 'object',
required: ['email', 'username'],
properties: {
// this is the part where validation fails
email: { type: 'string' },
username: { type: 'string' },
}
},
body: { type: 'string' },
courseCode: { type: 'string' },
}
}
}
}, async (req, rep) => {
// logic
return {someResponse};
});
I am using REST Client extension in VSCode. This is my sample request
put http://localhost:3000/comment
Content-Type: application/json
{
"from": {
/* Notice the `email` and `username`, Fastify does not throw an error or some exception
that tells this is suppose to be a string and not an integer */
"email": 3123,
"username": 123123
},
"body": "some comment from enginex",
"courseCode": "d-c"
}
I also try using insomia.io and postman but the result is the same.
Did I miss something important here? Thank You so much.

Fastify Route Params required is not honored

I have a route something like this:
// collection GET
fastify.route({
method: 'GET',
url: `${constants.EXTERNAL_PATH}/:serviceName/:collectionName/account/:accountId`,
schema: {
description: 'Get all documents from the specified table for the specified service database and account.',
summary: 'Get all documents from the specified table.',
tags: ['persistence'],
headers: {
$ref: 'authorization-user#'
},
params:{
type: 'object',
properties: {
serviceName: { type: 'string' },
collectionName: { type: 'string' },
accountId: { type: 'string' }
},
required: ['serviceName', 'collectionName', 'accountId'],
},
response: {
200: {
properties: {
'documents': {
description: 'All the retrieved documents from the specified table for the specified service database and account.',
type: 'array',
items: {
$ref: 'persistence-response-doc#',
}
},
},
},
'4xx': {
$ref: 'error-response#',
description: 'Error response'
}
}
},
handler: async (request, reply) => {
// Logic goes here //
}
});
Now my expectation was that:
This should work:
http://localhost:9011/persistence/external/myservice/mycollection/account/my_actn_id
As it has the service as myservice, collection as mycollection and accountId as my_actn_id
However this should fail with a route error (say when I am missing the :accountId, which is required):
http://localhost:9011/persistence/external/myservice/mycollection/account/ <== should fail
However both are passing, returning same collection results.
What am I missing here?
Thanks,
Pradip

LOOPBACK 4: Add parameters in a API call

I am new in Loopback 4 (NodeJS) and I have a question. I am developing an API. How can indicate parameters in the body of a post request that are not define as a model?.
Example:
#post('/gameshits/{id}', {
responses: {
'200': {
description: 'Return the number of correct answers',
},
},
})
async gamesHits(
#param.path.string('id') id: string,
#requestBody() answers: Array<String>,
): Promise<number> {
....
}
The problem is in the requestBody()
Its compile but in the loopback/explorer said that it can be render. The only option is create a model? how can add more parameters to send in the body of the call? (not in the url like #param do)
Thanks.
No you don't need to create a model to indicate parameters, actually it's very easy but there's no much documentation about it.
You can indicate something like this.
#post('/gameshits/{id}', {
responses: {
'200': {
description: 'Return the number of correct answers',
},
},
})
async gamesHits(
#param.path.string('id') id: string,
#requestBody({
content: {
'application/json': {
type: 'object',
schema: {
properties: {
gameName: {type: 'string'},
characters: {
type: 'array',
items: {
properties: {
name: {type: 'number'},
power: {type: 'number'},
cost: {type: 'number'},
ability: {type: 'string'}
},
},
},
},
},
},
},
}) data: any,
): Promise<number> {
....
}
To get a loopback/explorer response like this.
{
"gameName": "string",
"characters": [
{
"name": 0,
"power": 0,
"cost": 0,
"ability": "string"
}
]
}
You don't need any documentation but swagger's documentation regarding describing requests: https://swagger.io/docs/specification/2-0/describing-request-body/?sbsearch=request
Then just apply the OpenAPI Specification inside the #requestBody decorator as mentioned by darkunbeknownst.

NodeJS: Server-side request data validation

What is the right way to validate incoming data on server side?
I'm using lodash for simple validation like isObject or isArray etc, and validator for cases when i need to validate, say, if a string isEmail. But all this looks awkward and i'm not sure if this gonna hurt performance a lot or not so much.
There should be a way to validate incoming data the more elegant way.
One way to do it would be to use schema-inspector.
It's a module meant to validate json objects based on a json-schema description.
Here is an example from the github README :
var inspector = require('schema-inspector');
// Data that we want to sanitize and validate
var data = {
firstname: 'sterling ',
lastname: ' archer',
jobs: 'Special agent, cocaine Dealer',
email: 'NEVER!',
};
// Sanitization Schema
var sanitization = {
type: 'object',
properties: {
firstname: { type: 'string', rules: ['trim', 'title'] },
lastname: { type: 'string', rules: ['trim', 'title'] },
jobs: {
type: 'array',
splitWith: ',',
items: { type: 'string', rules: ['trim', 'title'] }
},
email: { type: 'string', rules: ['trim', 'lower'] }
}
};
// Let's update the data
inspector.sanitize(sanitization, data);
/*
data is now:
{
firstname: 'Sterling',
lastname: 'Archer',
jobs: ['Special Agent', 'Cocaine Dealer'],
email: 'never!'
}
*/
// Validation schema
var validation = {
type: 'object',
properties: {
firstname: { type: 'string', minLength: 1 },
lastname: { type: 'string', minLength: 1 },
jobs: {
type: 'array',
items: { type: 'string', minLength: 1 }
},
email: { type: 'string', pattern: 'email' }
}
};
var result = inspector.validate(validation, data);
if (!result.valid)
console.log(result.format());
/*
Property #.email: must match [email], but is equal to "never!"
*/
The sanitization schema is meant to "clean" your json before validating it (Setting optional values, trying to convert numbers to string, etc).
The validation schema describes the properties your json should respect.
You then call inspector.validate to check if everything is fine.

Resources