<commit_before>'use strict';

const Schema = require('../').Schema;
const StandardError = require('standard-error');
const ValidateError = require('./error');
const _ = require('lodash');
const applyDefaults = require('../defaults');
const checkRequired = require('../required');
const debug = require('debug')('archetype:umarshal');
const handleCast = require('./util').handleCast;
const inspect = require('util').inspect;
const join = require('./util').join;
const mpath = require('mpath');
const realPathToSchemaPath = require('./util').realPathToSchemaPath;
const shouldSkipPath = require('../util').shouldSkipPath;

module.exports = castDocument;

function markSubpaths(projection, path, val) {
  const pieces = path.split('.');
  let cur = pieces[0];
  projection[cur] = val;
  for (let i = 1; i < pieces.length; ++i) {
    cur += `.${pieces[i]}`;
    projection[cur] = val;
  }
}

function handleProjection(projection) {
  if (!projection) {
    return { $inclusive: true };
  }
  projection = _.cloneDeep(projection);
  projection.$hasExclusiveChild = {};
  let inclusive = null;
  for (const key of Object.keys(projection)) {
    if (key.startsWith('$')) {
      continue;
    }
    if (projection[key] > 0) {
      if (inclusive === true) {
        throw new Error("Can't mix inclusive and exclusive in projection");
      }
      markSubpaths(projection.$hasExclusiveChild, key, projection[key]);
      inclusive = false;
    } else {
      if (inclusive === false) {
        throw new Error("Can't mix inclusive and exclusive in projection");
      }
      inclusive = true;
    }
  }

  if (inclusive === null) {
    inclusive = true;
  }
  projection.$inclusive = inclusive;

  return projection;
}

function castDocument(obj, schema, projection) {
  projection = handleProjection(projection);
  if (obj == null) {
    throw new Error(`Can't cast null or undefined`);
  }
  applyDefaults(obj, schema, projection);
  const error = new ValidateError();
  error.merge(visitObject(obj, schema, projection, '').error);
  error.merge(checkRequired(obj, schema, projection));
  error.merge(runValidation(obj, schema, projection));
  if (error.hasError) {
    throw error;
  }
  return obj;
}

function visitArray(arr, schema, projection, path) {
  debug('visitArray', arr, path, schema);
  let error = new ValidateError();
  let curPath = realPathToSchemaPath(path);
  let newPath = join(curPath, '$');

  if (arr == null) {
    return {
      value: arr,
      error: null
    };
  }

  if (!Array.isArray(arr)) {
    arr = [arr];
  }

  if (!schema._paths[newPath] || !schema._paths[newPath].$type) {
    debug('skipping', newPath);
    return {
      value: arr,
      error: null
    };
  }

  debug('newPath', newPath, schema._paths[newPath].$type);
  arr.forEach(function(value, index) {
    const pathOptions = schema._paths[newPath];
    if (pathOptions.$transform != null) {
      try {
        arr[index] = pathOptions.$transform(arr[index]);
      } catch (err) {
        error.markError(`newPath.${index}`, err);
        return;
      }
      value = arr[index];
    }
    if (pathOptions.$type === Array ||
        Array.isArray(schema._paths[newPath].$type)) {
      let res = visitArray(value, schema, projection, join(path, index, true));
      if (res.error) {
        error.merge(res.error);
      }
      arr[index] = res.value;
      return;
    } else if (pathOptions.$type === Object) {
      let res = visitObject(value, schema, projection, join(path, index, true));
      if (res.error) {
        error.merge(res.error);
      }
      arr[index] = res.value;
      return;
    }

    try {
      handleCast(arr, index, pathOptions.$type, pathOptions.$transform);
    } catch(err) {
      error.markError(join(path, index, true), err);
    }
  });

  return {
    value: arr,
    error: (error.hasError ? error : null)
  };
}

