vgno/data-shaper

View on GitHub
lib/resolve-fragment.js

Summary

Maintainability
A
1 hr
Test Coverage
'use strict';

var async = require('async');
var merge = require('lodash.merge');

var helpers = require('./helpers');
var splitReference = helpers.splitReference;
var startsWith = helpers.startsWith;

function createFragmentHandler(options, shape, id, cb) {
    return function handleFragment(fetchErr, fragmentData) {
        if (fetchErr) {
            return cb(fetchErr);
        }

        // We got null back from fetchData. Create data for fragment
        // with null value and pass it to the callback
        if (fragmentData === null) {
            var nullFragmentData = {};
            nullFragmentData[shape.collectionName + '::' + id] = null;

            return cb(null, {
                data: nullFragmentData,
                shape: shape,
                id: id
            });
        }

        // Shape the data for the fragment
        options.shapeData(fragmentData, shape, options, function(err, shapedFragmentData) {
            if (err) {
                return cb(err);
            }

            cb(err, {
                data: shapedFragmentData,
                shape: shape,
                id: id
            });
        });
    };
}

/**
 * Resolves a property specified using a shape fragment
 *
 * The options object must contain the following functions;
 * resolveValue, resolveFragment and fetchData.
 *
 * @param {object} data Object to get property data from
 * @param {object} fragment Fragment definition
 * @param {object} options Options object with functions to use in the shaping
 * @param {function} callback
 */
function resolveFragment(data, fragment, options, callback) {
    var resolveValue = options.resolveValue;
    var fetchData = options.fetchData;

    var reference = fragment.reference;
    var shape = fragment.shape;

    // Split the reference and get the last part
    var referenceParts = splitReference(reference);
    var propertyName = referenceParts[referenceParts.length - 1];

    // Resolve the value for the reference
    resolveValue(data, reference, options, function(resolveErr, value) {
        if (resolveErr) {
            callback(resolveErr);
            return;
        }

        if (typeof value === 'object') {
            var keys = Object.keys(value);

            // Got a single object back
            if (typeof value[keys[0]] !== 'object') {
                options.shapeData(value, shape, options, function(err, shapedFragmentData) {
                    if (err) {
                        return callback(err);
                    }

                    callback(err, {
                        data: shapedFragmentData,
                        shape: shape,
                        id: value.id
                    });
                });
                return;
            }

            // Got multiple objects
            async.map(keys, function(id, cb) {
                createFragmentHandler(options, shape, id, cb)(null, value[id]);
            }, function(err, res) {
                if (err) {
                    return callback(err);
                }

                var fragmentData = merge.apply(null, res.map(function(item) {
                    return item.data;
                }));

                var entityFilter = startsWith(fragment.shape.collectionName + '::');
                callback(null, {
                    data: fragmentData,
                    shape: shape,
                    id: Object.keys(fragmentData).filter(entityFilter).map(function(key) {
                        return fragmentData[key] && fragmentData[key].id;
                    }).filter(Boolean)
                });
            });
            return;
        }

        // Fetch the data for the shape
        fetchData(value, propertyName, createFragmentHandler(options, shape, value, callback));
    });
}

module.exports = resolveFragment;