forEachDocumentField = (document, schema, callback, currentPath = '') => {
  if (!document) return;

  Object.keys(document).forEach(fieldName => {
    const fieldSchema = schema[fieldName];
    callback({ fieldName, fieldSchema, currentPath, document, schema, isNested: !!currentPath });
    // Check if we need a recursive call
    if (!fieldSchema) return; // field has no corresponding schema, we are done
    const value = document[fieldName];
    if (!value) return;
    // if value is an array, validate permissions for all children
    if (_isArray(value)) {
      const arrayChildField = getArrayChild(fieldName, schema);
      if (arrayChildField) {
        const arrayFieldSchema = getNestedSchema(arrayChildField);
        // apply only if the field is an array of objects
        if (arrayFieldSchema) {
          value.forEach((item, idx) => {
            forEachDocumentField(item, arrayFieldSchema, callback, `${currentPath}${fieldName}[${idx}].`);
          });
        }
      }
      // if value is an object, run recursively
    } else if (typeof value === 'object' && !isBlackbox(fieldSchema)) {
      const nestedFieldSchema = getNestedSchema(fieldSchema);
      if (nestedFieldSchema) {
        forEachDocumentField(value, nestedFieldSchema, callback, `${currentPath}${fieldName}.`);
      }
    }
  });
}
Iterate over a document fields and run a callback with side effect
Works recursively for nested fields and arrays of objects (but excluding blackboxed objects, native JSON, and arrays of native values)
@param {*} document Current document
@param {*} schema Document schema
@param {*} callback Called on each field with the corresponding field schema, including fields of nested objects and arrays of nested object
@param {*} currentPath Global path of the document (to track recursive calls)
@param {*} isNested Differentiate nested fields