<commit_before>"use strict";

const Endpoints = require("./Endpoints");
const HTTPS = require("https");
const MultipartData = require("../util/MultipartData");
const SequentialBucket = require("../util/SequentialBucket");
const Zlib = require("zlib");

/**
* Handles API requests
*/
class RequestHandler {
    constructor(client, forceQueueing) {
        this._client = client;
        this.baseURL = Endpoints.BASE_URL;
        this.userAgent = `DiscordBot (https://github.com/abalabahaha/eris, ${require("../../package.json").version})`;
        this.ratelimits = {};
        this.latencyRef = {
            latency: 500,
            raw: [500, 500, 500, 500, 500, 500, 500, 500, 500, 500],
            timeOffset: 0,
            timeOffsets: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            lastTimeOffsetCheck: 0
        };
        this.globalBlock = false;
        this.readyQueue = [];
        if(forceQueueing) {
            this.globalBlock = true;
            this._client.once("shardPreReady", () => this.globalUnblock());
        }
    }

    globalUnblock() {
        this.globalBlock = false;
        while(this.readyQueue.length > 0) {
            this.readyQueue.shift()();
        }
    }

    routefy(url) {
        var route = url.replace(/\/([a-z-]+)\/(?:[0-9]{17,19})/g, function(match, p) {
            return p === "channels" || p === "guilds" || p === "webhooks" ? match : `/${p}/:id`;
        }).replace(/\/reactions\/[^/]+/g, "/reactions/:id").replace(/^\/webhooks\/(\d+)\/[A-Za-z0-9-_]{64,}/, "/webhooks/$1/:token");
    }

    /**
    * Make an API request
    * @arg {String} method Uppercase HTTP method
    * @arg {String} url URL of the endpoint
    * @arg {Boolean} auth Whether to add the Authorization header and token or not
    * @arg {Object} [body] Request payload
    * @arg {Object} [file] File object
    * @arg {String} file.file A buffer containing file data
    * @arg {String} file.name What to name the file
    * @returns {Promise<Object>} Resolves with the returned JSON data
    */
    request(method, url, auth, body, file, _route, short) {
        var route = _route || this.routefy(url);

        return new Promise((resolve, reject) => {
            var attempts = 0;

            var actualCall = (cb) => {
                var headers = {
                    "User-Agent": this.userAgent,
                    "Accept-Encoding": "gzip,deflate"
                };
                var data;

                try {
                    if(auth) {
                        headers.Authorization = this._client.token;
                    }
                    if(body && body.reason) { // Audit log reason sniping
                        if(body.reason === decodeURI(body.reason)) {
                            body.reason = encodeURIComponent(body.reason);
                        }
                        headers["X-Audit-Log-Reason"] = body.reason;
                        delete body.reason;
                    }
                    if(body && body.queryReason) {
                        body.reason = body.queryReason;
                        delete body.queryReason;
                    }
                    var finalURL = url;
                    if(file) {
                        if(Array.isArray(file)) {
                            data = new MultipartData();
                            headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
                            file.forEach(function(f) {
                                if(!f.file) {
                                    return;
                                }
                                data.attach(f.name, f.file, f.name);
                            });
                            if(body) {
                                data.attach("payload_json", body);
                            }
                            data = data.finish();
                        } else if(file.file) {
                            data = new MultipartData();
                            headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
                            data.attach("file", file.file, file.name);
                            if(body) {
                                data.attach("payload_json", body);
                            }
                            data = data.finish();
                        } else {
                            throw new Error("Invalid file object");
                        }
                    } else if(body) {
                        if(method === "GET" || (method === "PUT" && url.includes("/bans/"))) { // TODO remove PUT case when devs fix
                            var qs = "";
                            Object.keys(body).forEach(function(key) {
                                if(body[key] != undefined) {
                                     if(Array.isArray(body[key])) {
                                        body[key].forEach(function(val) {
                                            qs += `&${encodeURIComponent(key)}=${encodeURIComponent(val)}`;
                                        });
                                    } else {
                                        qs += `&${encodeURIComponent(key)}=${encodeURIComponent(body[key])}`;
                                    }
                                }
                            });
                            finalURL += "?" + qs.substring(1);
                        } else {
                            data = JSON.stringify(body);
                            headers["Content-Type"] = "application/json";
                        }
                    }
                } catch(err) {
                    cb();
                    reject(err);
                    return;
                }

                var req = HTTPS.request({
                    method: method,
                    host: "discordapp.com",
                    path: this.baseURL + finalURL,
                    headers: headers
                });

                var reqError;

                req.once("abort", () => {
                    cb();
                    reqError = reqError || new Error(`Request aborted by client on ${method} ${url}`);
                    reqError.req = req;
                    reject(reqError);
                }).once("aborted", () => {
                    cb();
                    reqError = reqError || new Error(`Request aborted by server on ${method} ${url}`);
                    reqError.req = req;
                    reject(reqError);
                }).once("error", (err) => {
                    reqError = err;
                    req.abort();
                });

                var latency = Date.now();

                req.once("response", (resp) => {
                    latency = Date.now() - latency;
                    this.latencyRef.raw.push(latency);
                    this.latencyRef.latency = ~~(this.latencyRef.latency - this.latencyRef.raw.shift() / 10 + latency / 10);

                    if(this.latencyRef.lastTimeOffsetCheck < Date.now() - 5000) {
                        var timeOffset = ~~((this.latencyRef.lastTimeOffsetCheck = Date.now()) - Date.parse(resp.headers["date"]));
                        if(this.latencyRef.timeOffset - this.latencyRef.latency >= 1000 && timeOffset - this.latencyRef.latency >= 1000) {
                            this._client.emit("error", new Error(`Your clock is ${this.latencyRef.timeOffset}ms behind Discord's server clock. Please check your connection and system time.`));
                        }
                        this.latencyRef.timeOffset = ~~(this.latencyRef.timeOffset - this.latencyRef.timeOffsets.shift() / 10 + timeOffset / 10);
                        console.log(timeOffset, this.latencyRef.timeOffset, Date.parse(resp.headers["date"]));
                        this.latencyRef.timeOffsets.push(timeOffset);
                    }

                    var response = "";

                    var _respStream = resp;
                    if(resp.headers["content-encoding"]) {
                        if(~resp.headers["content-encoding"].indexOf("gzip")) {
                            _respStream = resp.pipe(Zlib.createGunzip());
                        } else if(~resp.headers["content-encoding"].indexOf("deflate")) {
                            _respStream = resp.pipe(Zlib.createInflate());
                        }
                    }

                    _respStream.on("data", (str) => {
                        response += str;
                    }).once("end", () => {
                        var now = Date.now();

                        if(resp.headers["x-ratelimit-limit"]) {
                            this.ratelimits[route].limit = +resp.headers["x-ratelimit-limit"];
                        }

                        this.ratelimits[route].remaining = resp.headers["x-ratelimit-remaining"] === undefined ? 1 : +resp.headers["x-ratelimit-remaining"] || 0;

                        if(resp.headers["retry-after"]) {
                            if(resp.headers["x-ratelimit-global"]) {
                                this.globalBlock = true;
                                setTimeout(() => this.globalUnblock(), +resp.headers["retry-after"] || 1);
                            } else {
                                this.ratelimits[route].reset = (+resp.headers["retry-after"] || 1) + now;
                            }
                        } else if(resp.headers["x-ratelimit-reset"]) {
                            if(route.endsWith("/reactions/:id")) {
                                this.ratelimits[route].reset = now + Math.max(this.latencyRef.timeOffset, 250);
                            } else {
                                this.ratelimits[route].reset = Math.max(+resp.headers["x-ratelimit-reset"] * 1000 + this.latencyRef.timeOffset, now);
                            }
                        } else {
                            this.ratelimits[route].reset = now;
                        }

                        if(resp.statusCode !== 429) {
                            this._client.emit("debug", `${body && body.content} ${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - now}ms left)`);
                            // this._client.emit("debug", resp.headers["x-ratelimit-limit"] + " " + resp.headers["x-ratelimit-remaining"] + " " + resp.headers["x-ratelimit-reset"]);
                        }

                        if(resp.statusCode >= 300) {
                            if(resp.statusCode === 429) {
                                this._client.emit("warn", `${resp.headers["x-ratelimit-global"] ? "Global" : "Unexpected"} 429 (╯°□°）╯︵ ┻━┻: ${response}\n${body && body.content} ${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - now}ms left)`);
                                if(resp.headers["retry-after"]) {
                                    setTimeout(() => {
                                        cb();
                                        this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
                                    }, +resp.headers["retry-after"]);
                                    return;
                                } else {
                                    cb();
                                    this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
                                    return;
                                }
                            } else if(resp.statusCode === 502 && ++attempts < 4) {
                                this._client.emit("warn", "A wild 502 appeared! Thanks CloudFlare!");
                                setTimeout(() => {
                                    this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
                                }, Math.floor(Math.random() * 1900 + 100));
                                return cb();
                            }
                            cb();
                            var err = new Error(`${resp.statusCode} ${resp.statusMessage} on ${method} ${url}\n\n${response.substring(0, 200)}`);
                            err.resp = resp;
                            err.response = response;
                            err.req = req;
                            reject(err);
                            return;
                        }
                        if(response.length > 0) {
                            if(resp.headers["content-type"] === "application/json") {
                                try {
                                    response = JSON.parse(response);
                                } catch(err) {
                                    cb();
                                    reject(err);
                                    return;
                                }
                            }
                        }
                        cb();
                        resolve(response);
                    });
                });

                req.setTimeout(15000, function() {
                    reqError = new Error(`Request timed out (>15000ms) on ${method} ${url}`);
                    req.abort();
                });

                if(Array.isArray(data)) {
                    for(var chunk of data) {
                        req.write(chunk);
                    }
                    req.end();
                } else {
                    req.end(data);
                }
            };

            if(this.globalBlock && auth) {
                this.readyQueue.push(() => {
                    if(!this.ratelimits[route]) {
                        this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
                    }
                    this.ratelimits[route].queue(actualCall, short);
                });
            } else {
                if(!this.ratelimits[route]) {
                    this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
                }
                this.ratelimits[route].queue(actualCall, short);
            }
        });
    }
}

