Last active
July 26, 2022 07:46
-
-
Save ishiduca/412c6b53676781b3b69056cb225280f9 to your computer and use it in GitHub Desktop.
httpResponseの処理とビジネスロジック処理を分離させるインターフェイスについて
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
get('/article/:id', [ 200, 'html' ], function * (args) { | |
var { param } = args | |
var { id } = param | |
var article = yield api.getArticle(id) | |
var author = yield api.getAuthor(article.author.id) | |
return yo`<section> | |
<article>${JSON.stringify(article)}</article> | |
<footer>${JSON.stringify(author)}</footer> | |
</section>` | |
}) | |
post('/new/article', [ 201, 'json' ], body(reqSchema, function * (args) { | |
var { param } = args | |
var { json } = param | |
var id = createID() | |
var json.created_at = Date.now() | |
yield api.createNewArticle(id, json) | |
var author = yield api.getAuthor(json.author.id) | |
return { ...json, id, author } | |
}) | |
post('/rpc/v1', [ 200, 'json' ], body(rpc(api, { schemas }))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Ajv = require('ajv') | |
var addFormats = require('ajv-formats') | |
var jsonrpc2requestSchema = require('./jsonrpc-request') | |
var rpcErrors = require('./rpc-errors') | |
var ajv = new Ajv({ strict: false }) | |
addFormats(ajv) | |
module.exports = function bindRpc (api = null, opts = { schemas: null }) { | |
var { schemas } = opts | |
if (schemas == null) throw new Error('"schemas" not found') | |
if (api == null) throw new Error('"api" not found') | |
var validators = {} | |
for (let [ k, v ] of Object.entries(schemas)) { | |
if (typeof api[k] !== 'function') { | |
throw new Error(`api "${k}" not found`) | |
} | |
validators[k] = ajv.compile(v) | |
} | |
return (args) => { | |
var { json } = args | |
if (Array.isArray(json)) { | |
return Promise.all(json.map(json => _doit(json, api, validators))) | |
} else { | |
return _doit(json, api, validators) | |
} | |
} | |
} | |
function _doit (json, api, validators) { | |
return new Promise((resolve) => { | |
_testInvalidRequest(json) | |
.then(json => _testMethodNotFound(json, api)) | |
.then(json => _testInvalidParams(json, validators)) | |
.then(json => api[json.method](json.params)) | |
.then(result => resolve(_wrap(result, json))) | |
.catch(error => { | |
console.error(error) | |
resolve(_wrapError(error, json)) | |
}) | |
}) | |
} | |
function _wrap (result, json) { | |
var { jsonrpc, id } = json | |
return { jsonrpc, id, result } | |
} | |
function _wrapError (error, json) { | |
var { jsonrpc, id } = json | |
return { jsonrpc, id, error } | |
} | |
function _testInvalidParams (json, validators) { | |
console.log(json) | |
var { params, method } = json | |
var v = validators[method] | |
if (!v(params)) { | |
return Promise.reject(_invalidParamsError(v.errors, json)) | |
} | |
return Promise.resolve(json) | |
} | |
function _invalidParamsError (errors, json) { | |
var { code, message } = rpcErrors.invalidParams | |
var e = new Error(message) | |
e.code = code | |
e.data = { input: json, errors } | |
_toJSON(e) | |
return e | |
} | |
function _testMethodNotFound (json, api) { | |
var { method } = json | |
if (typeof api[method] !== 'function') { | |
return Promise.reject(_methodNotFoundError(json)) | |
} | |
return Promise.resolve(json) | |
} | |
function _methodNotFoundError (data) { | |
var { code, message } = rpcErrors.methodNotFound | |
var e = new Error(message) | |
e.code = code | |
e.data = data | |
_toJSON(e) | |
return e | |
} | |
function _testInvalidRequest (json) { | |
var v = ajv.compile(jsonrpc2requestSchema) | |
if (!v(json)) { | |
return Promise.reject(_invalidRequestError(v.errors, json)) | |
} | |
return Promise.resolve(json) | |
} | |
function _invalidRequestError (errors, json) { | |
var { code, message } = rpcErrors.invalidRequest | |
var e = new Error(message) | |
e.code = code | |
e.data = { input: json, errors } | |
_toJSON(e) | |
return e | |
} | |
function _toJSON (error) { | |
error.toJSON || (error.toJSON = function () { | |
return { | |
code: this.code, | |
data: this.data, | |
message: this.message | |
} | |
}) | |
return error | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var qs = require('qs') | |
var Ajv = require('ajv') | |
var addFormats = require('ajv-formats') | |
var { BufferListStream } = require('bl') | |
var ajv = new Ajv({ strict: false }) | |
addFormats(ajv) | |
// post(pathPattern, resArg, body(function * (args) { ... })) | |
module.exports = function body (schema, handler) { | |
var valid | |
if (schema != null) valid = ajv.compile(schema) | |
if (_testGenConstructor(handler)) { | |
return (args) => (_parse(args)).then(xargs => _wrap(handler(xargs))) | |
} else { | |
return (args) => (_parse(args).then(xargs => handler(xargs))) | |
} | |
function _wrap (g) { | |
return new Promise((resolve, reject) => { | |
var next = v => { | |
var { done, value } = g.next(v) | |
if (done) return resolve(value) | |
Promise.resolve(value).then(next).catch(reject) | |
} | |
next() | |
}) | |
} | |
function _parse (args) { | |
return new Promise((resolve, reject) => { | |
args.req.pipe(BufferListStream((error, raw) => { | |
if (error) return reject(error) | |
var str = String(raw) | |
if (/json/.test(args.req.headers['content-type'])) { | |
try { | |
let json = JSON.parse(str) | |
if (valid && !valid(json)) { | |
return reject(_InvalidParamsError(valid, json)) | |
} | |
resolve({ ...args, json }) | |
} catch (x) { | |
reject(_ParseError(str)) | |
} | |
} else { | |
let body = qs.parse(str) | |
if (valid && !valid(body)) { | |
return reject(_InvalidParamsError(valid, body)) | |
} | |
resolve({ ...args, body }) | |
} | |
})) | |
}) | |
} | |
function _ParseError (data) { | |
var e = new Error('Parse Error') | |
e.code = -32700 | |
e.data = data | |
_addToJSON(e) | |
return e | |
} | |
function _InvalidParamsError (valid, x) { | |
var e = new Error('Invalid params') | |
e.code = -32602 | |
e.data = { | |
input: x, | |
errors: valid.errors | |
} | |
_addToJSON(e) | |
return e | |
} | |
} | |
function _testGenConstructor (x) { | |
return ( | |
(x instanceof function * () {}.constructor) || | |
(x instanceof async function * () {}.constructor) | |
) | |
} | |
function _addToJSON (e) { | |
e.toJSON || (e.toJSON = function () { | |
return { | |
code: this.code, | |
message: this.message, | |
data: this.data | |
} | |
}) | |
return e | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"type": "object", | |
"required": [ | |
"jsonrpc", | |
"id", | |
"method" | |
], | |
"additionalProperties": false, | |
"properties": { | |
"jsonrpc": { | |
"name": "jsonrpc", | |
"type": "string", | |
"enum": [ | |
"2.0" | |
], | |
"addionalPropeties": false | |
}, | |
"id": { | |
"name": "id", | |
"type": [ | |
"null", | |
"number", | |
"string" | |
], | |
"addionalPropeties": false | |
}, | |
"method": { | |
"name": "method", | |
"type": "string", | |
"addionalPropeties": false | |
}, | |
"params": { | |
"name": "params", | |
"type": [ | |
"object", | |
"array" | |
] | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"type": "object", | |
"required": [ | |
"content_text" | |
], | |
"properties": { | |
"content_text": { | |
"type": "string" | |
}, | |
"author": { | |
"type": "object", | |
"required": [ | |
"id" | |
], | |
"properties": { | |
"id": { | |
"type": "string" | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var { STATUS_CODES } = require('http') | |
var { Buffer } = require('buffer') | |
var { lookup } = require('mime-types') | |
var safe = require('json-stringify-safe') | |
var qs = require('qs') | |
var parseurl = require('parseurl') | |
var routington = require('routington') | |
var _contentType = 'content-type' | |
var _contentLength = 'content-length' | |
var r | |
var _defaults = { | |
_onerror (error) { | |
return [ 500, { [_contentType]: lookup('text') }, String(error) ] | |
}, | |
response: { | |
statusCode: 200, | |
headers: { | |
[_contentType]: lookup('html') | |
} | |
} | |
} | |
module.exports = function () { | |
if (r == null) r = routington() | |
var _onerror = _defaults._onerror | |
return { | |
onError, | |
dispatch, | |
post, | |
put, | |
get | |
} | |
function onError (__onerror) { | |
if (!__onerror) _onerror = _defaults._onerror | |
else if (typeof __onerror === 'function') _onerror = __onerror | |
} | |
function put (pattern, resArgs, handler) { | |
_request('PUT', pattern, resArgs, handler) | |
} | |
function post (pattern, resArgs, handler) { | |
_request('POST', pattern, resArgs, handler) | |
} | |
function get (pattern, resArgs, handler) { | |
_request('GET', pattern, resArgs, handler) | |
} | |
function _request (_method, pattern, resArgs, handler) { | |
if (!handler && typeof resArgs === 'function') { | |
handler = resArgs | |
resArgs = null | |
} | |
var method = _method.toUpperCase() | |
var node = r.define(pattern)[0] | |
node[method] = function (args) { | |
var p = _testGenConstructor(handler) | |
? _wrapGeneratorPromise(handler(args)) | |
: Promise.resolve(handler(args)) | |
return p.then(result => _flush(resArgs, result)) | |
} | |
function _wrapGeneratorPromise (g) { | |
return new Promise((resolve, reject) => { | |
var next = v => { | |
var { done, value } = g.next(v) | |
if (done) return resolve(value) | |
Promise.resolve(value).then(next).catch(reject) | |
} | |
next() | |
}) | |
} | |
} | |
function _flush (resArgs, body) { | |
if (resArgs == null) resArgs = null | |
var statusCode | |
var headers | |
if (Array.isArray(resArgs)) { | |
var [ a, b ] = resArgs | |
if (_testStatusCode(a)) statusCode = a | |
else if (_testContentType(b)) headers = { [_contentType]: lookup(b) } | |
else if (_testIsObject(b)) headers = b | |
} else if (_testStatusCode(resArgs)) { | |
statusCode = resArgs | |
} else if (_testContentType(resArgs)) { | |
headers = { [_contentType]: lookup(resArgs) } | |
} else if (_testIsObject(resArgs)) { | |
if (resArgs.statusCode) statusCode = resArgs.statusCode | |
if (resArgs.headers) headers = resArgs.headers | |
if (!('headers' in resArgs)) headers = resArgs | |
} | |
if (!statusCode) statusCode = _defaults.response.statusCode | |
if (!headers) headers = { ..._defaults.response.headers } | |
return [ statusCode, headers, body ] | |
function _testStatusCode (x) { | |
return !isNaN(parseInt(x, 10)) && STATUS_CODES[x] | |
} | |
function _testContentType (x) { | |
return typeof x === 'string' && lookup(x) !== false | |
} | |
} | |
function dispatch (next) { | |
return (req, res) => { | |
var u = parseurl(req) | |
var m = r.match(u.pathname) | |
if (m == null) return next(req, res) | |
var { node, param } = m | |
var method = req.method.toUpperCase() | |
if (!node[method]) return _methodNotAllowed() | |
var query = qs.parse(u.query) | |
var wrapHandler = node[method] | |
// wrapHandler({ param, query, req }, res) | |
try { | |
wrapHandler({ param, query, req }) | |
.then(resArgs => _finish(res, resArgs)) | |
.catch(error => _finish(res, _onerror(error))) | |
} catch (error) { | |
_finish(res, _onerror(error)) | |
} | |
function _methodNotAllowed () { | |
var msg = `method not allowed. [${method} ${u.pathname}]` | |
return _finish(res, [ 405, {}, msg ]) | |
} | |
} | |
} | |
} | |
function _finish (res, resArgs) { | |
var [ statusCode, headers, _body ] = resArgs | |
res.statusCode = statusCode | |
var __isObject = _testIsObject(_body) || Array.isArray(_body) | |
var body = __isObject ? safe(_body) : String(_body) | |
for (let [ k, v ] of Object.entries(headers)) { | |
res.setHeader(k, v) | |
} | |
if (!(_contentType in headers)) { | |
res.setHeader(_contentType, _defaults.response.headers[_contentType]) | |
} | |
if (__isObject) res.setHeader(_contentType, lookup('json')) | |
if (!(_contentLength in headers)) { | |
res.setHeader(_contentLength, Buffer.byteLength(body)) | |
} | |
res.end(body) | |
} | |
function _testIsObject (x) { | |
return Object.prototype.toString.apply(x) === '[object Object]' | |
} | |
function _testGenConstructor (x) { | |
return ( | |
(x instanceof function * () {}.constructor) || | |
(x instanceof async function * () {}.constructor) | |
) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = { | |
parseError: { code: -32700, message: 'Parse error' }, | |
invalidRequest: { code: -32600, message: 'Invalid Request' }, | |
methodNotFound: { code: -32601, message: 'Method not found' }, | |
invalidParams: { code: -32602, message: 'Invalid params' }, | |
internalError: { code: -32603, message: 'Internal error' } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment