Skip to content

Instantly share code, notes, and snippets.

@ishiduca
Last active July 26, 2022 07:46
Show Gist options
  • Save ishiduca/412c6b53676781b3b69056cb225280f9 to your computer and use it in GitHub Desktop.
Save ishiduca/412c6b53676781b3b69056cb225280f9 to your computer and use it in GitHub Desktop.
httpResponseの処理とビジネスロジック処理を分離させるインターフェイスについて
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 })))
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
}
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
}
{
"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"
]
}
}
}
{
"type": "object",
"required": [
"content_text"
],
"properties": {
"content_text": {
"type": "string"
},
"author": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string"
}
}
}
}
}
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)
)
}
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