module.exports = RequestHandler;
<commit_msg>Warn when unexpectedly missing ratelimit header<commit_after>"use strict";

const Endpoints = require("./Endpoints");
const HTTPS = require("https");
const MultipartData = require("../util/MultipartData");
const SequentialBucket = require("../util/SequentialBucket");
const Zlib = require("zlib");

/**
* Handles API requests
*/
class RequestHandler {
    constructor(client, forceQueueing) {
        this._client = client;
        this.baseURL = Endpoints.BASE_URL;
        this.userAgent = `DiscordBot (https://github.com/abalabahaha/eris, ${require("../../package.json").version})`;
        this.ratelimits = {};
        this.latencyRef = {
            latency: 500,
            raw: [500, 500, 500, 500, 500, 500, 500, 500, 500, 500],
            timeOffset: 0,
            timeOffsets: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            lastTimeOffsetCheck: 0
        };
        this.globalBlock = false;
        this.readyQueue = [];
        if(forceQueueing) {
            this.globalBlock = true;
            this._client.once("shardPreReady", () => this.globalUnblock());
        }
    }

    globalUnblock() {
        this.globalBlock = false;
        while(this.readyQueue.length > 0) {
            this.readyQueue.shift()();
        }
    }

    routefy(url) {
        var route = url.replace(/\/([a-z-]+)\/(?:[0-9]{17,19})/g, function(match, p) {
            return p === "channels" || p === "guilds" || p === "webhooks" ? match : `/${p}/:id`;
        }).replace(/\/reactions\/[^/]+/g, "/reactions/:id").replace(/^\/webhooks\/(\d+)\/[A-Za-z0-9-_]{64,}/, "/webhooks/$1/:token");
    }

    /**
    * Make an API request
    * @arg {String} method Uppercase HTTP method
    * @arg {String} url URL of the endpoint
    * @arg {Boolean} auth Whether to add the Authorization header and token or not
    * @arg {Object} [body] Request payload
    * @arg {Object} [file] File object
    * @arg {String} file.file A buffer containing file data
    * @arg {String} file.name What to name the file
    * @returns {Promise<Object>} Resolves with the returned JSON data
    */
    request(method, url, auth, body, file, _route, short) {
        var route = _route || this.routefy(url);

        return new Promise((resolve, reject) => {
            var attempts = 0;

            var actualCall = (cb) => {
                var headers = {
                    "User-Agent": this.userAgent,
                    "Accept-Encoding": "gzip,deflate"
                };
                var data;

                try {
                    if(auth) {
                        headers.Authorization = this._client.token;
                    }
                    if(body && body.reason) { // Audit log reason sniping
                        if(body.reason === decodeURI(body.reason)) {
                            body.reason = encodeURIComponent(body.reason);
                        }
                        headers["X-Audit-Log-Reason"] = body.reason;
                        delete body.reason;
                    }
                    if(body && body.queryReason) {
                        body.reason = body.queryReason;
                        delete body.queryReason;
                    }
                    var finalURL = url;
                    if(file) {
                        if(Array.isArray(file)) {
                            data = new MultipartData();
                            headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
                            file.forEach(function(f) {
                                if(!f.file) {
                                    return;
                                }
                                data.attach(f.name, f.file, f.name);
                            });
                            if(body) {
                                data.attach("payload_json", body);
                            }
                            data = data.finish();
                        } else if(file.file) {
                            data = new MultipartData();
                            headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
                            data.attach("file", file.file, file.name);
                            if(body) {
                                data.attach("payload_json", body);
                            }
                            data = data.finish();
                        } else {
                            throw new Error("Invalid file object");
                        }
                    } else if(body) {
                        if(method === "GET" || (method === "PUT" && url.includes("/bans/"))) { // TODO remove PUT case when devs fix
                            var qs = "";
                            Object.keys(body).forEach(function(key) {
                                if(body[key] != undefined) {
                                     if(Array.isArray(body[key])) {
                                        body[key].forEach(function(val) {
                                            qs += `&${encodeURIComponent(key)}=${encodeURIComponent(val)}`;
                                        });
                                    } else {
                                        qs += `&${encodeURIComponent(key)}=${encodeURIComponent(body[key])}`;
                                    }
                                }
                            });
                            finalURL += "?" + qs.substring(1);
                        } else {
                            data = JSON.stringify(body);
                            headers["Content-Type"] = "application/json";
                        }
                    }
                } catch(err) {
                    cb();
                    reject(err);
                    return;
                }

                var req = HTTPS.request({
                    method: method,
                    host: "discordapp.com",
                    path: this.baseURL + finalURL,
                    headers: headers
                });

                var reqError;

                req.once("abort", () => {
                    cb();
                    reqError = reqError || new Error(`Request aborted by client on ${method} ${url}`);
                    reqError.req = req;
                    reject(reqError);
                }).once("aborted", () => {
                    cb();
                    reqError = reqError || new Error(`Request aborted by server on ${method} ${url}`);
                    reqError.req = req;
                    reject(reqError);
                }).once("error", (err) => {
                    reqError = err;
                    req.abort();
                });

                var latency = Date.now();

                req.once("response", (resp) => {
                    latency = Date.now() - latency;
                    this.latencyRef.raw.push(latency);
                    this.latencyRef.latency = ~~(this.latencyRef.latency - this.latencyRef.raw.shift() / 10 + latency / 10);

                    if(this.latencyRef.lastTimeOffsetCheck < Date.now() - 5000) {
                        var timeOffset = ~~((this.latencyRef.lastTimeOffsetCheck = Date.now()) - Date.parse(resp.headers["date"]));
                        if(this.latencyRef.timeOffset - this.latencyRef.latency >= 1000 && timeOffset - this.latencyRef.latency >= 1000) {
                            this._client.emit("error", new Error(`Your clock is ${this.latencyRef.timeOffset}ms behind Discord's server clock. Please check your connection and system time.`));
                        }
                        this.latencyRef.timeOffset = ~~(this.latencyRef.timeOffset - this.latencyRef.timeOffsets.shift() / 10 + timeOffset / 10);
                        console.log(timeOffset, this.latencyRef.timeOffset, Date.parse(resp.headers["date"]));
                        this.latencyRef.timeOffsets.push(timeOffset);
                    }

                    var response = "";

                    var _respStream = resp;
                    if(resp.headers["content-encoding"]) {
                        if(~resp.headers["content-encoding"].indexOf("gzip")) {
                            _respStream = resp.pipe(Zlib.createGunzip());
                        } else if(~resp.headers["content-encoding"].indexOf("deflate")) {
                            _respStream = resp.pipe(Zlib.createInflate());
                        }
                    }

                    _respStream.on("data", (str) => {
                        response += str;
                    }).once("end", () => {
                        var now = Date.now();

                        if(resp.headers["x-ratelimit-limit"]) {
                            this.ratelimits[route].limit = +resp.headers["x-ratelimit-limit"];
                        }

                        if((resp.headers["x-ratelimit-remaining"] == undefined || resp.headers["x-ratelimit-limit"] == undefined) && this.ratelimits[route].limit !== 1) {
                            this._client.emit("warn", "Missing ratelimit headers for SequentialBucket with non-default limit\n"
                                + "x-ratelimit-remaining = " + resp.headers["x-ratelimit-remaining"] + "\n"
                                + "x-ratelimit-limit = " + resp.headers["x-ratelimit-limit"] + "\n"
                                + "x-ratelimit-reset = " + resp.headers["x-ratelimit-reset"] + "\n"
                                + "x-ratelimit-global = " + resp.headers["x-ratelimit-global"]);
                        }

                        this.ratelimits[route].remaining = resp.headers["x-ratelimit-remaining"] === undefined ? 1 : +resp.headers["x-ratelimit-remaining"] || 0;

                        if(resp.headers["retry-after"]) {
                            if(resp.headers["x-ratelimit-global"]) {
                                this.globalBlock = true;
                                setTimeout(() => this.globalUnblock(), +resp.headers["retry-after"] || 1);
                            } else {
                                this.ratelimits[route].reset = (+resp.headers["retry-after"] || 1) + now;
                            }
                        } else if(resp.headers["x-ratelimit-reset"]) {
                            if(route.endsWith("/reactions/:id")) {
                                this.ratelimits[route].reset = now + Math.max(this.latencyRef.timeOffset, 250);
                            } else {
                                this.ratelimits[route].reset = Math.max(+resp.headers["x-ratelimit-reset"] * 1000 + this.latencyRef.timeOffset, now);
                            }
                        } else {
                            this.ratelimits[route].reset = now;
                        }

                        if(resp.statusCode !== 429) {
                            this._client.emit("debug", `${body && body.content} ${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - now}ms left)`);
                            // this._client.emit("debug", resp.headers["x-ratelimit-limit"] + " " + resp.headers["x-ratelimit-remaining"] + " " + resp.headers["x-ratelimit-reset"]);
                        }

                        if(resp.statusCode >= 300) {
                            if(resp.statusCode === 429) {
                                this._client.emit("warn", `${resp.headers["x-ratelimit-global"] ? "Global" : "Unexpected"} 429 (╯°□°）╯︵ ┻━┻: ${response}\n${body && body.content} ${now} ${route} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - now}ms left)`);
                                if(resp.headers["retry-after"]) {
                                    setTimeout(() => {
                                        cb();
                                        this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
                                    }, +resp.headers["retry-after"]);
                                    return;
                                } else {
                                    cb();
                                    this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
                                    return;
                                }
                            } else if(resp.statusCode === 502 && ++attempts < 4) {
                                this._client.emit("warn", "A wild 502 appeared! Thanks CloudFlare!");
                                setTimeout(() => {
                                    this.request(method, url, auth, body, file, route, true).then(resolve).catch(reject);
                                }, Math.floor(Math.random() * 1900 + 100));
                                return cb();
                            }
                            cb();
                            var err = new Error(`${resp.statusCode} ${resp.statusMessage} on ${method} ${url}\n\n${response.substring(0, 200)}`);
                            err.resp = resp;
                            err.response = response;
                            err.req = req;
                            reject(err);
                            return;
                        }
                        if(response.length > 0) {
                            if(resp.headers["content-type"] === "application/json") {
                                try {
                                    response = JSON.parse(response);
                                } catch(err) {
                                    cb();
                                    reject(err);
                                    return;
                                }
                            }
                        }
                        cb();
                        resolve(response);
                    });
                });

                req.setTimeout(15000, function() {
                    reqError = new Error(`Request timed out (>15000ms) on ${method} ${url}`);
                    req.abort();
                });

                if(Array.isArray(data)) {
                    for(var chunk of data) {
                        req.write(chunk);
                    }
                    req.end();
                } else {
                    req.end(data);
                }
            };

            if(this.globalBlock && auth) {
                this.readyQueue.push(() => {
                    if(!this.ratelimits[route]) {
                        this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
                    }
                    this.ratelimits[route].queue(actualCall, short);
                });
            } else {
                if(!this.ratelimits[route]) {
                    this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
                }
                this.ratelimits[route].queue(actualCall, short);
            }
        });
    }
}

