<commit_before>const extend = require('extend');

const uniq = require('./lib/uniq');
const parseOptions = require('./lib/parse_options');
const extract = require('./lib/extract');

function Request(opts) {
  extend(this, parseOptions(opts));
}

Request.prototype.filters = function (request) {
  return extract({
    context: request,
    contextKeysToSearch: this.requestKeysToSearch,
    // named params in the route are automatically included with the
    // user-supplied list of valid params. given a route /resource/:id
    // the param 'id' will be considered valid even if it isn't listed
    // in the paramsForfilters property.
    find: this.allowedFilters.concat(Object.keys(request.params)),
    normalizer: this.paramNormalizer
  });
};

Request.prototype.relations = function (request) {
  var result = extract({
    context: request,
    contextKeysToSearch: this.requestKeysToSearch,
    find: this.relationKey,
    normalizer: this.paramNormalizer
  }) || [];
  if (result && !Array.isArray(result)) {
    result = [result];
  }
  return uniq(result);
};

Request.prototype.responder = function (response, code, data) {
  // cheap heuristics to detect the server type
  var isExpress = !!response.send;
  var isHapi = !!response.request;
  if (!isExpress && !isHapi) {
    throw new Error('Unsupported server type!');
  }

  var prettyPrint = JSON.stringify(data, null, 2);
  var contentType = 'application/json';

  // both of these should never happen, right?
  if (isExpress) {
    response.set('content-type', contentType).status(code).send(prettyPrint);
  }
  if (isHapi) {
    response(prettyPrint).type(contentType).code(code);
  }
};

Request.prototype.create = function (opts) {
  if (!opts) {
    opts = {};
  }
  var method = opts.method || 'create';
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.create(method, request.body, function (err, data) {
      var code = 201;
      if (err) {
        code = 422;
        data = {
          errors: {
            title: 'Unprocessable Entity',
            detail: err.message
          }
        };
      }
      responder(response, code, data);
    });
  };
};

Request.prototype.read = function (opts) {
  if (!opts) {
    opts = {};
  }
  var format = this.format.bind(this);
  var responder = this.responder;
  var getFilters = this.filters.bind(this);
  var getRelations = this.relations.bind(this);
  var source = this.source;
  var include = opts.include||[];
  var pass = !!opts.pass;
  var raw = !!opts.raw;
  return function (request, response, next) {
    var filters = getFilters(request);
    var relations = getRelations(request).concat(include);
    source.read(filters, relations, opts, function (err, data) {
      var code = 200;
      if (err) {
        code = 400;
        data = {
          errors: {
            title: 'Bad Request',
            detail: err.message
          }
        };
      }
      if (!raw) {
        data = format(data);
      }
      if (pass) {
        response.data = data;
        response.code = code;
        next();
      } else {
        responder(response, code, data);
      }
    });
  };
};


Request.prototype.update = function (opts) {
  if (!opts) {
    opts = {};
  }
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.byId(request.param('id'), function (err, model) {
      if (!model) {
        responder(response, 500, {
          title: 'Internal Server Error',
          detail: 'No resource by that id found.'
        });
      } else {
        source.update(model, request.body, function (err, data) {
          var code = 200;
          if (err) {
            code = 422;
            data = {
              title: 'Unprocessable Entity',
              detail: err.message
            };
          }
          responder(response, code, data);
        });
      }
    });
  };
};

Request.prototype.destroy = function (opts) {
  if (!opts) {
    opts = {};
  }
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.byId(request.param('id'), function (err, model) {
      if (!model) {
        responder(response, 500, {
          title: 'Internal Server Error',
          detail: 'No resource by that id found.'
        });
      } else {
        source.destroy(model, function (err, data) {
          var code = 200;
          if (err) {
            code = 422;
            data = {
              title: 'Unprocessable Entity',
              detail: err.message
            };
          }
          responder(response, code, data);
        });
      }
    });
  };
};

Request.prototype.format = function (data) {
  var primaryResourceName = this.source.resourceName();
  var primaryResource = data[primaryResourceName];
  var hasLinks = (Object.keys(data).length > 1);
  var output = {};
  if (hasLinks) {
    output.linked = data;
  }
  output[primaryResourceName] = primaryResource;
  delete data[primaryResourceName];
  return output;
};
<commit_msg>handle singularizing resources<commit_after>const extend = require('extend');

const uniq = require('./lib/uniq');
const parseOptions = require('./lib/parse_options');
const extract = require('./lib/extract');

function Request(opts) {
  extend(this, parseOptions(opts));
}

