A moratorium on a series of mistakes.

I recently emplaced a solution for logging on hapi services in Pacifica

It was not smooth sailing

proxy(request, reply) {
    return reply.proxy({
      mapUri: (req, cb) => cb(null, `${this.baseUrl}${req.url.path}`, ServiceCaller.scrubHeaders(req.headers)),
      onResponse: (err, res, req, rep) => {
        if (err) {
          rep(err.output.payload).code(err.output.statusCode);
        } else {
          rep(res);
        }
      }
    });
  }

service-caller.js

My first attempt at solving this problem.

proxy(request, reply) {
    const mapUri = setMapUri(this.baseUrl);
    return reply.proxy({
      mapUri,
      onResponse
    });
  }
function onResponse(error, response, request, reply) {
  return wreck.read(response, { json: true }, (err, payload) => {
    if (err) return reply(err);
    if (payload && ajv.validate(schema, payload)) {
      return reply(create(payload.statusCode, payload.message));
    }

    return reply(payload).code(response.statusCode);
  });
}

so I added all this crap...

const Ajv = require('ajv');

const ajv = new Ajv();

const schema = {
  maxProperties: 3,
  required: ['statusCode', 'error', 'message'],
  properties: {
    statusCode: {
      type: 'number',
      minimum: 400
    },
    error: {
      type: 'string'
    },
    message: {
      type: 'string'
    }
  }
};

Until some one tried to send an array through here.

function onResponse(error, response, request, reply) {
  return wreck.read(response, { json: true }, (err, payload) => {
    const isBoomError = ajv.validate(schema, payload);
    if (err) return reply(err);

    if (payload && !is(Array, payload) && isBoomError) {
      return reply(create(payload.statusCode, payload.message));
    }

    return reply(payload).code(response.statusCode);
  });
}
function onResponse(error, response, request, reply) {
  return wreck.read(response, { json: true }, (err, payload) => {
    if (err) return reply(err);

    if (response.statusCode >= BAD_REQUEST) {
      return reply(create(payload.statusCode, payload.message));
    }

    return reply(payload).code(response.statusCode);
  });
}
'use strict';

const Ajv = require('ajv');
const niceRequest = require('nice-request');
const _ = require('lodash');
const { wrap, create } = require('boom');
const wreck = require('wreck');
const { is } = require('ramda');

const ajv = new Ajv();

const schema = {
  maxProperties: 3,
  required: ['statusCode', 'error', 'message'],
  properties: {
    statusCode: {
      type: 'number',
      minimum: 400
    },
    error: {
      type: 'string'
    },
    message: {
      type: 'string'
    }
  }
};

class ServiceCaller {
  constructor({ locator, credentials, headers, tag }) {
    this.locator = locator;
    this.headers = ServiceCaller.scrubHeaders(headers);
    this.credentials = credentials;
    this.tag = tag;
    this.baseUrl = `http://${locator.host}:${locator.port}`;
  }

  static createFromRequest(request, locator, tag) {
    return new ServiceCaller({
      locator,
      tag,
      headers: request.headers,
      credentials: request.auth.credentials
    });
  }

  static scrubHeaders(headers) {
    // whitelist specific headers
    return _.pick(headers, [
      'user-agent',
      'host',
      'mfauth',
      'x-request-id'
    ]);
  }

  appendTag(tag) {
    if (!tag) return null;
    if (!this.tag) return tag;
    return [this.tag, tag].join('.');
  }

  callService({ method, path, headers, payload, tag }) {
    const opts = {
      method,
      url: `${this.baseUrl}${path}`,
      metricTag: this.appendTag(tag),
      body: payload,
      headers: Object.assign({}, this.headers, ServiceCaller.scrubHeaders(headers))
    };

    if (this.credentials) opts.headers.mfauth = JSON.stringify(this.credentials);
    return niceRequest.request(opts)
    .catch(error => {
      throw wrap(error, error.statusCode, error.message);
    });
  }

  // convenience methods to reduce opt call requirements

  get(opts) {
    return this.callService(Object.assign({ method: 'GET' }, opts));
  }

  put(opts) {
    return this.callService(Object.assign({ method: 'PUT' }, opts));
  }

  post(opts) {
    return this.callService(Object.assign({ method: 'POST' }, opts));
  }

  del(opts) {
    return this.callService(Object.assign({ method: 'DELETE' }, opts));
  }

  proxy(request, reply) {
    const mapUri = setMapUri(this.baseUrl);
    return reply.proxy({
      mapUri,
      onResponse
    });
  }
}

function setMapUri(baseUrl) {
  return (request, cb) => cb(null, `${baseUrl}${request.url.path}`, ServiceCaller.scrubHeaders(request.headers));
}

function onResponse(error, response, request, reply) {
  return wreck.read(response, { json: true }, (err, payload) => {
    const isBoomError = ajv.validate(schema, payload);
    if (err) return reply(err);

    if (!is(Array, payload) && isBoomError) {
      return reply(create(payload.statusCode, payload.message));
    }

    return reply(payload).code(response.statusCode);
  });
}

exports.setMapUri = setMapUri;
exports.onResponse = onResponse;
exports.ServiceCaller = ServiceCaller;

3rd version. 

2nd version

'use strict';

const Ajv = require('ajv');
const niceRequest = require('nice-request');
const _ = require('lodash');
const { wrap, create } = require('boom');
const wreck = require('wreck');

const ajv = new Ajv();