module.exports = RequestHandler;
<|endoftext|>


<commit_before>var Botkit = require(__dirname + '/CoreBot.js');
var express = require('express');
var bodyParser = require('body-parser');
var querystring = require('querystring');
var request = require('request');

function TeamsBot(configuration) {
  var controller = Botkit(configuration || {});

  var token = null;

  request(
    {
      uri: 'https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token',
      form: {
        grant_type: 'client_credentials',
        client_id: configuration.client_id,
        client_secret: configuration.client_secret,
        scope: 'https://api.botframework.com/.default'
      }
    },
    function(err, res, body) {
      var json = JSON.parse(body);
      token = json.access_token;
      console.log('TOKEN', token);
    }
  );

  controller.defineBot(function(botkit, config) {
    var bot = {
      type: 'teams',
      botkit: botkit,
      config: config || {},
      utterances: botkit.utterances
    };

    bot.startConversation = function(message, cb) {
      botkit.startConversation(this, message, cb);
    };

    bot.createConversation = function(message, cb) {
      botkit.createConversation(this, message, cb);
    };

    bot.openConvo = function(src, members, cb) {
        var serviceUrl = src.serviceUrl + 'v3/conversations';
        var data = {
          isGroup: true,
          bot: src.recipient,
          members: members,
          channelData: src.channelData,
          // title: 'new convo title',
        };

        console.log('MAKING REQUEST:');
        console.log(serviceUrl, data);

        request(
          {
            method: 'POST',
            json: true,
            headers: {
              'content-type': 'application/json',
              Authorization: 'Bearer ' + token
            },
            body: data,
            uri: serviceUrl
          },
          function(err, res, body) {
            if (body.error) {
              cb(body.error);
            } else {
              cb(err, body);
            }
          }
        );
    };

    bot.send = function(message, cb) {
      var serviceUrl = message.serviceUrl;

      var serviceUrl =
        serviceUrl +
        '/v3/conversations/' +
        message.conversation.id +
        '/activities';

      var data = {
        type: 'message',
        recipient: message.recipient,
        from: message.from,
      //  conversation: message.conversation,
        // textFormat: 'string',
        text: message.text,
        attachments: message.attachments
      };

      console.log('REQUEST TO',serviceUrl);

      request(
        {
          method: 'POST',
          json: true,
          headers: {
            'content-type': 'application/json',
            Authorization: 'Bearer ' + token
          },
          body: data,
          uri: serviceUrl
        },
        function(err, res, body) {

          if (err && cb) {
            return cb(er);
          }
          if (!body) {
            if (cb) { return cb('Error parsing json response'); }
          }

          if (body.error) {
            if (cb) { return cb(body.error) }
          }

          if (cb) { cb(null, body); }

        }
      );
    };

    bot.replyToComposeExtension = function(src, resp, cb){
      // console.log('src: ', src);
      src.http_res.send(resp)
      if(cb){
        cb();
      }
    }

    bot.replyInThread = function(src, resp, cb) {

      // make sure this does NOT include the activity id
      src.original_message.conversation = src.channelData.channel;

      bot.reply(src, resp, cb);

    }

    bot.reply = function(src, resp, cb) {
      if(src.type === 'composeExtension'){
        bot.replyToComposeExtension(src, resp, cb)
      }
      if (typeof resp == 'string') {
        resp = {
          text: resp
        };
      }

      resp.serviceUrl = src.original_message.serviceUrl;
      resp.from = src.original_message.recipient;
      resp.recipient = src.original_message.from;
      resp.channel = src.channel;
      resp.conversation = src.original_message.conversation;

      bot.say(resp, cb);
    };

    bot.findConversation = function(message, cb) {
      botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
      for (var t = 0; t < botkit.tasks.length; t++) {
        for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
          if (
            botkit.tasks[t].convos[c].isActive() &&
            botkit.tasks[t].convos[c].source_message.user == message.user
          ) {
            botkit.debug('FOUND EXISTING CONVO!');
            cb(botkit.tasks[t].convos[c]);
            return;
          }
        }
      }

      cb();
    };
    return bot;
  });

  controller.setupWebserver = function(port, cb) {
    if (!port) {
      throw new Error('Cannot start webserver without a port');
    }
    if (isNaN(port)) {
      throw new Error('Specified port is not a valid number');
    }

    var static_dir = process.cwd() + '/public';

    if (
      controller.config &&
      controller.config.webserver &&
      controller.config.webserver.static_dir
    )
      static_dir = controller.config.webserver.static_dir;

    controller.config.port = port;

    controller.webserver = express();
    controller.webserver.use(bodyParser.json());
    controller.webserver.use(bodyParser.urlencoded({ extended: true }));
    controller.webserver.use(express.static(static_dir));

    var server = controller.webserver.listen(
      controller.config.port,
      controller.config.hostname,
      function() {
        controller.log(
          '** Starting webserver on port ' + controller.config.port
        );
        if (cb) {
          cb(null, controller.webserver);
        }
      }
    );

    return controller;
  };

  controller.configureIncomingWebhook = function() {
    controller.webserver.post('/teams/receive', function(req, res) {

      var bot = controller.spawn({});
      var message = req.body;
      bot.serviceUrl = message.serviceUrl;

      controller.ingest(bot, message, res);

    });
  };

  controller.middleware.ingest.use(function(bot, message, res, next) {

      res.status(200);
      if (message.name != 'composeExtension/query') {
        // send a result back immediately
        res.send('');
      }

      message.http_res = res;
      next();

  });

  controller.middleware.normalize.use(function(bot, message, next) {

    message.user = message.from.id;
    message.channel = message.conversation.id;

    next();

  });

  controller.middleware.categorize.use(function(bot, message, next){

    if (message.type === 'invoke' && message.name === 'composeExtension/query'){
      message.type = 'composeExtension';
      console.log('message.value:', JSON.stringify(message.value));
      // message.value: {"commandId":"exampleCmd","parameters":[{"name":"keyword","value":"dddd"}],"queryOptions":{"skip":0,"count":25}}
      var query_params = message.value.parameters.filter(function(f){
        return f.name == 'keyword';
      });
      if(query_params.length){
        message.text = query_params[0].value;
      }
    }

    next();

  });

  controller.middleware.categorize.use(function(bot, message, next) {

    if (message.type == 'message') message.type = 'message_received';

    if (!message.conversation.isGroup && message.type == 'message_received') {
        message.type = 'direct_message';
    } else if (message.conversation.isGroup && message.type == 'message_received') {

        // start by setting this to a mention, meaning that the bot's name was _somewhere_ in the string
        message.type = 'mention';

        // check to see if this is a direct mention ,meaning bot was mentioned at start of string
        for (var e = 0; e < message.entities.length; e++) {
          var entity = message.entities[0];
          if (entity.type == 'mention' && message.text) {
            var pattern = new RegExp(message.recipient.id);
            if (entity.mentioned.id.match(pattern)) {
              var clean = new RegExp('^' + entity.text+'\\s+');
              if (message.text.match(clean)) {
                message.text = message.text.replace(clean,'');
                message.type = 'direct_mention';
              }
            }
          }
        }
    }

    next();
  });

  // controller.middleware.receive.use(function(bot, message, next) {
  //   console.log('RECEIVING ', message.type);
  //   next();
  //
  // });
  //
  // controller.middleware.send.use(function(bot, message, next) {
  //   console.log('SENDING ', JSON.stringify(message,null,2));
  //   next();
  // });

  controller.startTicking();
  return controller;
}

