Is it possible to get messages returned by express-validator into a language other than english for internationalization (i18n) ?
I tried looking into the source code and I could not find it.
express-validator
Thanks
It's probably something you have to create yourself, but it shouldn't be too hard.
When you assign an error message with withMessage() you can send more than just a single string. You can for example send an object. So you can put the error messages, for all the languages, for a particular error, in an object.
Here is an example:
Route:
const countValidation = require('./count.validation');
router
.route('/blogposts')
.get(
countValidation.count,
blogpostController.blogpostsGetAll,
);
Validator (in a separate file called count.validation.js):
const message = {
english: 'count must be between 1 and 1000',
chinese: 'count must be between 1 and 1000, but in chinese',
};
module.exports.count = [
check('count')
.optional()
.isInt({ min: 1, max: 1000 })
.withMessage(message)
];
This response will be sent when validation fails:
{
"errors": {
"count": {
"location": "query",
"param": "count",
"value": "-1",
"msg": {
"english": "count must be between 1 and 1000",
"chinese": "count must be between 1 and 1000, but in chinese"
}
}
}
}
In this particular example the front-end has to choose what error message to display depending on user settings or user agent.
It's also possible to deal with what error message to use on the server side, if we know what language the client is using from the request. We could for example read the accept-language header in the request. Here is an example of how that could be done:
The controller function:
Rather than using this (standard handling of errors, almost straight from the readme):
module.exports.blogpostsGetAll = (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.mapped() });
}
// The rest of the function...
};
we use this:
module.exports.blogpostsGetAll = (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const errorsInProperLanguage = handleLanguages(req.headers, errors.mapped());
return res.status(422).json({ errors: errorsInProperLanguage });
}
// The rest of the function...
};
Example function to only use one language:
function handleLanguages(headers, errorsMapped) {
const language = headers['accept-language'].split(',')[0];
for (let errorKey in errorsMapped) {
errorsMapped[errorKey].msg = errorsMapped[errorKey].msg[language];
}
return errorsMapped;
}
Because the accept-language header contains language codes, not language names, we have to modify the message object slightly:
const message = {
'en-US': 'count must be between 1 and 1000',
'zh-CH': 'count must be between 1 and 1000, but in chinese',
};
The message object HAS to contain the first language code in the accept-language header for this to work. The handleLanguage function doesn't handle errors. It's only an example to show how it could be done; don't use it directly.
The error message would change to
{
"errors": {
"count": {
"location": "query",
"param": "count",
"value": "-1",
"msg": "count must be between 1 and 1000, but in chinese"
}
}
}
when the first language in accept-language is zh-CH.
This is now possible with express-validator v5.0.0.
If you pass withMessage() a function, it will be called with the field value, the request, its location and its path.
Example from the docs:
check('something').isInt().withMessage((value, { req, location, path }) => {
return req.translate('validation.message.path', { value, location, path });
}),
Related
I am testing a frontend and I want to make my test more efficient
I have the following custom command:
cy.intercept('**/api/classification/dd86ac0a-ca23-413b-986c-535b6aad659c/items/**',
{ fixture: 'ItemsInEditor.json' }).as('ItemsInEditorStub')
This works correctly and is intercepts 25 times :). But the Id in the stub file has to be the same as in the requested Endpoint. Otherwise the frontEnd wilt not process it.
At this point I do not want to make 25 stubfiles in the fixture map.
In the printscreen you can see the different calls I need to intercept. The last ID I would like to save as variable and use it in the stub file
The Stub is like this:
{
"item": {
"version": 3,
"title": "Cars",
"rows": [],
"id": "dynamicIdBasedOnEndPoint" <- *Can we make it dynamic based on the ID in the endpoint*
},
"itemState": "Submitted"
}
UPDATE:
What I have for now is just the basic I guess:
cy.intercept('**/api/classification/*/items/**', {
body:
{
item: {
version: 3,
title: 'Cars',
rows: [],
id: '55eb5a28-24d8-4705-b465-8e1454f73ac8' //Still need this value to be dynamic and always the same as the intercepted '**'(wildcard)
},
itemState: "Submitted"
}
})
.as('ItemsInEditorStub')
cy.fixture('ItemsInEditor.json').then(ModFixture => {
cy.intercept('GET', '**/api/classification/**/items/id/**', (req) => {
const id = req.url.split('/').pop(); // last part of url path
ModFixture.item.id = id; // add the id dynamically
req.reply(ModFixture); // send altered fixture
})
}).as('ItemsInEditorStub')
Thanks to #Fody
You can make a dynamic fixture using javascript.
Ref Providing a stub response with req.reply()
cy.fixture('ItemsInEditor.json').then(fixture => {
cy.intercept('**/api/classification/dd86ac0a-ca23-413b-986c-535b6aad659c/items/**',
(req) => {
const id = req.url.split('/').pop(); // last part of url path
fixture.item.id = id; // add the id dynamically
req.reply(fixture); // send altered fixture
}
).as('ItemsInEditorStub')
})
I have a need to 'aggregate' multiple graphQl services (with same schema) into single read-only (query only) service exposing data from all services. For example:
---- domain 1 ----
"posts": [
{
"title": "Domain 1 - First post",
"description": "Content of the first post"
},
{
"title": "Domain 1 - Second post",
"description": "Content of the second post"
}
]
---- domain 2 ----
"posts": [
{
"title": "Domain 2 - First post",
"description": "Content of the first post"
},
{
"title": "Domain 2 - Second post",
"description": "Content of the second post"
}
]
I understand that 'stitching' is not meant for UC's like this but more to combine different micro-services into same API. In order to have same types (names) into single API, I implemented 'poor man namespaces' by on-the-fly' appending domain name to all data types. However, I'm able only to make a query with two different types like this:
query {
domain_1_posts {
title
description
}
domain_2_posts {
title
description
}
}
but, it results with data set consist out of two arrays:
{
"data": {
"domain_1_posts": [
{ ...},
],
"domain_2_posts": [
{ ...},
]
}
}
I would like to hear your ideas what I can do to combine it into single dataset containing only posts?
One idea is to add own resolver that can call actual resolvers and combine results into single array (if that is supported at all).
Also, as a plan B, I could live with sending 'domain' param to query and then construct query toward first or second domain (but, to keep initial query 'domain-agnostic', e.g. without using domain namses in query itself?
Thanks in advance for all suggestions...
I manage to find solution for my use-case so, I'll leave it here in case that anyone bump into this thread...
As already mentioned, stitching should be used to compose single endpoint from multiple API segments (microservices). In case that you try to stitch schemas containing same types or queries, your request will be 'routed' to pre-selected instance (so, only one).
As #xadm suggested, key for 'merging' data from multiple schemas into singe data set is in using custom fetch logic for Link used for remote schema, as explained:
1) Define custom fetch function matching your business needs (simplified example):
const customFetch = async (uri, options) => {
// do not merge introspection query results!!!
// for introspection query always use predefined (first?) instance
if( operationType === 'IntrospectionQuery'){
return fetch(services[0].uri, options);
}
// array fecth calls to different endpoints
const calls = [
fetch(services[0].uri, options),
fetch(services[1].uri, options),
fetch(services[2].uri, options),
...
];
// execute calls in parallel
const data = await Promise.all(fetchCalls);
// do whatever you need to merge data according to your needs
const retData = customBusinessLogic();
// return new response containing merged data
return new fetch.Response(JSON.stringify(retData),{ "status" : 200 });
}
2) Define link using custom fetch function. If you are using identical schemas you don't need to create links to each instance, just one should be enough.
const httpLink = new HttpLink(services[0].uri, fetch: customFetch });
3) Use Link to create remote executable schema:
const schema = await introspectSchema(httpLink );
return makeRemoteExecutableSchema({
schema,
link: httpLink,
context: ({ req }) => {
// inject http request headers into context if you need them
return {
headers: {
...req.headers,
}
}
},
})
4) If you want to forward http headers all the way to the fetch function, use apollo ContextLink:
// link for forwarding headers through context
const contextLink = setContext( (request, previousContext) => {
if( previousContext.graphqlContext ){
return {
headers: {
...previousContext.graphqlContext.headers
}
}
}
}).concat(http);
Just to mention, dependencies used for this one:
const { introspectSchema, makeRemoteExecutableSchema, ApolloServer } = require('apollo-server');
const fetch = require('node-fetch');
const { setContext } = require('apollo-link-context');
const { HttpLink } = require('apollo-link-http');
I hope that it will be helfull to someone...
I'm learning how to automate API with frisby.js on gmail.api.
I want to create a test where I create and delete(or send) a Draft message.
So I wrote a test which creates a Draft and my question is - can I write a code that gets at least ID of generated response from my Post call?
var frisby = require('frisby');
frisby.create('Create Draft Google')
.post('https://www.googleapis.com/gmail/v1/users/me/drafts?access_token=*my-token-here*', {
message: {
raw: "RGFuJ3MgVG9vbHMgYXJlIGNvb2wh",
id: "1547265285486966899"
}
}, { json: true })
.inspectJSON()
.inspectBody()
.expectStatus(200)
.toss();
So, to clarify, I want to write another part of THIS^ test with
.after(function(err, res, body){}
Steps:
I create a Draft message
I want my test to automatically get ID of just created Draft
So I could Delete it\Send it
Thanks!
When you create a draft, you will get the id of the newly created draft in the response:
Request
POST https://www.googleapis.com/gmail/v1/users/me/drafts?access_token={access_token}
{
"message": {
"raw": "RnJ..."
}
}
Response
{
"id": "r5019331921817638435",
"message": {
"id": "157948187e41b5bb",
"threadId": "157948187e41b5bb",
"labelIds": [
"DRAFT"
]
}
}
Then you can use this id to either send or delete the message.
.afterJSON(function(json){
callback(json.id);
})
I used this function and it worked. Thanks to my friend for help :D
Here're full tests if someone needs it:
This is how I get an ID of created Draft
var frisby = require('frisby');
var new_id = function(frisby, callback)
{
frisby.create('Create Draft Google')
.post('https://www.googleapis.com/gmail/v1/users/me/drafts?access_token=[my_token]', {
message: {
raw: "RGFu...",
}
}, { json: true })
.inspectJSON()
.inspectBody()
.expectStatus(200)
.afterJSON(function(json){
callback(json.id);
})
.toss();
};
module.exports = new_id;
This is how I used it to delete this Draft
var frisby = require('frisby');
var getid_spec = require("./getid_spec.js");
getid_spec(frisby,function(id){
frisby.create('Delete Google Draft Test')
.delete("https://www.googleapis.com/gmail/v1/users/me/drafts/" +id +"?access_token=[my_token]", {})
.expectStatus(204)
.toss();
})
In my app (sails 0.12.0) I want to extend a limit of bytes send upon POST request. So I've followed the comments in this stackoverflow question
var skipper = require('skipper');
skipper.limit = 1024*1024*100;
middleware: {
bodyParser: skipper
}
I still get an error:
"data": {
"code": "E_EXCEEDS_UPLOAD_LIMIT",
"name": "Upload Error",
"maxBytes": 15000000,
"written": 15007474,
"message": "Upload limit of 15000000 bytes exceeded (15007474 bytes written)"
}
I've also tried to add the code below directly under module.exports.http and then I've tried to add it in the middleware only.
bodyParser: (function () {
var opts = {limit:'50mb'};
var fn;
// Default to built-in bodyParser:
fn = require('skipper');
return fn(opts);
})
My question is: Why none of these codes work and how can I increase the limit. The solution can be not elegant.
Everything that you need - set
maxBytes
attribute in object of options to upload() method of skipper Upstream.
req.file('image').upload({maxBytes: 50000000}, function (err, uploadedFiles) {
if (err) return res.serverError(err.message);
if(uploadedFiles.length > 0) {
// do with uploaded images what you want
.....
}
});
A contrived example of bi-directional data binding
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
m.render("body", [
m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
]);
}
};
https://lhorie.github.io/mithril/mithril.withAttr.html
I tried the above code does not work nothing.
It was the first to try to append the following.
m.mount(document.body, user);
Uncaught SyntaxError: Unexpected token n
Then I tried to append the following.
var users = m.prop([]);
var error = m.prop("");
m.request({method: "GET", url: "/users/index.php"})
.then(users, error);
▼/users/index.php
<?php
echo '[{name: "John"}, {name: "Mary"}]';
Uncaught SyntaxError: Unexpected token n
How do I operate the m.withAttr tutorials code?
Try returning m('body', [...]) from your controller.
view: function (ctrl) {
return m("body", [
...
]);
}
render should not be used inside of Mithril components (render is only used to mount Mithril components on existing DOM nodes).
The example is difficult to operate because it's contrived, it's not meant to be working out-of-the-box. Here's a slightly modified, working version:
http://jsfiddle.net/ciscoheat/8dwenn02/2/
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
return [
m("input", {
oninput: m.withAttr("value", controller.user.name),
value: controller.user.name()
}),
m("h1", controller.user.name())
];
}
};
m.mount(document.body, user);
Changes made:
m.mount injects html inside the element specified as first parameter, so rendering a body element in view will make a body inside a body.
Changed the input field event to oninput for instant feedback, and added a h1 to display the model, so you can see it changing when the input field changes.
Using m.request
Another example how to make an ajax request that displays the retrieved data, as per your modifications:
http://jsfiddle.net/ciscoheat/3senfh9c/
var userList = {
controller: function() {
var users = m.prop([]);
var error = m.prop("");
m.request({
method: "GET",
url: "http://jsonplaceholder.typicode.com/users",
}).then(users, error);
return { users: users, error: error };
},
view: function(controller) {
return [
controller.users().map(function(u) {
return m("div", u.name)
}),
controller.error() ? m(".error", {style: "color:red"}, "Error: " + controller.error()) : null
];
}
};
m.mount(document.body, userList);
The Unexpected token n error can happen if the requested url doesn't return valid JSON, so you need to fix the JSON data in /users/index.php to make it work with your own code. There are no quotes around the name field.