Skip to content

Commit

Permalink
initial workflow is implemented, yet not working
Browse files Browse the repository at this point in the history
  • Loading branch information
Dashron committed Jun 15, 2014
1 parent dc6dbd2 commit c007386
Show file tree
Hide file tree
Showing 18 changed files with 532 additions and 180 deletions.
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2013 Aaron Hedges <aaron@dashron.com>
Copyright (c) 2014 Aaron Hedges <aaron@dashron.com>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
19 changes: 19 additions & 0 deletions example/representations/collection.js
@@ -0,0 +1,19 @@
var Promise = require('bluebird');

module.exports = Promise.coroutine(function* (array, representation) {
var response = {
total : array.length,
collection : Promise.coroutine(function* () {
var promises = [];

for (var i = 0; i < array.length; i++) {
promises.push(representation(array[i]));
}

// yield here so it runs in parallel
return yield promises;
});
};

return response;
});
11 changes: 11 additions & 0 deletions example/representations/errors/notAcceptable.js
@@ -0,0 +1,11 @@
var Promise = require('bluebird');

module.exports = Promise.coroutine(function (error) {
var representation = {};

representation.error = error.message;
representation.code = 406;

this.status = 406;
return representation;
});
11 changes: 11 additions & 0 deletions example/representations/errors/notFound.js
@@ -0,0 +1,11 @@
var Promise = require('bluebird');

module.exports = Promise.coroutine(function (url) {
var representation = {};

representation.error = 'There is no resource located at ' + url;
representation.code = 404;

this.status = 404;
return representation;
});
11 changes: 11 additions & 0 deletions example/representations/errors/unknown.js
@@ -0,0 +1,11 @@
var Promise = require('bluebird');

module.exports = Promise.coroutine(function (error) {
var representation = {};

representation.error = error.message;
representation.code = error.code;

this.status = 500;
return representation;
});
5 changes: 5 additions & 0 deletions example/representations/options.js
@@ -0,0 +1,5 @@
var Promise = require('bluebird');

module.exports = Promise.coroutine(function (methods) {
return methods;
});
14 changes: 14 additions & 0 deletions example/representations/post.js
@@ -0,0 +1,14 @@
var Promise = require('bluebird');
var user_representation = require('./user');

module.exports = Promise.coroutine(function* (post) {
var representation = {};

representation.name = post.title;
representation.description = post.body;
representation.user = Promise.coroutine(function* () {
return yield user_representation(user);
});

return representation;
});
12 changes: 12 additions & 0 deletions example/representations/user.js
@@ -0,0 +1,12 @@
var Promise = require('bluebird');

module.exports = Promise.coroutine(function (user) {
var representation = {};

representation.uri = '/users/' + user.id;
representation.name = user.name;
representation.url = 'http://dashron.com/users/' + user.id;
representation.email = user.email;

return representation;
});
10 changes: 10 additions & 0 deletions example/resources/posts.js
@@ -0,0 +1,10 @@
var Resource = require('../../lib/resource').Resource;

/**
* [many description]
* @type {Resource}
*/
module.exports.many = new Resource({
resources : {
}
});
23 changes: 23 additions & 0 deletions example/resources/root.js
@@ -0,0 +1,23 @@
var Resource = require('../../lib/resource').Resource;
var Response = require('../../lib/response').Response;

/**
* [one description]
* @type {Resource}
*/
module.exports.many = new Resource({
resources : {
'users' : require('./users').many,
'posts' : require('./posts').many
},
methods : {
GET : function* (request) {
return new Response(function () {
return {
"users" : "/users",
"posts" : "/posts"
};
});
}
}
});
18 changes: 18 additions & 0 deletions example/resources/user/user.posts.js
@@ -0,0 +1,18 @@
var Resource = require('../../../lib/resource').Resource;

/**
* [one description]
* @type {Resource}
*/
module.exports.one = new Resource({
});

/**
* [many description]
* @type {Resource}
*/
module.exports.many = new Resource({
resources : {
'#id' : module.exports.one
}
});
31 changes: 31 additions & 0 deletions example/resources/users.js
@@ -0,0 +1,31 @@
var Resource = require('../../lib/resource').Resource;
var Response = require('../../lib/response').Response;