module.exports = TeamsBot;
<commit_msg>fixed a thing<commit_after>var Botkit = require(__dirname + '/CoreBot.js');
var express = require('express');
var bodyParser = require('body-parser');
var querystring = require('querystring');
var request = require('request');

function TeamsBot(configuration) {
  var controller = Botkit(configuration || {});

  var token = null;

  request(
    {
      uri: 'https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token',
      form: {
        grant_type: 'client_credentials',
        client_id: configuration.client_id,
        client_secret: configuration.client_secret,
        scope: 'https://api.botframework.com/.default'
      }
    },
    function(err, res, body) {
      var json = JSON.parse(body);
      token = json.access_token;
      console.log('TOKEN', token);
    }
  );

  controller.defineBot(function(botkit, config) {
    var bot = {
      type: 'teams',
      botkit: botkit,
      config: config || {},
      utterances: botkit.utterances
    };

    bot.startConversation = function(message, cb) {
      botkit.startConversation(this, message, cb);
    };

    bot.createConversation = function(message, cb) {
      botkit.createConversation(this, message, cb);
    };

    bot.openConvo = function(src, members, cb) {
        var serviceUrl = src.serviceUrl + 'v3/conversations';
        var data = {
          isGroup: true,
          bot: src.recipient,
          members: members,
          channelData: src.channelData,
          // title: 'new convo title',
        };

        console.log('MAKING REQUEST:');
        console.log(serviceUrl, data);

        request(
          {
            method: 'POST',
            json: true,
            headers: {
              'content-type': 'application/json',
              Authorization: 'Bearer ' + token
            },
            body: data,
            uri: serviceUrl
          },
          function(err, res, body) {
            if (body.error) {
              cb(body.error);
            } else {
              cb(err, body);
            }
          }
        );
    };

    bot.send = function(message, cb) {
      var serviceUrl = message.serviceUrl;

      var serviceUrl =
        serviceUrl +
        '/v3/conversations/' +
        message.conversation.id +
        '/activities';

      var data = {
        type: 'message',
        recipient: message.recipient,
        from: message.from,
      //  conversation: message.conversation,
        // textFormat: 'string',
        text: message.text,
        attachments: message.attachments
      };

      console.log('REQUEST TO',serviceUrl);

      request(
        {
          method: 'POST',
          json: true,
          headers: {
            'content-type': 'application/json',
            Authorization: 'Bearer ' + token
          },
          body: data,
          uri: serviceUrl
        },
        function(err, res, body) {

          if (err && cb) {
            return cb(er);
          }
          if (!body) {
            if (cb) { return cb('Error parsing json response'); }
          }

          if (body.error) {
            if (cb) { return cb(body.error) }
          }

          if (cb) { cb(null, body); }

        }
      );
    };

    bot.replyToComposeExtension = function(src, resp, cb){
      // console.log('src: ', src);
      src.http_res.send(resp)
      if(cb){
        cb();
      }
    }

    bot.replyInThread = function(src, resp, cb) {

      // make sure this does NOT include the activity id
      src.original_message.conversation = src.channelData.channel;

      bot.reply(src, resp, cb);

    }

    bot.reply = function(src, resp, cb) {
      if(src.type === 'composeExtension'){
        bot.replyToComposeExtension(src, resp, cb)
      }
      if (typeof resp == 'string') {
        resp = {
          text: resp
        };
      }

      resp.serviceUrl = src.original_message.serviceUrl;
      resp.from = src.original_message.recipient;
      resp.recipient = src.original_message.from;
      resp.channel = src.channel;
      resp.conversation = src.original_message.conversation;

      bot.say(resp, cb);
    };

    bot.findConversation = function(message, cb) {
      botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
      for (var t = 0; t < botkit.tasks.length; t++) {
        for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
          if (
            botkit.tasks[t].convos[c].isActive() &&
            botkit.tasks[t].convos[c].source_message.user == message.user
          ) {
            botkit.debug('FOUND EXISTING CONVO!');
            cb(botkit.tasks[t].convos[c]);
            return;
          }
        }
      }

      cb();
    };
    return bot;
  });

  controller.setupWebserver = function(port, cb) {
    if (!port) {
      throw new Error('Cannot start webserver without a port');
    }
    if (isNaN(port)) {
      throw new Error('Specified port is not a valid number');
    }

    var static_dir = process.cwd() + '/public';

    if (
      controller.config &&
      controller.config.webserver &&
      controller.config.webserver.static_dir
    )
      static_dir = controller.config.webserver.static_dir;

    controller.config.port = port;

    controller.webserver = express();
    controller.webserver.use(bodyParser.json());
    controller.webserver.use(bodyParser.urlencoded({ extended: true }));
    controller.webserver.use(express.static(static_dir));

    var server = controller.webserver.listen(
      controller.config.port,
      controller.config.hostname,
      function() {
        controller.log(
          '** Starting webserver on port ' + controller.config.port
        );
        if (cb) {
          cb(null, controller.webserver);
        }
      }
    );

    return controller;
  };

  controller.configureIncomingWebhook = function() {
    controller.webserver.post('/teams/receive', function(req, res) {

      var bot = controller.spawn({});
      var message = req.body;
      bot.serviceUrl = message.serviceUrl;

      controller.ingest(bot, message, res);

    });
  };

  controller.middleware.ingest.use(function(bot, message, res, next) {

      res.status(200);
      if (message.name != 'composeExtension/query') {
        // send a result back immediately
        res.send('');
      }

      message.http_res = res;
      next();

  });

  controller.middleware.normalize.use(function(bot, message, next) {

    message.user = message.from.id;
    message.channel = message.conversation.id;

    next();

  });

  controller.middleware.categorize.use(function(bot, message, next){

    if (message.type === 'invoke' && message.name === 'composeExtension/query'){
      message.type = 'composeExtension';
      console.log('message.value:', JSON.stringify(message.value));
      // message.value: {"commandId":"exampleCmd","parameters":[{"name":"keyword","value":"dddd"}],"queryOptions":{"skip":0,"count":25}}
      var query_params = message.value.parameters.filter(function(f){
        return f.name == 'keyword';
      });
      if(query_params.length > 0){
        message.text = query_params[0].value;
      }
    }

    next();

  });

  controller.middleware.categorize.use(function(bot, message, next) {

    if (message.type == 'message') message.type = 'message_received';

    if (!message.conversation.isGroup && message.type == 'message_received') {
        message.type = 'direct_message';
    } else if (message.conversation.isGroup && message.type == 'message_received') {

        // start by setting this to a mention, meaning that the bot's name was _somewhere_ in the string
        message.type = 'mention';

        // check to see if this is a direct mention ,meaning bot was mentioned at start of string
        for (var e = 0; e < message.entities.length; e++) {
          var entity = message.entities[0];
          if (entity.type == 'mention' && message.text) {
            var pattern = new RegExp(message.recipient.id);
            if (entity.mentioned.id.match(pattern)) {
              var clean = new RegExp('^' + entity.text+'\\s+');
              if (message.text.match(clean)) {
                message.text = message.text.replace(clean,'');
                message.type = 'direct_mention';
              }
            }
          }
        }
    }

    next();
  });

  // controller.middleware.receive.use(function(bot, message, next) {
  //   console.log('RECEIVING ', message.type);
  //   next();
  //
  // });
  //
  // controller.middleware.send.use(function(bot, message, next) {
  //   console.log('SENDING ', JSON.stringify(message,null,2));
  //   next();
  // });

  controller.startTicking();
  return controller;
}

module.exports = TeamsBot;
<|endoftext|>


<commit_before>
/**
 This is borrowed from angularJS project. Great stuff.
 */

function isObject(value) { return value !== null && typeof value == 'object'; }

function isNumber(value) { return typeof value == 'number'; }

function isDate(value) {
  return toString.apply(value) == '[object Date]';
}

function isArray(value) {
  return toString.apply(value) == '[object Array]';
}

function isFunction(value) { return typeof value == 'function'; }

function isString(value) { return typeof value == 'string'; }

/**
 * @ngdoc function
 * @name angular.forEach
 * @function
 *
 * @description
 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
 * is the value of an object property or an array element and `key` is the object property key or
 * array element index. Specifying a `context` for the function is optional.
 *
 * Note: this function was previously known as `angular.foreach`.
 *

     var values = {name: 'misko', gender: 'male'};
     var log = [];
     angular.forEach(values, function(value, key){
       this.push(key + ': ' + value);
     }, log);
     expect(log).toEqual(['name: misko', 'gender:male']);

 *
 * @param {Object|Array} obj Object to iterate over.
 * @param {Function} iterator Iterator function.
 * @param {Object=} context Object to become context (`this`) for the iterator function.
 * @returns {Object|Array} Reference to `obj`.
 */
