NodeJS RESTful API - Как правильно обрабатывать «неопределенные» переменные запроса? - PullRequest
0 голосов
/ 04 октября 2018

Я разрабатываю RESTful API с использованием NodeJS и Express.
Я заметил, что во входящих запросах иногда отсутствуют некоторые ожидаемые переменные, что приводит к сбою программы, говоря, что она не может установить значение переменной на'undefined' значение - поскольку в запросе не было получено значение.
Пример:
Приложение ожидает переменную Y, но вместо этого отправляется переменная X:

 formData: { variableX: 'valueX' }

Программа ожидает получения переменной Yсо следующим кодом:

const checkVariables = Joi.validate({ 
    variableY: req.body.variableY,
}, schema);

Приложение аварийно завершает работу со следующей ошибкой:

TypeError: Cannot read property 'variableY' of undefined

Я подумал о нескольких способах справиться с этим, включая объявление переменных при запуске приложения ииспользуя их вместе, используя try-catch.
Другим способом будет использование if-else, if-chaining или case-switch, но, как вы поняли, конечно, я ищу самый чистый способ достижения этого.
Есть идеи?

Спасибо.

** РЕДАКТИРОВАТЬ **
Прогрессировал и сумел добиться результата, используя только объект.При попытке получить доступ к любому из его внутренних полей в любом случае будет выдано сообщение об ошибке, например:
if(req.body.variableY == undefined){console.log('The expected variable is undefined');} //true

Когда проверка обращается к полю внутри объекта undefined:
if(req.body.variableY.dataId == undefined){console.log('The expected variable is undefined');} //crashes
Снова выдается следующая ошибка:
TypeError: Cannot read property 'variableX' of undefined

После еще нескольких копаний нашел этот поток Stackoverflow:
Как проверить, существует ли свойство объекта с переменнойудерживая имя свойства?
Пробовал с помощью hasOwnProperty, но выдается такая же ошибка:
TypeError: Cannot read property 'hasOwnProperty' of undefined

Попытка объявления переменной обтекания с использованием try-catch, все еще неwork:

try{
    var variableX = req.body.variableX
    var variableXDataId = req.body.variableX.dataId
}
catch(e){
    res.status(400).send('Wrong request error: Please check your request variables and try again');
}