const schema = {
  maxProperties: 3,
  required: ['statusCode', 'error', 'message'],
  properties: {
    statusCode: {
      type: 'number',
      minimum: 400
    },
    error: {
      type: 'string'
    },
    message: {
      type: 'string'
    }
  }
};

class ServiceCaller {
  constructor({ locator, credentials, headers, tag }) {
    this.locator = locator;
    this.headers = ServiceCaller.scrubHeaders(headers);
    this.credentials = credentials;
    this.tag = tag;
    this.baseUrl = `http://${locator.host}:${locator.port}`;
  }

  static createFromRequest(request, locator, tag) {
    return new ServiceCaller({
      locator,
      tag,
      headers: request.headers,
      credentials: request.auth.credentials
    });
  }

  static scrubHeaders(headers) {
    // whitelist specific headers
    return _.pick(headers, [
      'user-agent',
      'host',
      'mfauth',
      'x-request-id'
    ]);
  }

  appendTag(tag) {
    if (!tag) return null;
    if (!this.tag) return tag;
    return [this.tag, tag].join('.');
  }

  callService({ method, path, headers, payload, tag }) {
    const opts = {
      method,
      url: `${this.baseUrl}${path}`,
      metricTag: this.appendTag(tag),
      body: payload,
      headers: Object.assign({}, this.headers, ServiceCaller.scrubHeaders(headers))
    };

    if (this.credentials) opts.headers.mfauth = JSON.stringify(this.credentials);
    return niceRequest.request(opts)
    .catch(error => {
      throw wrap(error, error.statusCode, error.message);
    });
  }

  // convenience methods to reduce opt call requirements

  get(opts) {
    return this.callService(Object.assign({ method: 'GET' }, opts));
  }

  put(opts) {
    return this.callService(Object.assign({ method: 'PUT' }, opts));
  }

  post(opts) {
    return this.callService(Object.assign({ method: 'POST' }, opts));
  }

  del(opts) {
    return this.callService(Object.assign({ method: 'DELETE' }, opts));
  }

  proxy(request, reply) {
    const mapUri = setMapUri(this.baseUrl);
    return reply.proxy({
      mapUri,
      onResponse
    });
  }
}

function setMapUri(baseUrl) {
  return (request, cb) => cb(null, `${baseUrl}${request.url.path}`, ServiceCaller.scrubHeaders(request.headers));
}

function onResponse(error, response, request, reply) {
  return wreck.read(response, { json: true }, (err, payload) => {
    if (err) return reply(err);
    if (payload && ajv.validate(schema, payload)) {
      return reply(create(payload.statusCode, payload.message));
    }

    return reply(payload).code(response.statusCode);
  });
}

exports.setMapUri = setMapUri;
exports.onResponse = onResponse;
exports.ServiceCaller = ServiceCaller;

Original Version Original Problem

const niceRequest = require('nice-request');
const _ = require('lodash');

class ServiceCaller {
  constructor({ locator, credentials, headers, tag }) {
    this.locator = locator;
    this.headers = ServiceCaller.scrubHeaders(headers);
    this.credentials = credentials;
    this.tag = tag;
    this.baseUrl = `http://${locator.host}:${locator.port}`;
  }

  static createFromRequest(request, locator, tag) {
    return new ServiceCaller({
      locator,
      tag,
      headers: request.headers,
      credentials: request.auth.credentials
    });
  }

  static scrubHeaders(headers) {
    // whitelist specific headers
    return _.pick(headers, [
      'user-agent',
      'host',
      'mfauth'
    ]);
  }

  appendTag(tag) {
    if (!tag) return null;
    if (!this.tag) return tag;
    return [this.tag, tag].join('.');
  }

  callService({ method, path, headers, payload, tag }) {
    const opts = {
      method,
      url: `${this.baseUrl}${path}`,
      metricTag: this.appendTag(tag),
      body: payload,
      headers: Object.assign({}, this.headers, ServiceCaller.scrubHeaders(headers))
    };

    if (this.credentials) opts.headers.mfauth = JSON.stringify(this.credentials);

    return niceRequest.request(opts);
  }

  // convenience methods to reduce opt call requirements

  get(opts) {
    return this.callService(Object.assign({ method: 'GET' }, opts));
  }

  put(opts) {
    return this.callService(Object.assign({ method: 'PUT' }, opts));
  }

  post(opts) {
    return this.callService(Object.assign({ method: 'POST' }, opts));
  }

  del(opts) {
    return this.callService(Object.assign({ method: 'DELETE' }, opts));
  }

  proxy(request, reply) {
    return reply.proxy({
      mapUri: (req, cb) => cb(null, `${this.baseUrl}${req.url.path}`, ServiceCaller.scrubHeaders(req.headers)),
      onResponse: (err, res, req, rep) => {
        if (err) {
          rep(err.output.payload).code(err.output.statusCode);
        } else {
          rep(res);
        }
      }
    });
  }
}

module.exports = ServiceCaller;

Mistake # Too large of a PR

Title Text

  • PR too large - Set reviewers up for failure. 
  • Mixed fixes in with current FB instead of doubling back to main rebranching, fixing, merging, then rebasing my FB back onto main after fix was accepted.
  • Because fixes where mixed in with larger unrelated body of work no one was able to have a proper discussion about my fix. No one asked the golden question, "What problem am I trying to solve."

Weak Process, no feed back.

  • No one called me out for the large PR
  • No one called me out for mixing fixes in with larger body of work. 

deck

By bobbiebarker

deck

  • 918