function forEach(obj, iterator, context) {
  var key;
  if (obj) {
    if (isFunction(obj)){
      for (key in obj) {
        if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
          iterator.call(context, obj[key], key);
        }
      }
    } else if (obj.forEach && obj.forEach !== forEach) {
      obj.forEach(iterator, context);
    } else if (isObject(obj) && isNumber(obj.length)) {
      for (key = 0; key < obj.length; key++)
        iterator.call(context, obj[key], key);
    } else {
      for (key in obj) {
        if (obj.hasOwnProperty(key)) {
          iterator.call(context, obj[key], key);
        }
      }
    }
  }
  return obj;
}

function capitaliseFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}

/**
 * @ngdoc function
 * @name angular.extend
 * @function
 *
 * @description
 * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
 * to `dst`. You can specify multiple `src` objects.
 *
 * @param {Object} dst Destination object.
 * @param {...Object} src Source object(s).
 */
function extend(dst) {
  forEach(arguments, function(obj){
    if (obj !== dst) {
      forEach(obj, function(value, key){
        dst[key] = value;
      });
    }
  });
  return dst;
}

/**
 * @ngdoc function
 * @name angular.copy
 * @function
 *
 * @description
 * Creates a deep copy of `source`, which should be an object or an array.
 *
 * * If no destination is supplied, a copy of the object or array is created.
 * * If a destination is provided, all of its elements (for array) or properties (for objects)
 *   are deleted and then all elements/properties from the source are copied to it.
 * * If  `source` is not an object or array, `source` is returned.
 *
 * Note: this function is used to augment the Object type in Angular expressions. See
 * {@link ng.$filter} for more information about Angular arrays.
 *
 * @param {*} source The source that will be used to make a copy.
 *                   Can be any type, including primitives, `null`, and `undefined`.
 * @param {(Object|Array)=} destination Destination into which the source is copied. If
 *     provided, must be of the same type as `source`.
 * @returns {*} The copy or updated `destination`, if `destination` was specified.
 */
function copy(source, destination) {
  if (!destination) {
    destination = source;
    if (source) {
      if (isArray(source)) {
        destination = copy(source, []);
      } else if (isDate(source)) {
        destination = new Date(source.getTime());
      } else if (isObject(source)) {
        destination = copy(source, {});
      }
    }
  } else {
    if (source === destination) throw Error("Can't copy equivalent objects or arrays");
    if (isArray(source)) {
      while(destination.length) {
        destination.pop();
      }
      for ( var i = 0; i < source.length; i++) {
        destination.push(copy(source[i]));
      }
    } else {
      forEach(destination, function(value, key){
        delete destination[key];
      });
      for ( var key in source) {
        destination[key] = copy(source[key]);
      }
    }
  }
  return destination;
}

/*
 Not too DRY. Temporary fix.
 */

function copyOwn(source, destination) {
  if (!destination) {
    destination = source;
    if (source) {
      if (isArray(source)) {
        destination = copyOwn(source, []);
      } else if (isDate(source)) {
        destination = new Date(source.getTime());
      } else if (isObject(source)) {
        destination = copyOwn(source, {});
      }
    }
  } else {
    if (source === destination) throw Error("Can't copy equivalent objects or arrays");
    if (isArray(source)) {
      destination.length = 0;
      for ( var i = 0; i < source.length; i++) {
        destination.push(copyOwn(source[i]));
      }
    } else {
      forEach(destination, function(value, key){
        delete destination[key];
      });
      for ( var key in source) {
        if (source.hasOwnProperty(key)) {
          destination[key] = copyOwn(source[key]);
        }
      }
    }
  }
  return destination;
}

module.exports = exports = {
  isArray: isArray,
  isDate: isDate,
  isObject: isObject,
  isFunction: isFunction,
  isString: isString,
  forEach: forEach,
  copy: copy,
  copyOwn: copyOwn,
  extend: extend,
  capitaliseFirstLetter: capitaliseFirstLetter
};
<commit_msg>+concat<commit_after>
/**
 This is borrowed from angularJS project. Great stuff.
 */

function isObject(value) { return value !== null && typeof value == 'object'; }

function isNumber(value) { return typeof value == 'number'; }

function isDate(value) {
  return toString.apply(value) == '[object Date]';
}

function isArray(value) {
  return toString.apply(value) == '[object Array]';
}

function isFunction(value) { return typeof value == 'function'; }

function isString(value) { return typeof value == 'string'; }

/**
 * @ngdoc function
 * @name angular.forEach
 * @function
 *
 * @description
 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
 * is the value of an object property or an array element and `key` is the object property key or
 * array element index. Specifying a `context` for the function is optional.
 *
 * Note: this function was previously known as `angular.foreach`.
 *

     var values = {name: 'misko', gender: 'male'};
     var log = [];
     angular.forEach(values, function(value, key){
       this.push(key + ': ' + value);
     }, log);
     expect(log).toEqual(['name: misko', 'gender:male']);

 *
 * @param {Object|Array} obj Object to iterate over.
 * @param {Function} iterator Iterator function.
 * @param {Object=} context Object to become context (`this`) for the iterator function.
 * @returns {Object|Array} Reference to `obj`.
 */
function forEach(obj, iterator, context) {
  var key;
  if (obj) {
    if (isFunction(obj)){
      for (key in obj) {
        if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
          iterator.call(context, obj[key], key);
        }
      }
    } else if (obj.forEach && obj.forEach !== forEach) {
      obj.forEach(iterator, context);
    } else if (isObject(obj) && isNumber(obj.length)) {
      for (key = 0; key < obj.length; key++)
        iterator.call(context, obj[key], key);
    } else {
      for (key in obj) {
        if (obj.hasOwnProperty(key)) {
          iterator.call(context, obj[key], key);
        }
      }
    }
  }
  return obj;
}

function capitaliseFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
}

/**
 * @ngdoc function
 * @name angular.extend
 * @function
 *
 * @description
 * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
 * to `dst`. You can specify multiple `src` objects.
 *
 * @param {Object} dst Destination object.
 * @param {...Object} src Source object(s).
 */
function extend(dst) {
  forEach(arguments, function(obj){
    if (obj !== dst) {
      forEach(obj, function(value, key){
        dst[key] = value;
      });
    }
  });
  return dst;
}

/**
 * @ngdoc function
 * @name angular.copy
 * @function
 *
 * @description
 * Creates a deep copy of `source`, which should be an object or an array.
 *
 * * If no destination is supplied, a copy of the object or array is created.
 * * If a destination is provided, all of its elements (for array) or properties (for objects)
 *   are deleted and then all elements/properties from the source are copied to it.
 * * If  `source` is not an object or array, `source` is returned.
 *
 * Note: this function is used to augment the Object type in Angular expressions. See
 * {@link ng.$filter} for more information about Angular arrays.
 *
 * @param {*} source The source that will be used to make a copy.
 *                   Can be any type, including primitives, `null`, and `undefined`.
 * @param {(Object|Array)=} destination Destination into which the source is copied. If
 *     provided, must be of the same type as `source`.
 * @returns {*} The copy or updated `destination`, if `destination` was specified.
 */
function copy(source, destination) {
  if (!destination) {
    destination = source;
    if (source) {
      if (isArray(source)) {
        destination = copy(source, []);
      } else if (isDate(source)) {
        destination = new Date(source.getTime());
      } else if (isObject(source)) {
        destination = copy(source, {});
      }
    }
  } else {
    if (source === destination) throw Error("Can't copy equivalent objects or arrays");
    if (isArray(source)) {
      while(destination.length) {
        destination.pop();
      }
      for ( var i = 0; i < source.length; i++) {
        destination.push(copy(source[i]));
      }
    } else {
      forEach(destination, function(value, key){
        delete destination[key];
      });
      for ( var key in source) {
        destination[key] = copy(source[key]);
      }
    }
  }
  return destination;
}

/*
 Not too DRY. Temporary fix.
 */

function copyOwn(source, destination) {
  if (!destination) {
    destination = source;
    if (source) {
      if (isArray(source)) {
        destination = copyOwn(source, []);
      } else if (isDate(source)) {
        destination = new Date(source.getTime());
      } else if (isObject(source)) {
        destination = copyOwn(source, {});
      }
    }
  } else {
    if (source === destination) throw Error("Can't copy equivalent objects or arrays");
    if (isArray(source)) {
      destination.length = 0;
      for ( var i = 0; i < source.length; i++) {
        destination.push(copyOwn(source[i]));
      }
    } else {
      forEach(destination, function(value, key){
        delete destination[key];
      });
      for ( var key in source) {
        if (source.hasOwnProperty(key)) {
          destination[key] = copyOwn(source[key]);
        }
      }
    }
  }
  return destination;
}

function concat(destination, source) {
  var i = 0;
  var len = destination.length;
  while( i < len ) destination.push(source[i++]);
  return destination;
}

module.exports = exports = {
  capitaliseFirstLetter: capitaliseFirstLetter,
  copy: copy,
  copyOwn: copyOwn,
  concat: concat,
  extend: extend,
  forEach: forEach,
  isArray: isArray,
  isDate: isDate,
  isFunction: isFunction,
  isObject: isObject,
  isString: isString
};
<|endoftext|>


<commit_before>'use strict';

var domQuery = require('min-dom/lib/query'),
    is = require('bpmn-js/lib/util/ModelUtil').is,
    forEach = require('lodash/collection/forEach'),
    domify = require('min-dom/lib/domify'),
    Ids = require('ids');

var Utils = {};
module.exports = Utils;

var SPACE_REGEX = /\s/;

// for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
var QNAME_REGEX = /^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i;