Так как это действительно базовая проверка, которая должна выполняться большинством API-интерфейсов RESTful (проверка того, что вы получаете ожидаемые входящие переменные внутри запроса, поэтому программа не будет аварийно завершена, еслиошибки, которые он не может обработать - каково общее решение для таких проблем (ожидаемая / неожиданная проверка запроса)?

Спасибо.

Ответы [ 3 ]

0 голосов
/ 05 октября 2018

Вы можете использовать экспресс-валидатор https://www.npmjs.com/package/express-validator для проверки входящего запроса. Затем добавьте его в свой контроллер, где a, b, c, d - параметры, которые вы хотите проверить

const nonEmptyFields = ['a', 'b', 'c', 'd'];

  nonEmptyFields.forEach(field => req.assert(field, `${field} cannot be blank`).notEmpty());

  const errors = req.validationErrors();

  if (errors) {
    return res.status(400).send(errors);
  }

для проверки поля внутри поля, вы можете попробовать сделать это

typeof(req.body && req.body.name !== undefined)
0 голосов
/ 05 октября 2018

Решением будет установка пустого объекта по умолчанию для замены неопределенного на родительском уровне:

// checking for body.variableX.variableZ with object destructuring ES6
const {body = {}} = request;
const {variableX = {}, variableY} = body;
const {variableZ} = variableX.variableZ;

// or prior ES6
var body = request.body || {};
var variableX = body.variableX || {};
var variableY = variableX.variableY;

// or in a statement
var variableY = request.body && request.body.variableX ? request.body.variableX.variableY : undefined;

Исходя из этого, вы можете создать свою собственную функцию, например getValue(request, 'body.variableX.variableY'), для возврата нуля, если какой-либо родитель иликонечное значение не определено:

// asumes the value in the path is either object or undefined
function getValue(rootObj, path = '') {
    const parts = key.split('.');
    let value = rootObj || {};
    let part;
    while ((part = parts.shift()) && value !== null) {
        value = value[part] || null;
    }
    return value;
};
0 голосов
/ 04 октября 2018

Вы можете выбрать другой подход, отметьте req.body, прежде чем достигнете checkVariables:

let body = req.body;

// data - your req.body
// requiredKeys - is an array of strings , [ key1, key2 ... keyN]  | string[]

     const setKeys = ( data, requiredKeys )=>{

         if( !typeof requiredKeys.length ){
            requiredKeys = [];
         }

         if(requiredKeys.length) requiredKeys.forEach( k =>{

             k = k.replace(/\+/g,'/');

             let keysList = [];

             if( /\/+/g.test(k)){
               keysList = k.split('/');
             }else{
              keysList = [k];
             }

             let [firstKey, ...rest] = keysList;

             if( typeof data[firstKey] === 'undefined' ){
               data[firstKey] = {};
             }

             if( rest.length ){

                data[firstKey] = setKeys(data[firstKey], [rest.join('/')] );

             }

         })

         return data;

      }

let checkedData= setKeys(body, ['variableT','variableP/noname/emptyObj','custom/object/does/not/exist/but/it/will/be/created/here']);

const checkVariables = Joi.validate(checkedData, schema);

ОБНОВЛЕНИЕ

Ниже вы найдете рабочий пример покак все должно работать во время / (скажем, / usersStatus /: id) запроса:

const express = require('express')
const app = express()
const port = 3000

const setKeys = (data, requiredKeys) => {

  if (!typeof requiredKeys.length) {
    requiredKeys = [];
  }

  if (requiredKeys.length) requiredKeys.forEach(k => {

    k = k.replace(/\+/g, '/');

    let keysList = [];

    if (/\/+/g.test(k)) {
      keysList = k.split('/');
    } else {
      keysList = [k];
    }

    let [firstKey, ...rest] = keysList;

    if (typeof data[firstKey] === 'undefined') {
      data[firstKey] = {};
    }

    if (rest.length) {

      data[firstKey] = setKeys(data[firstKey], [rest.join('/')]);

    }

  })

  return data;

}

/**
 * Mock some data
 */
const getUserData = (req, res, next) => {

  if (typeof req.body === 'undefined') {
    req.body = {};
  }

  req.body = {
    variableY: {
      someName: 23
    },
    variableZ: {
      name: 3,
      type: {
        id: 5,
        typeName: 'something',
        tags: ['a', 'b', 'c']
      }
    }
  };

  console.log('Middleware 1 getUserData');

  next();

}

/**
 * 1. Setup our middleware for checking keys
 *    "requiredKeys" is an array of strings
 */
const middlewareSetKeys = (requiredKeys, wrappedMiddleware) => {

  return (req, res, next) => {

    console.log('Middleware 2 middlewareSetKeys');

    if (typeof req.body === "undefined") {
      console.log('Leaving Middleware 2 since we don\'t have req.body');
      next();
    }

    /**
     *  Update "req.body" with keys that we want to have available
     *  in our next middleware
     */
    req.body = setKeys(req.body, requiredKeys);

    if (typeof wrappedMiddleware === 'function') {

      return wrappedMiddleware.call(this, req, res, next);

    } else {
      next();
    }

  }

}

/**
 *  2. Let's assume a "user status" situation
 *      2.1.  We need userInfo from database
 *      2.2.  Some info won't be retrieved, unless the user accesed some parts of the website to trigger some mechanisms that allows those fields to be exposed, therefore the lack of keys
 *      2.3.  But we know those keys/objects, and we still want to be present so our code won't crash.
 */

// lets call our getUserData
app.get(
  '/', // this path is for some userInfo
  getUserData, // this returns userInfo and appends it to `req.data`
  middlewareSetKeys([
    'userActivity/daily/jobs', // these won't exist in getUserData because the user is lazy and he didn't apply for any JOBS
    'userStatus/active/two-weeks-ago', // these won't exist in getUserData because the user joined two days ago. BUT WE STILL NEED IT coz reazons.
  ]), // We set our desired-later-to-use keys
  (req, res, next) => {

    /**
     * 3. Now our req.body will have our keys 
     *    even if they didn't exist in the getUserData middleware
     */
    console.log('Middleware 3 Your middleware');

    console.log(req.body);
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify(req.body, null, 2))

  })

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
...