Source: main/serviceJson.js


'use strict'

let $accessControl

/**
 * @typedef sesathequeJsonResponse
 * @type Object
 * @property {string} message Jamais vide (OK si rien de spécial)
 * @property {Object|undefined} [data] Le contenu de la réponse s'il y en a
 */
/**
 * Service contenant les méthodes communes aux contrôleurs qui répondent en json
 * Il garanti de toujours avoir la propriété message et éventuellement une propriété data
 * @service $json
 */
module.exports = function (component) {
  component.service('$json', function () {
    /**
     * Équivalent json de context.denied (qui renvoie du text/plain en 403), mais renvoie toujours du json,
     * avec une 401 si on est pas authentifié (403 sinon)
     * @param {Context} context
     * @param {string} [message=Authentification requise|Droits insuffisants] Le message à renvoyé, s'il n'existe pas le message par défaut dépend de context (authentifié ou pas)
     */
    function denied (context, message) {
      if (!$accessControl) $accessControl = lassi.service('$accessControl')
      if ($accessControl.isAuthenticated(context)) {
        context.status = 403
        if (!message) message = 'Droits insuffisants'
      } else {
        context.status = 401
        if (!message) message = 'Authentification requise'
      }
      context.json({ message })
    }

    /**
     * Équivalent de context.notFound en json
     * @param {Context} context
     * @param {string}  [message=Ce contenu n’existe pas]
     */
    function notFound (context, message = 'Ce contenu n’existe pas') {
      context.status = 404
      context.json({ message })
    }

    /**
     * Callback générique de sortie json
     * @param {Context} context
     * @param {string|string[]|Error} error
     * @param {object} data
     */
    function send (context, error, data) {
      if (error) sendKo(context, error)
      else context.json({ message: 'OK', data })
    }

    /**
     * Envoie une erreur en json (log error si c'est une Error)
     * @param {Context} context
     * @param {string|Error} error
     * @param {number} [status=400] si non fourni, error.status sera utilisé s'il existe
     */
    function sendKo (context, error, status) {
      let message
      if (!error) {
        log.error(Error('sendKo appelé sans erreur'))
        message = 'Erreur interne'
        status = 500
      } else if (typeof error === 'string') {
        message = error
      } else if (error instanceof Error) {
        console.error(error)
        message = error.toString()
      } else if (Array.isArray(error) && error.every(err => typeof err === 'string')) {
        message = `Il y a ${error.length} erreurs :\n- ${error.join('\n- ')}`
      } else {
        log.error(Error('erreur à envoyer en json invalide'), error)
        message = 'Requête invalide'
      }
      // on reformule certains messages comme
      // E11000 duplicate key error collection: labomep2.Utilisateur index: entity_index_structureUniqueLogin-unique-sparse dup key: { : "la valeur en double"}
      if (/duplicate key error collection/.test(message)) {
        const chunks = message.match(/duplicate key error collection.* entity_index_([^- ]+).*"([^"]*)"/)
        if (chunks && chunks.length > 2) message = `La valeur "${chunks[2]}" existe déjà (index unique ${chunks[1]})`
      }
      status = status || error.status
      // attention aux comparaisons sans cast préalable, null < 1 est true, pas de risque ici car on teste les deux sens d'inégalité
      context.status = (status > 399 && status < 600) ? Number(status) : 400
      context.json({ message })
    }

    /**
     * Callback générique de sortie json avec {message: 'OK'}, et éventuelles data
     * @param {Context} context
     * @param {object} [data] données à envoyer
     */
    function sendOk (context, data) {
      context.json({ message: 'OK', data })
    }

    return {
      denied,
      notFound,
      send,
      sendKo,
      sendOk
    }
  })
}