Utils.selectedOption = function(selectBox) {
  if(selectBox.selectedIndex >= 0) {
    return selectBox.options[selectBox.selectedIndex].value;
  }
};

Utils.selectedType = function(elementSyntax, inputNode) {
  var typeSelect = domQuery(elementSyntax, inputNode);
  return this.selectedOption(typeSelect);
};

/**
 * returns the root element
 */
Utils.getRoot = function(businessObject) {
  var parent = businessObject;
  while(parent.$parent) {
    parent = parent.$parent;
  }
  return parent;
};

/**
 * filters all elements in the list which have a given type.
 * removes a new list
 */
Utils.filterElementsByType = function(objectList, type) {
  var list = objectList || [];
  var result = [];
  forEach(list, function(obj) {
    if(is(obj, type)) {
      result.push(obj);
    }
  });
  return result;
};

Utils.findRootElementsByType = function(businessObject, referencedType) {
  var root = this.getRoot(businessObject);
  return this.filterElementsByType(root.rootElements, referencedType);
};

Utils.removeAllChildren = function(domElement) {
  while(!!domElement.firstChild) {
    domElement.removeChild(domElement.firstChild);
  }
};

/**
 * adds an empty option to the list
 */
Utils.addEmptyParameter = function(list) {
  return list.push({'label': '', 'value': '', 'name': ''});
};

/**
 * returns a list with all root elements for the given parameter 'referencedType'
 */
Utils.refreshOptionsModel = function(businessObject, referencedType) {
  var model = [];
  var referableObjects = this.findRootElementsByType(businessObject, referencedType);
  forEach(referableObjects, function(obj) {
    model.push({
      label: obj.name + ' (id='+obj.id+')',
      value: obj.id,
      name: obj.name
    });
  });
  return model;
};

/**
 * fills the drop down with options
 */
Utils.updateOptionsDropDown = function(domSelector, businessObject, referencedType, entryNode) {
  var options = this.refreshOptionsModel(businessObject, referencedType);
  this.addEmptyParameter(options);
  var selectBox = domQuery(domSelector, entryNode);
  this.removeAllChildren(selectBox);
  forEach(options, function(option){
    var optionEntry = domify('<option value="' + option.value + '">' + option.label + '</option>');
    selectBox.appendChild(optionEntry);
  });
  return options;
};

/**
  * checks whether the id value is valid
  */
Utils.isIdValid = function(bo, idValue) {
  var assigned = bo.$model.ids.assigned(idValue);

  var idExists = !!assigned && assigned !== bo;

  if (!idValue || idExists) {
    return 'Element must have an unique id.';
  }

  return this.validateId(idValue);
};

Utils.validateId = function(idValue) {

  if (this.containsSpace(idValue)) {
    return 'Id must not contain spaces.';
  }

  if (!QNAME_REGEX.test(idValue)) {
    return 'Id must be a valid QName.';
  }
};

Utils.containsSpace = function(value) {
  return SPACE_REGEX.test(value);
};

/**
  * generate a semantic id with given prefix
  */
Utils.nextId = function(prefix) {
  var ids = new Ids([32,32,1]);

  return ids.nextPrefixed(prefix);
};
<commit_msg>chore(Utils): make utility library<commit_after>'use strict';

var domQuery = require('min-dom/lib/query'),
    domClear = require('min-dom/lib/clear'),
    is = require('bpmn-js/lib/util/ModelUtil').is,
    forEach = require('lodash/collection/forEach'),
    domify = require('min-dom/lib/domify'),
    Ids = require('ids');

var SPACE_REGEX = /\s/;

// for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar
var QNAME_REGEX = /^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i;

function selectedOption(selectBox) {
  if (selectBox.selectedIndex >= 0) {
    return selectBox.options[selectBox.selectedIndex].value;
  }
}

module.exports.selectedOption = selectedOption;

function selectedType(elementSyntax, inputNode) {
  var typeSelect = domQuery(elementSyntax, inputNode);
  return selectedOption(typeSelect);
}

module.exports.selectedType = selectedType;

/**
 * Retrieve the root element the document this
 * business object is contained in.
 *
 * @return {ModdleElement}
 */
function getRoot(businessObject) {
  var parent = businessObject;
  while (parent.$parent) {
    parent = parent.$parent;
  }
  return parent;
}

module.exports.getRoot = getRoot;

/**
 * filters all elements in the list which have a given type.
 * removes a new list
 */
function filterElementsByType(objectList, type) {
  var list = objectList || [];
  var result = [];
  forEach(list, function(obj) {
    if (is(obj, type)) {
      result.push(obj);
    }
  });
  return result;
}

module.exports.filterElementsByType = filterElementsByType;

function findRootElementsByType(businessObject, referencedType) {
  var root = getRoot(businessObject);

  return filterElementsByType(root.rootElements, referencedType);
}

module.exports.findRootElementsByType = findRootElementsByType;

/**
 * adds an empty option to the list
 */
function addEmptyParameter(list) {
  return list.push({'label': '', 'value': '', 'name': ''});
}

module.exports.addEmptyParameter = addEmptyParameter;

/**
 * returns a list with all root elements for the given parameter 'referencedType'
 */
function refreshOptionsModel(businessObject, referencedType) {
  var model = [];
  var referableObjects = findRootElementsByType(businessObject, referencedType);
  forEach(referableObjects, function(obj) {
    model.push({
      label: obj.name + ' (id='+obj.id+')',
      value: obj.id,
      name: obj.name
    });
  });
  return model;
}

module.exports.refreshOptionsModel = refreshOptionsModel;

/**
 * fills the drop down with options
 */
function updateOptionsDropDown(domSelector, businessObject, referencedType, entryNode) {
  var options = refreshOptionsModel(businessObject, referencedType);
  addEmptyParameter(options);
  var selectBox = domQuery(domSelector, entryNode);
  domClear(selectBox);

  forEach(options, function(option){
    var optionEntry = domify('<option value="' + option.value + '">' + option.label + '</option>');
    selectBox.appendChild(optionEntry);
  });
  return options;
}

module.exports.updateOptionsDropDown = updateOptionsDropDown;

/**
 * checks whether the id value is valid
 *
 * @param {ModdleElement} bo
 * @param {String} idValue
 *
 * @return {String} error message
 */
function isIdValid(bo, idValue) {
  var assigned = bo.$model.ids.assigned(idValue);

  var idExists = !!assigned && assigned !== bo;

  if (!idValue || idExists) {
    return 'Element must have an unique id.';
  }

  return validateId(idValue);
}

module.exports.isIdValid = isIdValid;

function validateId(idValue) {

  if (containsSpace(idValue)) {
    return 'Id must not contain spaces.';
  }

  if (!QNAME_REGEX.test(idValue)) {
    return 'Id must be a valid QName.';
  }
}

module.exports.validateId = validateId;

function containsSpace(value) {
  return SPACE_REGEX.test(value);
}

module.exports.containsSpace = containsSpace;

/**
 * generate a semantic id with given prefix
 */
function nextId(prefix) {
  var ids = new Ids([32,32,1]);

  return ids.nextPrefixed(prefix);
}

module.exports.nextId = nextId;
<|endoftext|>


<commit_before>/**
 * @jsx React.DOM
 * @preventMunge
 * @copyright Prometheus Research, LLC 2014
 */
'use strict';

var {Vector, Map, OrderedMap, Record}            = require('immutable');
var {isSchema, isList, isMapping, isScalar}      = require('./schema');
var ValidationResult                             = require('./ValidationResult');
var mergeInto                                    = require('./mergeInto');
var merge                                        = require('./merge');
var invariant                                    = require('./invariant');
var validate                                     = require('./validate');
var emptyFunction                                = require('./emptyFunction');
var toArray                                      = require('./toArray');

var DIRTY_KEY = '__react_forms_dirty__';

/**
 * Serves as a immutable container for form value attributes.
 */
var ValueAttributes = Record({
  schema: null,
  value: null,
  validation: null,
  externalValidation: null,
  serialized: null,
  onUpdate: emptyFunction,
  dirty: Map.empty()
}, 'ValueAttributes');

/**
 * Reference to the current form value.
 */
class Value {

  constructor(attributes, path) {
    this._attributes = attributes;
    this.path = path;
  }

  equals(other) {
    return (
      this.path.toString() === other.path.toString()
      && this.schema === other.schema
      && this.value === other.value
      && this.serialized === other.serialized
      && this.validation === other.validation
      && this.externalValidation === other.externalValidation
      && this.dirty === other.dirty
      && this.onUpdate === other.onUpdate
    );
  }

  child(...key) {
    var schema = this.schema.childIn(key);
    var ctor = _getCtor(schema);
    return new ctor(this._attributes, this.path.concat(key));
  }

  childIn(keyPath) {
    return this.child.apply(this, keyPath);
  }

  root() {
    if (this.path.length === 0) {
      return this;
    }
    var schema = this._attributes.schema;
    var ctor = _getCtor(schema);
    return new ctor(this._attributes, []);
  }

  update(newValue) {
    var update = this._update(newValue);
    var {value, serialized, validation, schema} = this.root();
    var path = this.path.slice();

    while (true) {
      if (path.length === this.path.length) {
        // only first update updates value and serialized
        if (path.length > 0) {
          value = value.updateIn(path, () => update.value);
          serialized = serialized.updateIn(path, () => update.serialized);
          validation = validation.updateChildIn(path, update.validation);
        } else {
          value = update.value;
          serialized = update.serialized;
          validation = update.validation;
        }

      } else if (validation.isSuccess) {
        // we need to update validation along the path if previou validation was
        // successful
        validation = validation.updateChildIn(path, validate(schema.childIn(path), value.getIn(path)).validation);
      }
      if (path.length === 0) {
        break;
      } else {
        path.pop();
      }
    }
    var dirty = this._makeDirty();
    return new this.constructor(
      this._attributes.merge({value, serialized, validation, dirty}),
      this.path
    );
  }

