caravaggio
Advanced tools
Comparing version 2.0.2 to 2.1.0
# Changelog | ||
## 2.1.0 | ||
- 😍 Errors are now amazing. They can be shown as text, json, html and also contain link to the relevant documentation | ||
- ✨ Support gzip/deflate compression | ||
## 2.0.2 | ||
@@ -4,0 +9,0 @@ |
@@ -126,4 +126,17 @@ /** | ||
}, | ||
compress: true, | ||
/** | ||
* Let you decide how to show errors. | ||
* It can be 'html', 'json' and 'plain' | ||
* Default to 'html' | ||
*/ | ||
errors: 'json', | ||
/** | ||
* Compress the response through deflate/gzip | ||
* The requester must add `Accept-Encoding` header otherwise this option is ignored. | ||
* By default is false because usually this behavior is delegated to CDNs | ||
*/ | ||
compress: false, | ||
}; | ||
@@ -7,3 +7,4 @@ module.exports = { | ||
defaultTransformations: [], | ||
errors: 'plain', | ||
}; | ||
{ | ||
"name": "caravaggio", | ||
"version": "2.0.2", | ||
"version": "2.1.0", | ||
"description": "A blazing fast image processor service", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -51,3 +51,3 @@ [![caravaggio logo](https://res.cloudinary.com/ramiel/image/upload/c_scale,r_0,w_100/v1517679412/caravaggio-logo_xdwpin.jpg)](https://res.cloudinary.com/ramiel/image/upload/c_scale,r_0,w_100/v1517679412/caravaggio-logo_xdwpin.jpg) | ||
Please, refer to the [documentation](https://ramiel.gitlab.io/caravaggio/docs/docs.html) to know what are the available operations | ||
Please, refer to the [documentation](https://ramiel.gitlab.io/caravaggio/docs/docs.html) to know what are the available operations. | ||
@@ -54,0 +54,0 @@ |
@@ -0,9 +1,11 @@ | ||
const BadRequestError = require('./errors/BadRequestError'); | ||
/** | ||
* Create a set of chainable validators on values | ||
*/ | ||
const numberValidators = (parsed, errorMessage) => { | ||
const numberValidators = (parsed, errorMessage, docUri) => { | ||
const valuedValidators = { | ||
min: (min) => { | ||
if (parsed < min) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
@@ -15,3 +17,3 @@ return valuedValidators; | ||
if (parsed > max) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
@@ -23,3 +25,3 @@ return valuedValidators; | ||
if (parsed % divisor !== 0) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
@@ -34,7 +36,7 @@ return valuedValidators; | ||
const stringValidators = (parsed, errorMessage) => { | ||
const stringValidators = (parsed, errorMessage, docUri) => { | ||
const valueValidators = { | ||
enum: (accept = []) => { | ||
if (accept.indexOf(parsed) === -1) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
@@ -45,3 +47,3 @@ return valueValidators; | ||
if (!regex.test(parsed)) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
@@ -55,7 +57,7 @@ return valueValidators; | ||
const boolValidators = (parsed, errorMessage) => { | ||
const boolValidators = (parsed, errorMessage, docUri) => { | ||
const valueValidators = { | ||
isTrue: () => { | ||
if (parsed !== true) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
@@ -66,3 +68,3 @@ return valueValidators; | ||
if (parsed !== false) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
@@ -76,3 +78,3 @@ return valueValidators; | ||
module.exports = (value, errorMessage = 'The value is in the wrong format') => { | ||
module.exports = (value, errorMessage = 'The value is in the wrong format', docUri = 'docs.html') => { | ||
/** | ||
@@ -85,5 +87,5 @@ * Coherce a string to a typed value and return a set of validators on that value | ||
if (Number.isNaN(res)) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
return numberValidators(res, errorMessage); | ||
return numberValidators(res, errorMessage, docUri); | ||
}, | ||
@@ -94,5 +96,5 @@ | ||
if (Number.isNaN(res)) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
return numberValidators(res, errorMessage); | ||
return numberValidators(res, errorMessage, docUri); | ||
}, | ||
@@ -103,8 +105,8 @@ | ||
if (Number.isNaN(res)) { | ||
throw new Error(errorMessage); | ||
throw new BadRequestError(errorMessage, docUri); | ||
} | ||
return numberValidators(res, errorMessage); | ||
return numberValidators(res, errorMessage, docUri); | ||
}, | ||
toString: () => stringValidators(`${value}`, errorMessage), | ||
toString: () => stringValidators(`${value}`, errorMessage, docUri), | ||
@@ -115,3 +117,3 @@ toBool: () => { | ||
.value(); | ||
return boolValidators(res === 'true', errorMessage); | ||
return boolValidators(res === 'true', errorMessage, docUri); | ||
}, | ||
@@ -118,0 +120,0 @@ }; |
const { send } = require('micro'); | ||
const { buildDocumentationLink } = require('../utils'); | ||
module.exports = fn => async (req, res) => { | ||
try { | ||
return await fn(req, res); | ||
} catch (err) { | ||
return send(res, err.statusCode || 500, err.message || 'An unknown error happened'); | ||
const UNKNOWN_ERROR_MESSAGE = 'An unknown error happened :('; | ||
const buildHtmlError = err => ` | ||
<!doctype html> | ||
<html class="no-js" lang=""> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta http-equiv="x-ua-compatible" content="ie=edge"> | ||
<title>Error - Caravaggio</title> | ||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
<style> | ||
body { | ||
background-color: #4ABDAC; | ||
margin: 0; | ||
height: 100vh; | ||
} | ||
/*Typography*/ | ||
h1 { font-family: 'Droid sans', sans; font-weight: 400; font-style: italic; line-height: 44px;} | ||
p { font-family: 'Droid Sans', sans-serif; font-size: 15px; font-weight: 400; line-height: 24px; margin: 0 0 14px; } | ||
a { text-decoration: none; border-bottom: 1px solid #4c3a07; color: inherit; } | ||
a:hover { color: #fff; background: #4ABDAC; } | ||
/*/Typography*/ | ||
#container { | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
height: 100%; | ||
} | ||
#error { | ||
display:flex; | ||
flex-direction: horizontal; | ||
justify-content: flex-start; | ||
align-items:center; | ||
background-color: #FF7964; | ||
min-width: 30%; | ||
padding: 1em; | ||
margin: 0 auto; | ||
border-radius: 5px; | ||
} | ||
#logo { | ||
margin-right: 1em; | ||
} | ||
</style> | ||
</head> | ||
<body style="background-color: #4ABDAC;"> | ||
<div id="container"> | ||
<div id="error"> | ||
<div id="logo"> | ||
<img src="//res.cloudinary.com/ramiel/image/upload/v1519252402/caravaggio-logo-error_jvq4fg.png" alt="caravaggio distorted logo"/> | ||
</div> | ||
<div> | ||
<h1>Error</h1> | ||
<p>${err.message || UNKNOWN_ERROR_MESSAGE} | ||
${err.docUri ? `<br />See <a target="_blank" href="${buildDocumentationLink(err.docUri)}">${buildDocumentationLink(err.docUri)}</a>` : ''}</p> | ||
</div> | ||
</div> | ||
</div> | ||
</body> | ||
</html> | ||
`; | ||
const buildJsonError = err => ({ | ||
statusCode: err.statusCode, | ||
error: err.message || UNKNOWN_ERROR_MESSAGE, | ||
see: buildDocumentationLink(err.docUri), | ||
}); | ||
const buildErrorText = err => `${err.message || UNKNOWN_ERROR_MESSAGE}${err.docUri ? ` | ||
See ${buildDocumentationLink(err.docUri)}` : ''}`; | ||
module.exports = (config) => { | ||
let build; | ||
switch (config.get('errors')) { | ||
case 'html': | ||
build = buildHtmlError; | ||
break; | ||
case 'json': | ||
build = buildJsonError; | ||
break; | ||
case 'plain': | ||
default: | ||
build = buildErrorText; | ||
break; | ||
} | ||
return fn => async (req, res) => { | ||
try { | ||
return await fn(req, res); | ||
} catch (err) { | ||
return send(res, err.statusCode || 500, build(err)); | ||
} | ||
}; | ||
}; | ||
const cohercer = require('../cohercer'); | ||
const { buildDocumentationLink } = require('../utils'); | ||
module.exports = (value) => { | ||
const v = cohercer(value, `Blur must be a value between 0.3 and 1000. | ||
See ${buildDocumentationLink('blur.html')} | ||
`) | ||
const v = cohercer(value, 'Blur must be a value between 0.3 and 1000.', 'blur.html') | ||
.toFloat() | ||
@@ -9,0 +6,0 @@ .min(0.3) |
const cohercer = require('../cohercer'); | ||
const { buildDocumentationLink, isPercentage, percentageToPixel } = require('../utils'); | ||
const { isPercentage, percentageToPixel } = require('../utils'); | ||
module.exports = (x, y, w, h) => { | ||
const left = cohercer(x, `Extract: the x parameter in in the wrong format. | ||
See ${buildDocumentationLink('extract.html')} | ||
`) | ||
const left = cohercer(x, 'Extract: the x parameter in in the wrong format.', 'extract.html') | ||
.toNumber() | ||
.value(); | ||
const top = cohercer(y, `Extract: the y parameter in in the wrong format. | ||
See ${buildDocumentationLink('extract.html')} | ||
`) | ||
const top = cohercer(y, 'Extract: the y parameter in in the wrong format.', 'extract.html') | ||
.toNumber() | ||
.value(); | ||
const width = cohercer(w, `Extract: the width parameter in in the wrong format. | ||
See ${buildDocumentationLink('extract.html')} | ||
`) | ||
const width = cohercer(w, 'Extract: the width parameter in in the wrong format.', 'extract.html') | ||
.toNumber() | ||
.value(); | ||
const height = cohercer(h, `Extract: the width parameter in in the wrong format. | ||
See ${buildDocumentationLink('extract.html')} | ||
`) | ||
const height = cohercer(h, 'Extract: the width parameter in in the wrong format.', 'extract.html') | ||
.toNumber() | ||
@@ -27,0 +19,0 @@ .value(); |
const cohercer = require('../cohercer'); | ||
const { buildDocumentationLink } = require('../utils'); | ||
module.exports = (value) => { | ||
const operation = value && cohercer(value, `Flip accepts "x" or "y" as values. | ||
See ${buildDocumentationLink('flip.html')} | ||
`) | ||
const operation = value && cohercer(value, 'Flip accepts "x" or "y" as values.', 'flip.html') | ||
.toString() | ||
@@ -9,0 +6,0 @@ .enum(['x', 'y']) |
@@ -1,4 +0,3 @@ | ||
const { buildDocumentationLink } = require('../utils'); | ||
const UnknownOperationError = require('../errors/UnknownOperationError'); | ||
const blurNormalizer = require('./blur'); | ||
const cropNormalizer = require('./crop'); | ||
const flipNormalizer = require('./flip'); | ||
@@ -14,3 +13,2 @@ const oNormalizer = require('./o'); | ||
blur: blurNormalizer, | ||
crop: cropNormalizer, | ||
flip: flipNormalizer, | ||
@@ -27,12 +25,2 @@ o: oNormalizer, | ||
class UnknownOperation extends Error { | ||
constructor(operation) { | ||
super(`Unknown operation "${operation}" | ||
See documentation at ${buildDocumentationLink('')} | ||
`); | ||
this.statusCode = 400; | ||
} | ||
} | ||
module.exports = (config) => { | ||
@@ -61,8 +49,5 @@ const defaultTransformations = config.get('defaultTransformations'); | ||
if (!normalizers[name]) { | ||
throw new UnknownOperation(name); | ||
throw new UnknownOperationError(name); | ||
} | ||
const normalized = normalizers[name](...params); | ||
// const normalized = normalizers[name] | ||
// ? normalizers[name](...params) | ||
// : {}; | ||
return { | ||
@@ -69,0 +54,0 @@ ...acc, |
const cohercer = require('../cohercer'); | ||
const { buildDocumentationLink } = require('../utils'); | ||
module.exports = (value) => { | ||
const format = cohercer(value, `Accepted values are "original", jpg", "jpeg", "png", "webp", "tiff". | ||
See ${buildDocumentationLink('output.html')} | ||
`) | ||
const format = cohercer(value, 'Accepted values are "original", jpg", "jpeg", "png", "webp", "tiff".', 'output.html') | ||
.toString() | ||
@@ -9,0 +6,0 @@ .enum(['original', 'jpg', 'jpeg', 'png', 'webp', 'tiff']) |
const cohercer = require('../cohercer'); | ||
const { buildDocumentationLink } = require('../utils'); | ||
@@ -10,5 +9,3 @@ const getOutputType = async pipeline => (pipeline.getOptions().o !== 'original' | ||
module.exports = (value) => { | ||
const progressive = cohercer(value, `Progressive value is not valid. | ||
See ${buildDocumentationLink('progressive.html')} | ||
`) | ||
const progressive = cohercer(value, 'Progressive value is not valid.', 'progressive.html') | ||
.toBool() | ||
@@ -15,0 +12,0 @@ .value(); |
const cohercer = require('../cohercer'); | ||
const { buildDocumentationLink } = require('../utils'); | ||
@@ -37,5 +36,3 @@ const normalizeQ = value => Math.round((value * 80) / 100); | ||
module.exports = (value) => { | ||
const v = cohercer(value, `Quality must be a value between 1 and 100. | ||
See ${buildDocumentationLink('quality.html')} | ||
`) | ||
const v = cohercer(value, 'Quality must be a value between 1 and 100.', 'quality.html') | ||
.toInt() | ||
@@ -42,0 +39,0 @@ .min(1) |
@@ -1,2 +0,1 @@ | ||
const { buildDocumentationLink } = require('../../utils'); | ||
const cohercer = require('../../cohercer'); | ||
@@ -22,6 +21,4 @@ | ||
getColorFromParameter: (param, errorMessage) => { | ||
const value = cohercer(param, errorMessage || `Invalid color paramter "${param}". | ||
See ${buildDocumentationLink('resize.html#colors')} | ||
`) | ||
getColorFromParameter: (param, errorMessage = `Invalid color paramter "${param}".`, docUri = 'resize.html#colors') => { | ||
const value = cohercer(param, errorMessage, docUri) | ||
.toString() | ||
@@ -28,0 +25,0 @@ .match(COLOR_REGEX) |
@@ -1,2 +0,1 @@ | ||
const { buildDocumentationLink } = require('../../utils'); | ||
const cohercer = require('../../cohercer'); | ||
@@ -32,7 +31,5 @@ | ||
gravity, | ||
{ acceptAuto, error } = { acceptAuto: false, error: null }, | ||
{ acceptAuto, error } = { acceptAuto: false, error: `Invalid gravity parameter "${gravity}".` }, | ||
) => { | ||
let value = gravity && cohercer(gravity, error || `Invalid gravity parameter "${gravity}". | ||
See ${buildDocumentationLink('resize.html#gravity')} | ||
`) | ||
let value = gravity && cohercer(gravity, error, 'resize.html#gravity') | ||
.toString() | ||
@@ -39,0 +36,0 @@ .match(acceptAuto ? EXTENDED_GRAVITY_PARAM_REGEX : GRAVITY_PARAM_REGEX) |
const cohercer = require('../../cohercer'); | ||
const { buildDocumentationLink } = require('../../utils'); | ||
const scale = require('./scale'); | ||
@@ -58,5 +57,3 @@ const fit = require('./fit'); | ||
/* eslint-disable no-param-reassign */ | ||
size = cohercer(size, `Resize: the size parameter in in the wrong format. | ||
See ${buildDocumentationLink('resize.html#sizes')} | ||
`) | ||
size = cohercer(size, 'Resize: the size parameter is in the wrong format.', 'resize.html#sizes') | ||
.toString() | ||
@@ -66,5 +63,3 @@ .match(RESIZE_PATTERN) | ||
mode = cohercer(mode, `Resize, the mode ${mode} is not valid. | ||
See ${buildDocumentationLink('resize.html')} | ||
`) | ||
mode = cohercer(mode, `Resize, the mode ${mode} is not valid.`, 'resize.html') | ||
.toString() | ||
@@ -71,0 +66,0 @@ .enum(AVAILABLE_MODES) |
const cohercer = require('../cohercer'); | ||
const { buildDocumentationLink } = require('../utils'); | ||
module.exports = (value) => { | ||
const angle = cohercer(value, `Angle must be multiple of 90°. | ||
See ${buildDocumentationLink('rotate.html')} | ||
`) | ||
const angle = cohercer(value, 'Angle must be multiple of 90°.', 'rotate.html') | ||
.toInt() | ||
@@ -9,0 +6,0 @@ .multipleOf(90) |
@@ -17,3 +17,2 @@ const normalizerFactory = require('./normalizers'); | ||
} catch (e) { | ||
e.statusCode = 400; | ||
throw e; | ||
@@ -20,0 +19,0 @@ } |
@@ -14,3 +14,3 @@ const { router, get } = require('microrouter'); | ||
compose( | ||
errorHandler, | ||
errorHandler(config), | ||
domainWhitelist(whitelist), | ||
@@ -17,0 +17,0 @@ )(indexRoute(config)(Cache(persistor))), |
const { URL } = require('url'); | ||
const { createError } = require('micro'); | ||
const logger = require('../logger'); | ||
const parser = require('../parser'); | ||
const pipeline = require('../pipelines'); | ||
const { sendImage } = require('../sender'); | ||
const sender = require('../sender'); | ||
module.exports = (config) => { | ||
const { parseOptions } = parser(config); | ||
const { sendImage } = sender(config); | ||
@@ -18,3 +18,3 @@ return cache => async (req, res) => { | ||
logger.debug(`Cache hit for resource ${url.toString()} with options ${options.rawNormalizedOptions}`); | ||
await sendImage(resource, options, res); | ||
await sendImage(resource, options, req, res); | ||
return; | ||
@@ -26,8 +26,8 @@ } | ||
const createdResource = await cache.set(url, options, imageBuffer); | ||
await sendImage(createdResource, options, res); | ||
await sendImage(createdResource, options, req, res); | ||
} catch (e) { | ||
logger.error(e); | ||
throw createError(e.statusCode, e.message); | ||
throw e; | ||
} | ||
}; | ||
}; |
const redirect = require('micro-redirect'); | ||
const { send } = require('micro'); | ||
const path = require('path'); | ||
const config = require('config'); | ||
const sharp = require('sharp'); | ||
const compressor = require('./compressor'); | ||
const browserCache = config.get('browserCache'); | ||
const guessTypeByExtension = config.get('guessTypeByExtension'); | ||
module.exports = (config) => { | ||
const browserCache = config.get('browserCache'); | ||
const guessTypeByExtension = config.get('guessTypeByExtension'); | ||
const sendFactory = compressor(config); | ||
const getTypeByUrl = resourceName => path.extname(resourceName) | ||
.replace('.', '') | ||
.replace('jpg', 'jpeg'); | ||
const getTypeByUrl = resourceName => path.extname(resourceName) | ||
.replace('.', '') | ||
.replace('jpg', 'jpeg'); | ||
const getTypeByMetadata = async resource => sharp(resource.buffer) | ||
.metadata() | ||
.then(({ format }) => format); | ||
const getTypeByMetadata = async resource => sharp(resource.buffer) | ||
.metadata() | ||
.then(({ format }) => format); | ||
const getTypeByResource = async (resource) => { | ||
if (!guessTypeByExtension) { | ||
return getTypeByMetadata(resource); | ||
} | ||
const extension = getTypeByUrl(resource.name); | ||
if (!extension) { | ||
return getTypeByMetadata(resource); | ||
} | ||
return extension; | ||
}; | ||
const getTypeByResource = async (resource) => { | ||
if (!guessTypeByExtension) { | ||
return getTypeByMetadata(resource); | ||
} | ||
const extension = getTypeByUrl(resource.name); | ||
if (!extension) { | ||
return getTypeByMetadata(resource); | ||
} | ||
return extension; | ||
}; | ||
const getMimeType = async (resource, options) => { | ||
const type = options.o === 'original' | ||
? await getTypeByResource(resource) | ||
: options.o; | ||
return `image/${type}`; | ||
}; | ||
const getMimeType = async (resource, options) => { | ||
const type = options.o === 'original' | ||
? await getTypeByResource(resource) | ||
: options.o; | ||
return `image/${type}`; | ||
}; | ||
const getCacheControlHeader = () => browserCache && `max-age=${browserCache.maxAge}`; | ||
const getCacheControlHeader = () => browserCache && `max-age=${browserCache.maxAge}`; | ||
module.exports = { | ||
sendImage: async (resource, options, res) => { | ||
switch (resource.type) { | ||
case 'buffer': { | ||
const cacheHeader = getCacheControlHeader(); | ||
if (cacheHeader) { | ||
res.setHeader('cache-control', cacheHeader); | ||
return { | ||
sendImage: async (resource, options, req, res) => { | ||
const send = sendFactory(req); | ||
switch (resource.type) { | ||
case 'buffer': { | ||
const cacheHeader = getCacheControlHeader(); | ||
if (cacheHeader) { | ||
res.setHeader('cache-control', cacheHeader); | ||
} | ||
res.setHeader('Content-Type', await getMimeType(resource, options)); | ||
res.setHeader('Content-Length', resource.buffer.length); | ||
return send(res, 200, resource.buffer); | ||
} | ||
res.setHeader('Content-Type', await getMimeType(resource, options)); | ||
res.setHeader('Content-Length', resource.buffer.length); | ||
return send(res, 200, resource.buffer); | ||
case 'location': | ||
return redirect(res, 301, resource.location); | ||
default: | ||
throw new Error(`Invalid type of resource ${resource.type}`); | ||
} | ||
case 'location': | ||
return redirect(res, 301, resource.location); | ||
default: | ||
throw new Error(`Invalid type of resource ${resource.type}`); | ||
} | ||
}, | ||
}, | ||
}; | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59270
62
1406