var UserRepresentation = require('../representations/user');

/**
* [one description]
* @type {Resource}
*/
module.exports.one = new Resource({
resources : {
'posts' : require('./user/user.posts').many
}
});

/**
* [many description]
* @type {Resource}
*/
module.exports.many = new Resource({
resources : {
'#id' : module.exports.one
},
methods : {
GET : function* (request) {
var users = yield users_model.getAll();

return new Response(CollectionRepresentation(users, UserRepresentation));
}
}
});
35 changes: 35 additions & 0 deletions example/server.js
@@ -0,0 +1,35 @@
var api = new (require('../lib/api').API)();

api.representations.users = require('./representations/user');
api.representations.post = require('./representations/post');
api.representations.options = require('./representations/options');
api.representations.errors = {
unknown : require('./representations/errors/unknown'),
notFound : require('./representations/errors/notFound')
};

api.rootResource = require('./resources/root').many;

require('http').createServer(function (request, http_response) {
var body = '';

request.on('data', function (data) {
body += data;
});

request.on('end', function () {
request.body = body;
var response = api[request.method](request);
response.writeTo(http_response);
});

request.on('error', function (err) {
var response = new Response(api.representations.errors.unknown(err));

response.status = 500;
response.writeTo(http_response);
});

}).listen(8080, function () {
console.log('server has started');
});
118 changes: 116 additions & 2 deletions lib/api.js
@@ -1,5 +1,119 @@
module.exports.API = function API () {
"use strict";

/**
* api.js
* Copyright(c) 2014 Aaron Hedges <aaron@dashron.com>
* MIT Licensed
*/

var url_module = require('url');
var Promise = require('bluebird');
var Response = require('./response').Response;

var API = module.exports.API = function API () {
this.resources = {};
this.representations = {};
};

API.prototype.resources = null;
API.prototype.representations = null;

API.prototype.GET = function (request) {
return this.request('GET', request.url, request.body, request.headers);
};

API.prototype.POST = function (request) {
return this.request('POST', request.url, request.body, request.headers);
}

API.prototype.PUT = function (request) {
return this.request('PUT', request.url, request.body, request.headers);
}

API.prototype.DELETE = function (request) {
return this.request('DELETE', request.url, request.body, request.headers);
}

API.prototype.PATCH = function (request) {
return this.request('PATCH', request.url, request.body, request.headers);
}

API.prototype.OPTIONS = function* (request) {
if (request.headers['request-target'] === '*') {
// applies to server
return;
}

var OptionsRepresentation = new this.representations['options']();
var resource = this.locateResource(request.url.path);

// this is not explicitly defined in the spec, we arbitrarily return an options representation object
var resource_methods = resource.getValidMethods();
var response = new Response(OptionsRepresentation(resource_methods));

response.headers.allow = resource_methods;
response.status = 200;

return response;
};

API.prototype.resources = null;
/**
* Make a request into the API
*
* @param string method
* @param string url
* @param string body
* @param array headers
* @return string
*/
API.prototype.request = function (method, url, body, headers) {
if (!this.rootResource) {
throw new Error('You must configure a root resource before making API requests');
}

if (!this.representations.errors.notFound) {
throw new Error('You must configure a notFound representation before making API requests');
}

url = url_module.parse(url, true);

var resource = this.locateResource(url.pathname);
var response = null;

if (resource) {
if (resource.allowsMethod(method)) {
return resource[method](url, body, headers);
} else {
return new Response(this.representations.errors.notAcceptable());
}
} else {
return new Response(this.representations.errors.notFound(url));
}
};

/**
* Route the pathname directly to a resource object
*
* @param string pathname
* @return Resource
*/
API.prototype.locateResource = function (pathname) {
// this will cause issues
var parts = pathname.split('/');
// the uri starts with a forward slash, so we will have an empty string at the start of the array
var part = parts.shift();

var resource = this.rootResource;

// loop through every part separated by slashes and incrementally check them against the routes
while (parts.length) {
part = parts.shift();
resource = resource.getResource(part);

if (!resource) {
return false;
}
}

return resource;
}

0 comments on commit c007386

Please sign in to comment.