-
Parameters
- user_name (string) ... ユーザの識別子
-
Response 200 (application/json)
-
Attributes
- name (string) - ユーザの識別子
- age (number) - 年齢
-
Body
{ name: "mizuki", age: 17, }
-
Last active
September 1, 2016 14:43
-
-
Save rymizuki/ab99a288dfafa841983182eddc0408c9 to your computer and use it in GitHub Desktop.
2016-09-02.gotandajs.
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
'use strict' | |
const fs = require('fs') | |
const path = require('path') | |
const _ = require('lodash') | |
const protagonist = require('protagonist') | |
const filepath = path.join(__dirname, 'user.md') | |
const raw = fs.readFileSync(filepath, 'utf8') | |
let result | |
try { | |
result = protagonist.parseSync(raw) | |
} catch (e) { | |
console.log('ERROR', e) | |
} | |
function parse_content (content, previous) { | |
previous || (previous = {}) | |
previous.current || (previous.current = { | |
namespace_index: -1, | |
resource_index: -1, | |
response_index: -1, | |
method: null, | |
uri: null, | |
parameters: null, | |
req_name: null, | |
}) | |
previous.data || (previous.data = []) | |
if (content && Array.isArray(content)) { | |
content.forEach((element) => { | |
previous = parse_element(element, previous) | |
}) | |
return previous | |
} else { | |
return | |
} | |
} | |
function parse_element (content, previous) { | |
switch (content.element) { | |
case 'category': | |
var namespace = to_namespace(content.meta.title) | |
if (namespace) { | |
var ns_index = ++previous.current.namespace_index | |
previous.data[ns_index] = { | |
namespace, | |
data: [] | |
} | |
} | |
break; | |
case 'resource': | |
previous.current.uri = content.attributes.href | |
previous.current.method = null | |
break; | |
case 'transition': | |
var action_name = to_action_name(content.meta.title) | |
var params = to_fields(content.attributes.hrefVariables.content) | |
previous.current.action_name = action_name || 'get' | |
previous.current.parameters = params | |
break; | |
case 'httpRequest': | |
var index = previous.current.resource_index | |
var ns_index = previous.current.namespace_index | |
var action_name = previous.current.action_name | |
var uri = previous.current.uri | |
var uri_template = to_uri_template(uri) | |
var params = previous.current.parameters | |
var prev_method = previous.current.method | |
var method = content.attributes.method | |
previous.current.req_name = content.meta && content.meta.title ? content.meta.title : 'basic' | |
if (!prev_method || (prev_method != method)) { | |
index++ | |
previous.current.resource_index = index | |
previous.current.method = method | |
previous.current.response_index = -1 | |
previous.data[ns_index].data[index] || (previous.data[ns_index].data[index] = {}) | |
previous.data[ns_index].data[index] = { | |
action_name, | |
uri, | |
uri_template, | |
method, | |
params, | |
responses: [] | |
} | |
} | |
break; | |
case 'httpResponse': | |
var ns_index = previous.current.namespace_index | |
var index = previous.current.resource_index | |
var response_index = ++previous.current.response_index | |
var req_name = previous.current.req_name | |
var status_code = content.attributes.statusCode | |
previous.data[ns_index].data[index].responses[response_index] = { | |
req_name, | |
status_code, | |
} | |
break; | |
case 'dataStructure': | |
var ns_index = previous.current.namespace_index | |
var index = previous.current.resource_index | |
var response_index = previous.current.response_index | |
previous.data[ns_index].data[index].responses[response_index].fields | |
= to_fields(content.content) | |
default: | |
} | |
if (content.content && Array.isArray(content.content)) { | |
return parse_content(content.content, previous) | |
} else { | |
return previous | |
} | |
} | |
// CamelCase | |
function to_namespace (stuff) { | |
return to_action_name(stuff) | |
.replace(/^[a-z]{1}/, function (target) { return target.toUpperCase() }) | |
} | |
// camelCase | |
function to_action_name (stuff) { | |
return stuff | |
.toLowerCase() | |
.replace(/ /g, '_') | |
.replace(/_([a-z]{1})/g, function (m, target) { | |
return target.toUpperCase() | |
}) | |
} | |
function to_fields (content) { | |
if (!content || !Array.isArray(content)) return | |
return content.map((member) => { | |
if (member.element == 'member') { | |
let nullable = member.attributes && (_.includes(member.attributes.typeAttributes, 'nullable') || _.includes(member.attributes.typeAttributes, 'optional')) | |
return { | |
key: member.content.key.content, | |
value: /^object|array$/.test(member.content.value.element) ? to_fields(member.content.value.content) | |
: null, | |
type: member.content.value.element, | |
nullable: !!nullable, | |
} | |
} else if (member.element == 'object') { | |
return to_fields(member.content) | |
} | |
}) | |
} | |
function to_uri_template (stuff) { | |
return stuff | |
.split(/\?/)[0] | |
.replace(/\{(.+?)\}/g, function (m, target) { | |
return `:${ target }` | |
}) | |
} | |
function to_response_body (fields) { | |
let kolon = '=>' | |
return to_response_object('$entity', '=>', fields[0], 'object') | |
} | |
function to_response_object (context, op, fields, type) { | |
if (!fields) return 'undef,' | |
return '+{\n' + fields.map((field) => { | |
if (field.type == 'object') { | |
return `\t${ field.key } ${ op } ${ to_response_object(context+'->'+field.key, op, field.value, 'object') }` | |
} else if (field.type == 'array') { | |
return `\t${ field.key } ${ op } [ map { ${ to_response_object('$_', op, field.value[0], 'object') }} @${ context }->${ field.key }]` | |
} else { | |
return `\t${ field.key } ${ op } ${ field.type }(${ context }->${ field.key }),` | |
} | |
}).join('\n') + '\n\t},' | |
} | |
function parse (result) { | |
return parse_content(result.content) | |
} | |
//console.log(JSON.stringify(result, null, 2)) | |
//console.log(JSON.stringify(result.content, null, 2)) | |
const data = parse(result) | |
//console.log(JSON.stringify(data, null, 2)) | |
const tmpl = _.template(` | |
use strict; | |
use warnings; | |
use utf8; | |
package Entity::User { | |
sub new { | |
my ($class, %data) = @_; | |
bless \\%data => $class; | |
} | |
sub name { | |
shift->{user_name}; | |
} | |
sub age { | |
shift->{age} // 17; | |
} | |
1; | |
} | |
package Factory::User { | |
sub get { | |
my ($class, %args) = @_; | |
return Entity::User->new(%args); | |
} | |
} | |
package main { | |
use Amon2::Lite; | |
use JSON::Types; | |
use Try::Lite; | |
__PACKAGE__->load_plugins('Web::JSON'); | |
<% data.forEach(function (namespace) { %><% namespace.data.forEach(function (action) { %> | |
<%= action.method.toLowerCase() %> '<%= action.uri_template %>' => sub { | |
my ($c, $args) = @_; | |
my %params = map { | |
$_ => $c->req->parameters->{$_} // $args->{$_} | |
} qw(<%= action.params.map(function (p) { return p.key }).join(' ') %>); | |
my ($entity); | |
try { | |
$entity = Factory::<%= namespace.namespace %>-><%= action.action_name %>(%params); | |
} ( | |
# FIXME: exceptionケースの把握 | |
<% action.responses.forEach(function (res) { %><% if (/^exception:/.test(res.req_name)) { %> | |
'Exception::<%= res.req_name.replace(/^exception:/,'') %>' => sub { | |
my $e = $@; | |
return $c->render_json(+{ | |
status => <%= res.status_code %>, | |
error => +{ | |
type => '<%= res.req_name %>', | |
reason => $e, | |
} | |
}); | |
}, | |
<% } %><% }) %> | |
'*' => sub { | |
# rethorw | |
die $@; | |
} | |
); | |
<% action.responses.forEach(function (res) { %><% if (res.req_name == 'basic') { %> | |
return $c->render_json(<%= to_response_body(res.fields) %>); | |
<% } %><% }) %> | |
}; | |
<% }) %><% }) %> | |
__PACKAGE__->to_app; | |
} | |
`) | |
console.log(tmpl({ | |
_, | |
to_response_body, | |
data: data.data, | |
})) |
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
requires 'Plack'; | |
requires 'Amon2::Lite'; | |
requires 'JSON::Types'; | |
requires 'Try::Lite'; |
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
{ | |
"name": "node-example", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"author": "", | |
"license": "ISC", | |
"devDependencies": { | |
"lodash": "^4.15.0", | |
"protagonist": "^1.5.0-pre.0" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment