Skip to content
This repository was archived by the owner on Feb 12, 2021. It is now read-only.

Error handling #135

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
function extendError(ctor) {
ctor.prototype = Object.create(Error.prototype);
ctor.prototype.isNewsflowError = true;
ctor.prototype.constructor = ctor;
}

function MissingParameterError(message) {
this.name = "MissingParameterError";
this.message = String(message);
Error.captureStackTrace(this, MissingParameterError);
}
extendError(MissingParameterError);

function InvalidParameterError(message) {
this.name = "InvalidParameterError";
this.message = String(message);
Error.captureStackTrace(this, InvalidParameterError);
}
extendError(InvalidParameterError);

function RequestError(message) {
this.name = "RequestError";
this.message = String(message || "Request error");
Error.captureStackTrace(this, ServerError);
}
extendError(RequestError);

function BadRequestError(message) {
this.name = "BadRequestError";
this.status = 400;
this.message = String(message || "Bad request");
Error.captureStackTrace(this, BadRequestError);
}
extendError(NotFoundError);

function UnauthorizedError(message) {
this.name = "AuthenticationError";
this.status = 401;
this.message = message || "Unauthorized";
Error.captureStackTrace(this, UnauthorizedError);
}
extendError(UnauthorizedError);

function ForbiddenError(message) {
this.name = "ForbiddenError";
this.status = 403;
this.message = message || "Forbidden";
Error.captureStackTrace(this, ForbiddenError);
}
extendError(ForbiddenError);

function NotFoundError(message) {
this.name = "NotFoundError";
this.status = 404;
this.message = String(message || "Not found");
Error.captureStackTrace(this, NotFoundError);
}
extendError(NotFoundError);

function ServerError(message, code) {
this.name = "ServerError";
this.status = code || 500;
this.message = String(message || "Server error");
Error.captureStackTrace(this, ServerError);
}
extendError(ServerError);

function errorFromStatusCode(code, message) {
switch(code) {
case 400:
return new BadRequestError(message);

case 401:
return new UnauthorizedError(message);

case 403:
return new ForbiddenError(message);

case 404:
return new NotFoundError(message);

default:
return new ServerError(message, code);
}
}

module.exports = exports = {
MissingParameterError,
InvalidParameterError,
RequestError,
BadRequestError,
UnauthorizedError,
ForbiddenError,
NotFoundError,
ServerError,
errorFromStatusCode
};
40 changes: 21 additions & 19 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var fd_slicer = require('fd-slicer');
var mime = require('mime');
var StreamSink = require('streamsink');
var PassThrough = require('stream').PassThrough;
var errors = require('./errors');

var MAX_PUTOBJECT_SIZE = 5 * 1024 * 1024 * 1024;
var MAX_DELETE_COUNT = 1000;
Expand Down Expand Up @@ -51,16 +52,16 @@ function Client(options) {
this.multipartDownloadSize = options.multipartDownloadSize || (15 * 1024 * 1024);

if (this.multipartUploadThreshold < MIN_MULTIPART_SIZE) {
throw new Error("Minimum multipartUploadThreshold is 5MB.");
throw new errors.InvalidParameterError("Minimum multipartUploadThreshold is 5MB.");
}
if (this.multipartUploadThreshold > MAX_PUTOBJECT_SIZE) {
throw new Error("Maximum multipartUploadThreshold is 5GB.");
throw new errors.InvalidParameterError("Maximum multipartUploadThreshold is 5GB.");
}
if (this.multipartUploadSize < MIN_MULTIPART_SIZE) {
throw new Error("Minimum multipartUploadSize is 5MB.");
throw new errors.InvalidParameterError("Minimum multipartUploadSize is 5MB.");
}
if (this.multipartUploadSize > MAX_PUTOBJECT_SIZE) {
throw new Error("Maximum multipartUploadSize is 5GB.");
throw new errors.InvalidParameterError("Maximum multipartUploadSize is 5GB.");
}
}