Request.prototype.filters = function (request) {
  return extract({
    context: request,
    contextKeysToSearch: this.requestKeysToSearch,
    // named params in the route are automatically included with the
    // user-supplied list of valid params. given a route /resource/:id
    // the param 'id' will be considered valid even if it isn't listed
    // in the paramsForfilters property.
    find: this.allowedFilters.concat(Object.keys(request.params)),
    normalizer: this.paramNormalizer
  });
};

Request.prototype.relations = function (request) {
  var result = extract({
    context: request,
    contextKeysToSearch: this.requestKeysToSearch,
    find: this.relationKey,
    normalizer: this.paramNormalizer
  }) || [];
  if (result && !Array.isArray(result)) {
    result = [result];
  }
  return uniq(result);
};

Request.prototype.responder = function (response, code, data) {
  // cheap heuristics to detect the server type
  var isExpress = !!response.send;
  var isHapi = !!response.request;
  if (!isExpress && !isHapi) {
    throw new Error('Unsupported server type!');
  }

  var prettyPrint = JSON.stringify(data, null, 2);
  var contentType = 'application/json';

  // both of these should never happen, right?
  if (isExpress) {
    response.set('content-type', contentType).status(code).send(prettyPrint);
  }
  if (isHapi) {
    response(prettyPrint).type(contentType).code(code);
  }
};

Request.prototype.create = function (opts) {
  if (!opts) {
    opts = {};
  }
  var method = opts.method || 'create';
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.create(method, request.body, function (err, data) {
      var code = 201;
      if (err) {
        code = 422;
        data = {
          errors: {
            title: 'Unprocessable Entity',
            detail: err.message
          }
        };
      }
      responder(response, code, data);
    });
  };
};

Request.prototype.read = function (opts) {
  if (!opts) {
    opts = {};
  }
  var format = this.format.bind(this);
  var responder = this.responder;
  var getFilters = this.filters.bind(this);
  var getRelations = this.relations.bind(this);
  var source = this.source;
  var include = opts.include||[];
  var passMode = !!opts.pass;
  var rawMode = !!opts.raw;
  return function (request, response, next) {
    var filters = getFilters(request);
    var relations = getRelations(request).concat(include);
    source.read(filters, relations, opts, function (err, data) {
      var code = 200;
      if (err) {
        code = 400;
        data = {
          errors: {
            title: 'Bad Request',
            detail: err.message
          }
        };
      }
      if (!rawMode) {
        data = format(data, opts);
      }
      if (passMode) {
        response.data = data;
        response.code = code;
        next();
      } else {
        responder(response, code, data);
      }
    });
  };
};


Request.prototype.update = function (opts) {
  if (!opts) {
    opts = {};
  }
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.byId(request.param('id'), function (err, model) {
      if (!model) {
        responder(response, 500, {
          title: 'Internal Server Error',
          detail: 'No resource by that id found.'
        });
      } else {
        source.update(model, request.body, function (err, data) {
          var code = 200;
          if (err) {
            code = 422;
            data = {
              title: 'Unprocessable Entity',
              detail: err.message
            };
          }
          responder(response, code, data);
        });
      }
    });
  };
};

Request.prototype.destroy = function (opts) {
  if (!opts) {
    opts = {};
  }
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.byId(request.param('id'), function (err, model) {
      if (!model) {
        responder(response, 500, {
          title: 'Internal Server Error',
          detail: 'No resource by that id found.'
        });
      } else {
        source.destroy(model, function (err, data) {
          var code = 200;
          if (err) {
            code = 422;
            data = {
              title: 'Unprocessable Entity',
              detail: err.message
            };
          }
          responder(response, code, data);
        });
      }
    });
  };
};

Request.prototype.format = function (data, opts) {
  if (!opts) {
    opts = {};
  }
  var singleResource = !!opts.one;
  var primaryResourceName = this.source.resourceName();
  var primaryResource = data[primaryResourceName];
  var hasLinks = (Object.keys(data).length > 1);
  var output = {};
  if (hasLinks) {
    output.linked = data;
  }
  if (singleResource) {
    output[primaryResourceName] = primaryResource[0];
  } else {
    output[primaryResourceName] = primaryResource;
  }

  delete data[primaryResourceName];
  return output;
};

{'fraction_non_alphanumeric': 0.13347457627118645, 'fraction_numerical': 0.006259630200308166, 'mean_word_length': 2.356496444731739, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6477757', 'n_tokens_mistral': 3133, 'n_tokens_neox': 3055, 'n_words': 793}