function visitObject(obj, schema, projection, path) {
  debug('visitObject', obj, schema, projection, path);
  let error = new ValidateError();
  if (typeof obj !== 'object' || Array.isArray(obj)) {
    let err = new Error('Could not cast ' + require('util').inspect(obj) +<commit_msg>fix(unmarshal): don't use $transform on array if nested in child element<commit_after>'use strict';

const Schema = require('../').Schema;
const StandardError = require('standard-error');
const ValidateError = require('./error');
const _ = require('lodash');
const applyDefaults = require('../defaults');
const checkRequired = require('../required');
const debug = require('debug')('archetype:umarshal');
const handleCast = require('./util').handleCast;
const inspect = require('util').inspect;
const join = require('./util').join;
const mpath = require('mpath');
const realPathToSchemaPath = require('./util').realPathToSchemaPath;
const shouldSkipPath = require('../util').shouldSkipPath;

module.exports = castDocument;

function markSubpaths(projection, path, val) {
  const pieces = path.split('.');
  let cur = pieces[0];
  projection[cur] = val;
  for (let i = 1; i < pieces.length; ++i) {
    cur += `.${pieces[i]}`;
    projection[cur] = val;
  }
}

function handleProjection(projection) {
  if (!projection) {
    return { $inclusive: true };
  }
  projection = _.cloneDeep(projection);
  projection.$hasExclusiveChild = {};
  let inclusive = null;
  for (const key of Object.keys(projection)) {
    if (key.startsWith('$')) {
      continue;
    }
    if (projection[key] > 0) {
      if (inclusive === true) {
        throw new Error("Can't mix inclusive and exclusive in projection");
      }
      markSubpaths(projection.$hasExclusiveChild, key, projection[key]);
      inclusive = false;
    } else {
      if (inclusive === false) {
        throw new Error("Can't mix inclusive and exclusive in projection");
      }
      inclusive = true;
    }
  }

  if (inclusive === null) {
    inclusive = true;
  }
  projection.$inclusive = inclusive;

  return projection;
}

function castDocument(obj, schema, projection) {
  projection = handleProjection(projection);
  if (obj == null) {
    throw new Error(`Can't cast null or undefined`);
  }
  applyDefaults(obj, schema, projection);
  const error = new ValidateError();
  error.merge(visitObject(obj, schema, projection, '').error);
  error.merge(checkRequired(obj, schema, projection));
  error.merge(runValidation(obj, schema, projection));
  if (error.hasError) {
    throw error;
  }
  return obj;
}

function visitArray(arr, schema, projection, path) {
  debug('visitArray', arr, path, schema);
  let error = new ValidateError();
  let curPath = realPathToSchemaPath(path);
  let newPath = join(curPath, '$');

  if (arr == null) {
    return {
      value: arr,
      error: null
    };
  }

  if (!Array.isArray(arr)) {
    arr = [arr];
  }

  if (!schema._paths[newPath] || !schema._paths[newPath].$type) {
    debug('skipping', newPath);
    return {
      value: arr,
      error: null
    };
  }

  debug('newPath', newPath, schema._paths[newPath].$type);
  const pathOptions = schema._paths[newPath];
  arr.forEach(function(value, index) {
    if (pathOptions.$transform != null) {
      try {
        arr[index] = pathOptions.$transform(arr[index]);
      } catch (err) {
        error.markError(`newPath.${index}`, err);
        return;
      }
      value = arr[index];
    }
    if (pathOptions.$type === Array ||
        Array.isArray(schema._paths[newPath].$type)) {
      let res = visitArray(value, schema, projection, join(path, index, true));
      if (res.error) {
        error.merge(res.error);
      }
      arr[index] = res.value;
      return;
    } else if (pathOptions.$type === Object) {
      let res = visitObject(value, schema, projection, join(path, index, true));
      if (res.error) {
        error.merge(res.error);
      }
      arr[index] = res.value;
      return;
    }

    try {
      handleCast(arr, index, pathOptions.$type);
    } catch(err) {
      error.markError(join(path, index, true), err);
    }
  });

  return {
    value: arr,
    error: (error.hasError ? error : null)
  };
}

function visitObject(obj, schema, projection, path) {
  debug('visitObject', obj, schema, projection, path);
  let error = new ValidateError();
  if (typeof obj !== 'object' || Array.isArray(obj)) {
    let err = new Error('Could not cast ' + require('util').inspect(obj) +
{'fraction_non_alphanumeric': 0.15702777443789828, 'fraction_numerical': 0.0007214139713839125, 'mean_word_length': 3.289840123775142, 'pattern_counts': {'":': 0, '<': 5, '<?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': '1321764', 'n_tokens_mistral': 2572, 'n_tokens_neox': 2571, 'n_words': 629}