Expand Down Expand Up @@ -191,8 +192,9 @@ Client.prototype.uploadFile = function(params) {
multipartUploadSize = smallestPartSizeFromFileSize(localFileStat.size);
}
if (multipartUploadSize > MAX_PUTOBJECT_SIZE) {
var err = new Error("File size exceeds maximum object size: " + localFile);
var err = new errors.BadRequestError("File size exceeds maximum object size (see MAX_PUTOBJECT_SIZE): " + localFile);
err.retryable = false;
err.fileSize = multipartUploadSize;
handleError(err);
return;
}
Expand Down Expand Up @@ -318,7 +320,7 @@ Client.prototype.uploadFile = function(params) {
if (!compareMultipartETag(data.ETag, multipartETag)) {
errorOccurred = true;
uploader.progressAmount -= overallDelta;
cb(new Error("ETag does not match MD5 checksum"));
cb(new errors.RequestError("ETag does not match MD5 checksum"));
return;
}
part.ETag = data.ETag;
Expand Down Expand Up @@ -401,7 +403,7 @@ Client.prototype.uploadFile = function(params) {
uploader.progressAmount = 0;
inStream.pipe(multipartETag);
s3Params.Body = multipartETag;

self.s3.putObject(s3Params, function(err, data) {
pendCb();
if (fatalError) return;
Expand All @@ -412,7 +414,7 @@ Client.prototype.uploadFile = function(params) {
pend.wait(function() {
if (fatalError) return;
if (!compareMultipartETag(data.ETag, localFileStat.multipartETag)) {
cb(new Error("ETag does not match MD5 checksum"));
cb(new errors.RequestError("ETag does not match MD5 checksum"));
return;
}
cb(null, data);
Expand Down Expand Up @@ -463,9 +465,9 @@ Client.prototype.downloadFile = function(params) {

request.on('httpHeaders', function(statusCode, headers, resp) {
if (statusCode >= 300) {
handleError(new Error("http status code " + statusCode));
handleError(errors.errorFromStatusCode(statusCode));
return;
}
}
if (headers['content-length'] == undefined) {
var outStream = fs.createWriteStream(localFile);
outStream.on('error', handleError);
Expand All @@ -475,14 +477,14 @@ Client.prototype.downloadFile = function(params) {
downloader.progressTotal += chunk.length;
downloader.progressAmount += chunk.length;
downloader.emit('progress');
outStream.write(chunk);
outStream.write(chunk);
})

request.on('httpDone', function() {
request.on('httpDone', function() {
if (errorOccurred) return;
downloader.progressAmount += 1;
downloader.emit('progress');
outStream.end();
outStream.end();
cb();
})
} else {
Expand All @@ -504,11 +506,11 @@ Client.prototype.downloadFile = function(params) {
hashCheckPend.go(function(cb) {
multipartETag.on('end', function() {
if (multipartETag.bytes !== contentLength) {
handleError(new Error("Downloaded size does not match Content-Length"));
handleError(new errors.RequestError("Downloaded size does not match Content-Length"));
return;
}
if (eTagCount === 1 && !multipartETag.anyMatch(eTag)) {
handleError(new Error("ETag does not match MD5 checksum"));
handleError(new errors.RequestError("ETag does not match MD5 checksum"));
return;
}
cb();
Expand Down Expand Up @@ -801,7 +803,7 @@ Client.prototype.downloadBuffer = function(s3Params) {
var hashCheckPend = new Pend();
request.on('httpHeaders', function(statusCode, headers, resp) {
if (statusCode >= 300) {
handleError(new Error("http status code " + statusCode));
handleError(errors.errorFromStatusCode(statusCode));
return;
}
var contentLength = parseInt(headers['content-length'], 10);
Expand All @@ -822,11 +824,11 @@ Client.prototype.downloadBuffer = function(s3Params) {
hashCheckPend.go(function(cb) {
multipartETag.on('end', function() {
if (multipartETag.bytes !== contentLength) {
handleError(new Error("Downloaded size does not match Content-Length"));
handleError(new errors.RequestError("Downloaded size does not match Content-Length"));
return;
}
if (eTagCount === 1 && !multipartETag.anyMatch(eTag)) {
handleError(new Error("ETag does not match MD5 checksum"));
handleError(new errors.RequestError("ETag does not match MD5 checksum"));
return;
}
cb();
Expand Down Expand Up @@ -884,7 +886,7 @@ Client.prototype.downloadStream = function(s3Params) {
var hashCheckPend = new Pend();
request.on('httpHeaders', function(statusCode, headers, resp) {
if (statusCode >= 300) {
handleError(new Error("http status code " + statusCode));
handleError(errors.errorFromStatusCode(statusCode));
return;
}
downloadStream.emit('httpHeaders', statusCode, headers, resp);
Expand Down