  updateSchema(newSchema) {
    // TODO: Should we try to keep serialized for unchanged parts of schema?
    var {_attributes: {value, externalValidation, dirty, onUpdate}} = this.root();
    return make(newSchema, value, externalValidation, onUpdate, dirty);
  }

  _makeDirty() {
    var {_attributes: {dirty}, path} = this;
    return dirty.updateIn(path.concat(DIRTY_KEY), () => true);
  }

  markDirty() {
    var dirty = this._makeDirty();
    return new this.constructor(this._attributes.merge({dirty}), this.path);
  }

  notify() {
    this._attributes.onUpdate(this);
    return this;
  }
}

Object.defineProperty(Value.prototype, 'schema', {
  get: function() {
    if (this.path.length > 0) {
      return this._attributes.schema.childIn(this.path);
    } else {
      return this._attributes.schema;
    }
  }
});

Object.defineProperty(Value.prototype, 'value', {
  get: function() {
    var {_attributes: {value, schema}, path} = this;
    if (path.length > 0) {
      for (var i = 0, len = path.length; i < len; i++) {
        schema = schema.child(path[i]);
        value = value.get(path[i], schema.defaultValue);
      }
      return value;
    } else {
      return value;
    }
  }
});

Object.defineProperty(Value.prototype, 'serialized', {
  get: function() {
    if (this.path.length > 0) {
      return this._attributes.serialized.getIn(this.path);
    } else {
      return this._attributes.serialized;
    }
  }
});

Object.defineProperty(Value.prototype, 'validation', {
  get: function() {
    var validation = this._attributes.validation;
    if (this.path.length > 0) {
      return validation.childIn(this.path);
    } else {
      return validation;
    }
  }
});

Object.defineProperty(Value.prototype, 'externalValidation', {
  get: function() {
    var {_attributes: {externalValidation}, path} = this;
    if (path.length > 0) {
      return externalValidation.childIn(this.path);
    } else {
      return externalValidation;
    }
  }
});

Object.defineProperty(Value.prototype, 'isValid', {
  get: function() {
    return this.validation.isSuccess;
  }
});

Object.defineProperty(Value.prototype, 'isDirty', {
  get: function() {
    var {_attributes: {dirty}, path} = this;
    for (var i = 0, len = path.length; i < len; i++) {
      if (dirty) {
        if (dirty.get(DIRTY_KEY) || dirty.get(path[i])) {
          return true;
        }
      } else {
        return false;
      }
      dirty = dirty.get(path[i]);
    }
    return dirty && (dirty.get(DIRTY_KEY) || dirty.length > 0);
  }
});

class ScalarValue extends Value {

  _update(newValue) {
    var {value, validation} = validate(this.schema, newValue);
    var serialized = this.schema.type.serialize(value);
    return {value, validation, serialized};
  }
}

class MappingValue extends Value {

  _update(value) {
    var {schema} = this;
    return _makeMapping({schema, value});
  }

  map(func, context) {
    return this.schema.children
      .map((_, key) => func.call(context, this.child(key)))
      .toJS();
  }

  forEach(func, context) {
    this.schema.children.forEach((_, key) => {
      return func.call(context, this.child(key), key);
    });
  }
}

class ListValue extends Value {

  _update(value) {
    var {schema} = this;
    return _makeList({schema, value});
  }

  map(func, context) {
    return this.value
      .map((_, key) => func.call(context, this.child(key), key))
      .toJS();
  }

  forEach(func, context) {
    this.value.forEach((_, key) => func.call(context, this.child(key), key));
  }

  push(...values) {
    var newValue = this.value.push.apply(this.value, values);
    return this.update(newValue);
  }

  concat(...values) {
    var newValue = this.value.concat.apply(this.value, values);
    return this.update(newValue);
  }

  splice() {
    var newValue = this.value.splice.apply(this.value, toArray(arguments));
    return this.update(newValue);
  }
}

function isValue(maybeValue) {
  return maybeValue instanceof Value;
}

function isScalarValue(value) {
  return value instanceof ScalarValue;
}

function isMappingValue(value) {
  return value instanceof MappingValue;
}

function isListValue(value) {
  return value instanceof ListValue;
}

function make(schema, value, externalValidation, onUpdate, dirty) {
  externalValidation = externalValidation || ValidationResult.success;
  dirty = dirty || Map.empty();
  var params = _make({schema, value, externalValidation});
  var ctor = _getCtor(schema);
  return new ctor(new ValueAttributes(merge(params, {onUpdate, dirty})), []);
}

function _make(args) {
  var {schema, value} = args;

  invariant(
    isSchema(schema),
    'make(schema, ...) has received not a schema value as its first argument'
  );

  if (isScalar(schema)) {
    if (value === undefined) {
      value = schema.defaultValue;
    }
    var {value, validation} = validate(schema, value);
    var serialized = schema.type.serialize(value);
    return merge(args, {value, validation, serialized});
  } else if (isMapping(schema)) {
    return _makeMapping(args);
  } else if (isList(schema)) {
    return _makeList(args);
  }
}

function _makeMapping(args) {
  var {schema, value, externalValidation} = args;
  if (value === undefined) {
    value = schema.defaultValue;
  }
  if (!(value instanceof OrderedMap)) {
    value = OrderedMap.from(value);
  }

  var _value = {};
  var _serialized = {};
  var _childrenValidation = {};
  var _hasChildError = false;
  var _validation;
  schema.children.forEach((schema, name) => {
    var {
      value: childValue,
      serialized: childSerialized,
      validation: childValidation
    } = _make({
      schema,
      value: value.get(name)
    });
    _value[name] = childValue;
    _serialized[name] = childSerialized;
    if (childValidation.isFailure) {
      _hasChildError = true;
      _childrenValidation[name] = childValidation;
    }
  });
  if (_hasChildError) {
    _validation = new ValidationResult(null, OrderedMap(_childrenValidation));
  } else {
    var {value: _value, validation: _validation} = validate(schema, _value);
  }
  return {
    schema,
    value: value.merge(_value),
    serialized: OrderedMap.from(_serialized),
    validation: _validation,
    externalValidation
  };
}

function _makeList(args) {
  var {schema, value, externalValidation} = args;
  if (value === undefined) {
    value = schema.defaultValue;
  }
  if (!(value instanceof Vector)) {
    value = Vector.from(value);
  }

  var _value = [];
  var _serialized = [];
  var _childrenValidation = {};
  var _hasChildError = false;
  var _validation;
  value.forEach((value, name) => {
    var {
      value: childValue,
      serialized: childSerialized,
      validation: childValidation
    } = _make({
      schema: schema.child(name),
      value
    });
    _value.push(childValue);
    _serialized.push(childSerialized);
    if (childValidation.isFailure) {
      _hasChildError = true;
      _childrenValidation[name] = childValidation;
    }
  });
  if (_hasChildError) {
    _validation = new ValidationResult(null, OrderedMap(_childrenValidation));
  } else {
    var {value: _value, validation: _validation} = validate(schema, _value);
  }
  return {
    schema,
    value: Vector.from(_value),
    serialized: Vector.from(_serialized),
    validation: _validation,
    externalValidation
  };
}

/**
 * Get value constructor for given schema node.
 *
 * @private
 * @param {Schema} schema
 * @returns {Constructor}
 */
function _getCtor(schema) {
  var constructor;
  if (isScalar(schema)) {
    constructor = ScalarValue;
  } else if (isMapping(schema)) {
    constructor = MappingValue;
  } else if (isList(schema)) {
    constructor = ListValue;
  } else {
    invariant(false);
  }
  return constructor;
}

module.exports = make;
mergeInto(module.exports, {
  isValue, isScalarValue, isMappingValue, isListValue
});
<commit_msg>Value: updateChild and updateChildIn methods<commit_after>/**
 * @jsx React.DOM
 * @preventMunge
 * @copyright Prometheus Research, LLC 2014
 */
'use strict';

var {Vector, Map, OrderedMap, Record}            = require('immutable');
var {isSchema, isList, isMapping, isScalar}      = require('./schema');
var ValidationResult                             = require('./ValidationResult');
var mergeInto                                    = require('./mergeInto');
var merge                                        = require('./merge');
var invariant                                    = require('./invariant');
var validate                                     = require('./validate');
var emptyFunction                                = require('./emptyFunction');
var toArray                                      = require('./toArray');

var DIRTY_KEY = '__react_forms_dirty__';

/**
 * Serves as a immutable container for form value attributes.
 */
var ValueAttributes = Record({
  schema: null,
  value: null,
  validation: null,
  externalValidation: null,
  serialized: null,
  onUpdate: emptyFunction,
  dirty: Map.empty()
}, 'ValueAttributes');

/**
 * Reference to the current form value.
 */
class Value {

  constructor(attributes, path) {
    this._attributes = attributes;
    this.path = path;
  }

  equals(other) {
    return (
      this.path.toString() === other.path.toString()
      && this.schema === other.schema
      && this.value === other.value
      && this.serialized === other.serialized
      && this.validation === other.validation
      && this.externalValidation === other.externalValidation
      && this.dirty === other.dirty
      && this.onUpdate === other.onUpdate
    );
  }

  child(...key) {
    var schema = this.schema.childIn(key);
    var ctor = _getCtor(schema);
    return new ctor(this._attributes, this.path.concat(key));
  }

  childIn(keyPath) {
    return this.child.apply(this, keyPath);
  }

  root() {
    if (this.path.length === 0) {
      return this;
    }
    var schema = this._attributes.schema;
    var ctor = _getCtor(schema);
    return new ctor(this._attributes, []);
  }

  update(newValue) {
    var update = this._update(newValue);
    var {value, serialized, validation, schema} = this.root();
    var path = this.path.slice();

    while (true) {
      if (path.length === this.path.length) {
        // only first update updates value and serialized
        if (path.length > 0) {
          value = value.updateIn(path, () => update.value);
          serialized = serialized.updateIn(path, () => update.serialized);
          validation = validation.updateChildIn(path, update.validation);
        } else {
          value = update.value;
          serialized = update.serialized;
          validation = update.validation;
        }

      } else if (validation.isSuccess) {
        // we need to update validation along the path if previou validation was
        // successful
        validation = validation.updateChildIn(path, validate(schema.childIn(path), value.getIn(path)).validation);
      }
      if (path.length === 0) {
        break;
      } else {
        path.pop();
      }
    }
    var dirty = this._makeDirty();
    return new this.constructor(
      this._attributes.merge({value, serialized, validation, dirty}),
      this.path
    );
  }

  updateChild(key, newValue) {
    return this.child(key).update(newValue).root();
  }

  updateChildIn(keyPath, newValue) {
    return this.childIn(keyPath).update(newValue).root();
  }

  updateSchema(newSchema) {
    // TODO: Should we try to keep serialized for unchanged parts of schema?
    var {_attributes: {value, externalValidation, dirty, onUpdate}} = this.root();
    return make(newSchema, value, externalValidation, onUpdate, dirty);
  }

  _makeDirty() {
    var {_attributes: {dirty}, path} = this;
    return dirty.updateIn(path.concat(DIRTY_KEY), () => true);
  }

  markDirty() {
    var dirty = this._makeDirty();
    return new this.constructor(this._attributes.merge({dirty}), this.path);
  }

  notify() {
    this._attributes.onUpdate(this);
    return this;
  }
}

Object.defineProperty(Value.prototype, 'schema', {
  get: function() {
    if (this.path.length > 0) {
      return this._attributes.schema.childIn(this.path);
    } else {
      return this._attributes.schema;
    }
  }
});

Object.defineProperty(Value.prototype, 'value', {
  get: function() {
    var {_attributes: {value, schema}, path} = this;
    if (path.length > 0) {
      for (var i = 0, len = path.length; i < len; i++) {
        schema = schema.child(path[i]);
        value = value.get(path[i], schema.defaultValue);
      }
      return value;
    } else {
      return value;
    }
  }
});

Object.defineProperty(Value.prototype, 'serialized', {
  get: function() {
    if (this.path.length > 0) {
      return this._attributes.serialized.getIn(this.path);
    } else {
      return this._attributes.serialized;
    }
  }
});

Object.defineProperty(Value.prototype, 'validation', {
  get: function() {
    var validation = this._attributes.validation;
    if (this.path.length > 0) {
      return validation.childIn(this.path);
    } else {
      return validation;
    }
  }
});

Object.defineProperty(Value.prototype, 'externalValidation', {
  get: function() {
    var {_attributes: {externalValidation}, path} = this;
    if (path.length > 0) {
      return externalValidation.childIn(this.path);
    } else {
      return externalValidation;
    }
  }
});

Object.defineProperty(Value.prototype, 'isValid', {
  get: function() {
    return this.validation.isSuccess;
  }
});

Object.defineProperty(Value.prototype, 'isDirty', {
  get: function() {
    var {_attributes: {dirty}, path} = this;
    for (var i = 0, len = path.length; i < len; i++) {
      if (dirty) {
        if (dirty.get(DIRTY_KEY) || dirty.get(path[i])) {
          return true;
        }
      } else {
        return false;
      }
      dirty = dirty.get(path[i]);
    }
    return dirty && (dirty.get(DIRTY_KEY) || dirty.length > 0);
  }
});

class ScalarValue extends Value {

  _update(newValue) {
    var {value, validation} = validate(this.schema, newValue);
    var serialized = this.schema.type.serialize(value);
    return {value, validation, serialized};
  }
}

class MappingValue extends Value {

  _update(value) {
    var {schema} = this;
    return _makeMapping({schema, value});
  }

  map(func, context) {
    return this.schema.children
      .map((_, key) => func.call(context, this.child(key)))
      .toJS();
  }

  forEach(func, context) {
    this.schema.children.forEach((_, key) => {
      return func.call(context, this.child(key), key);
    });
  }
}

class ListValue extends Value {

  _update(value) {
    var {schema} = this;
    return _makeList({schema, value});
  }

  map(func, context) {
    return this.value
      .map((_, key) => func.call(context, this.child(key), key))
      .toJS();
  }

  forEach(func, context) {
    this.value.forEach((_, key) => func.call(context, this.child(key), key));
  }

  push(...values) {
    var newValue = this.value.push.apply(this.value, values);
    return this.update(newValue);
  }

  concat(...values) {
    var newValue = this.value.concat.apply(this.value, values);
    return this.update(newValue);
  }

  splice() {
    var newValue = this.value.splice.apply(this.value, toArray(arguments));
    return this.update(newValue);
  }
}

function isValue(maybeValue) {
  return maybeValue instanceof Value;
}

function isScalarValue(value) {
  return value instanceof ScalarValue;
}

function isMappingValue(value) {
  return value instanceof MappingValue;
}

function isListValue(value) {
  return value instanceof ListValue;
}

function make(schema, value, externalValidation, onUpdate, dirty) {
  externalValidation = externalValidation || ValidationResult.success;
  dirty = dirty || Map.empty();
  var params = _make({schema, value, externalValidation});
  var ctor = _getCtor(schema);
  return new ctor(new ValueAttributes(merge(params, {onUpdate, dirty})), []);
}

function _make(args) {
  var {schema, value} = args;

  invariant(
    isSchema(schema),
    'make(schema, ...) has received not a schema value as its first argument'
  );

  if (isScalar(schema)) {
    if (value === undefined) {
      value = schema.defaultValue;
    }
    var {value, validation} = validate(schema, value);
    var serialized = schema.type.serialize(value);
    return merge(args, {value, validation, serialized});
  } else if (isMapping(schema)) {
    return _makeMapping(args);
  } else if (isList(schema)) {
    return _makeList(args);
  }
}

function _makeMapping(args) {
  var {schema, value, externalValidation} = args;
  if (value === undefined) {
    value = schema.defaultValue;
  }
  if (!(value instanceof OrderedMap)) {
    value = OrderedMap.from(value);
  }

  var _value = {};
  var _serialized = {};
  var _childrenValidation = {};
  var _hasChildError = false;
  var _validation;
  schema.children.forEach((schema, name) => {
    var {
      value: childValue,
      serialized: childSerialized,
      validation: childValidation
    } = _make({
      schema,
      value: value.get(name)
    });
    _value[name] = childValue;
    _serialized[name] = childSerialized;
    if (childValidation.isFailure) {
      _hasChildError = true;
      _childrenValidation[name] = childValidation;
    }
  });
  if (_hasChildError) {
    _validation = new ValidationResult(null, OrderedMap(_childrenValidation));
  } else {
    var {value: _value, validation: _validation} = validate(schema, _value);
  }
  return {
    schema,
    value: value.merge(_value),
    serialized: OrderedMap.from(_serialized),
    validation: _validation,
    externalValidation
  };
}

function _makeList(args) {
  var {schema, value, externalValidation} = args;
  if (value === undefined) {
    value = schema.defaultValue;
  }
  if (!(value instanceof Vector)) {
    value = Vector.from(value);
  }

  var _value = [];
  var _serialized = [];
  var _childrenValidation = {};
  var _hasChildError = false;
  var _validation;
  value.forEach((value, name) => {
    var {
      value: childValue,
      serialized: childSerialized,
      validation: childValidation
    } = _make({
      schema: schema.child(name),
      value
    });
    _value.push(childValue);
    _serialized.push(childSerialized);
    if (childValidation.isFailure) {
      _hasChildError = true;
      _childrenValidation[name] = childValidation;
    }
  });
  if (_hasChildError) {
    _validation = new ValidationResult(null, OrderedMap(_childrenValidation));
  } else {
    var {value: _value, validation: _validation} = validate(schema, _value);
  }
  return {
    schema,
    value: Vector.from(_value),
    serialized: Vector.from(_serialized),
    validation: _validation,
    externalValidation
  };
}

/**
 * Get value constructor for given schema node.
 *
 * @private
 * @param {Schema} schema
 * @returns {Constructor}
 */
function _getCtor(schema) {
  var constructor;
  if (isScalar(schema)) {
    constructor = ScalarValue;
  } else if (isMapping(schema)) {
    constructor = MappingValue;
  } else if (isList(schema)) {
    constructor = ListValue;
  } else {
    invariant(false);
  }
  return constructor;
}

module.exports = make;
mergeInto(module.exports, {
  isValue, isScalarValue, isMappingValue, isListValue
});
<|endoftext|>