Skip to content

Instantly share code, notes, and snippets.

@ffejneslen
Created October 20, 2012 23:38
Show Gist options
  • Save ffejneslen/3925249 to your computer and use it in GitHub Desktop.
Save ffejneslen/3925249 to your computer and use it in GitHub Desktop.
IODocs changes: configuring headers and elements to build xml/json service request bodies
diff --git app.js app.js
index aa31957..b842869 100755
--- app.js
+++ app.js
@@ -70,7 +70,7 @@ db.on("error", function(err) {
// Load API Configs
//
var apisConfig;
-fs.readFile(__dirname +'/public/data/apiconfig.json', 'utf-8', function(err, data) {
+fs.readFile('public/data/apiconfig.json', 'utf-8', function(err, data) {
if (err) throw err;
apisConfig = JSON.parse(data);
if (config.debug) {
@@ -276,6 +276,7 @@ function processRequest(req, res, next) {
};
var reqQuery = req.body,
+ requestBody = reqQuery.rawpayload || '',
params = reqQuery.params || {},
methodURL = reqQuery.methodUri,
httpMethod = reqQuery.httpMethod,
@@ -318,8 +319,51 @@ function processRequest(req, res, next) {
path: apiConfig.publicPath + methodURL// + ((paramString.length > 0) ? '?' + paramString : "")
};
+ // build the request body if it was not passed in as 'rawpayload'
if (['POST','DELETE','PUT'].indexOf(httpMethod) !== -1) {
- var requestBody = query.stringify(params);
+ if (!requestBody) {
+
+ // if passed in a series of elements to use to build a request body,
+ // build it here based on the content type we need to send
+ if (reqQuery.elementNames && reqQuery.elementNames.length > 0) {
+ // consult our header values to find our content type
+ var bodyContentType = 'application/json';
+ if (reqQuery.headerNames && reqQuery.headerNames.length > 0) {
+ for (var x = 0, len = reqQuery.headerNames.length; x < len; x++) {
+ if (reqQuery.headerNames[x] == 'Content-Type') {
+ bodyContentType = reqQuery.headerValues[x];
+ }
+ }
+ }
+
+ if ( bodyContentType == 'application/xml') {
+ // we think we want XML -- need to find a way to get a real xml
+ // builder in here?!
+ requestBody += '<'+reqQuery.parentElement+'>';
+ for (var x = 0, len = reqQuery.elementNames.length; x < len; x++) {
+ if (reqQuery.elementNames[x] != '') {
+ requestBody += '<' + reqQuery.elementNames[x] + '>';
+ requestBody += reqQuery.elementValues[x];
+ requestBody += '</' + reqQuery.elementNames[x] + '>';
+ }
+ }
+ requestBody += '</'+reqQuery.parentElement+'>';
+ }
+ else {
+ // we think we want JSON - at least this is easy to build
+ var elList = {};
+ for (var x = 0, len = reqQuery.elementNames.length; x < len; x++) {
+ if (reqQuery.elementNames[x] != '') {
+ elList[reqQuery.elementNames[x]] = reqQuery.elementValues[x];
+ }
+ }
+ requestBody = JSON.stringify(elList);
+ }
+ } else {
+ // by default, we build a bunch of parameters
+ requestBody = query.stringify(params);
+ }
+ }
}
if (apiConfig.oauth) {
@@ -532,12 +576,28 @@ function processRequest(req, res, next) {
}
if (requestBody) {
- options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
+ if (options.headers['Content-Type']) {
+ if (config.debug) {
+ console.log('Header: Content-Type already set to: ' + options.headers['Content-Type']);
+ }
+ }
+ else {
+ if (config.debug) {
+ console.log('Setting header: Content-Type = application/x-www-form-urlencoded ');
+ }
+ options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
+ }
+ // embed our requestBody and the content type
+ // into the 'req' structure to pass along as part of the
+ // result in the app.post operation
+ req.body.requestBody=requestBody;
+ req.body.requestBodyContentType=options.headers['Content-Type'];
}
if (config.debug) {
+ console.log('req.body ' + JSON.stringify(req.body));
console.log(util.inspect(options));
- };
+ }
var doRequest;
if (options.protocol === 'https' || options.protocol === 'https:') {
@@ -590,7 +650,7 @@ function processRequest(req, res, next) {
// Response body
req.result = body;
- console.log(util.inspect(body));
+ sconsole.log(util.inspect(body));
next();
})
@@ -620,7 +680,7 @@ app.dynamicHelpers({
if (!req.params.api) {
pathName = req.url.replace('/','');
// Is it a valid API - if there's a config file we can assume so
- fs.stat(__dirname + '/public/data/' + pathName + '.json', function (error, stats) {
+ fs.stat('public/data/' + pathName + '.json', function (error, stats) {
if (stats) {
req.params.api = pathName;
}
@@ -647,7 +707,7 @@ app.dynamicHelpers({
},
apiDefinition: function(req, res) {
if (req.params.api) {
- var data = fs.readFileSync(__dirname + '/public/data/' + req.params.api + '.json');
+ var data = fs.readFileSync('public/data/' + req.params.api + '.json');
return JSON.parse(data);
}
}
@@ -665,13 +725,31 @@ app.get('/', function(req, res) {
// Process the API request
app.post('/processReq', oauth, processRequest, function(req, res) {
+ // embed data into the 'result' that is processed and used
+ // for display -- include the requestBody and the
+ // content type so that we can display what we want on
+ // the result page.
+
var result = {
headers: req.resultHeaders,
response: req.result,
call: req.call,
- code: req.res.statusCode
+ code: req.res.statusCode,
+ requestBody: req.body.requestBody,
+ requestBodyContentType: req.body.requestBodyContentType
};
+ // this is insane... but.. hmmm -- we want to send back all that
+ // information we have in the result. but on a 204 or 304 we have no
+ // content to send.. In normal operations, this effectively also stops
+ // the sending of result data since no response body is present.
+ // In such a case, we can try and short circuit the issue
+ // by resetting the res.statusCode to something else, and allowing the content
+ // in the result to be passed to the view. Remember, we have stashed
+ // the acutal response code as 'code' in the result structure.
+ if (res.statusCode == 204) {
+ res.statusCode = 200;
+ }
res.send(result);
});
@@ -692,7 +770,6 @@ app.post('/upload', function(req, res) {
// API shortname, all lowercase
app.get('/:api([^\.]+)', function(req, res) {
- req.params.api=req.params.api.replace(/\/$/,'');
res.render('api');
});
diff --git public/javascripts/docs.js public/javascripts/docs.js
index 41942c6..456fb16 100644
--- public/javascripts/docs.js
+++ public/javascripts/docs.js
@@ -203,6 +203,21 @@
})
.insertAfter($('input[type=submit]', self));
+ // We want to display the request Body passed to the service if a POST or PUT
+ // operation was executed. -- examine our parameters to find the http method
+ // assume a GET until we learn otherwise.
+ var httpMethod='GET';
+ for ( var i = 0 ; i < params.length ; i++ ) {
+ var nv = params[i];
+ if (nv.name == 'httpMethod') {
+ httpMethod = nv.value;
+ }
+ }
+ if ( httpMethod == 'POST' || httpMethod == 'PUT') {
+ resultContainer.append($(document.createElement('h4')).text('Request Body'));
+ resultContainer.append($(document.createElement('pre')).addClass('body prettyprint'));
+ }
+
// Call that was made, add pre elements
resultContainer.append($(document.createElement('h4')).text('Call'));
resultContainer.append($(document.createElement('pre')).addClass('call'));
@@ -240,10 +255,22 @@
if (result.signin) {
window.open(result.signin,"_blank","height=900,width=800,menubar=0,resizable=1,scrollbars=1,status=0,titlebar=0,toolbar=0");
} else {
+ // make sure we can display something even if we got no response text passed back to us.
+ var ct='text/plain';
+ if (result && result.headers['content-type']) {
+ ct=result.headers['content-type'];
+ }
var response,
- responseContentType = result.headers['content-type'];
+ responseContentType = ct;
+
// Format output according to content-type
- response = livedocs.formatData(result.response, result.headers['content-type'])
+ if (result && result.headers['content-type']) {
+ response = livedocs.formatData(result.response, ct);
+ }
+ else {
+ // if there is no content, by all means say so!
+ response = 'No Content';
+ }
$('pre.response', resultContainer)
.toggleClass('error', false)
@@ -253,7 +280,17 @@
})
// Complete, runs on error and success
.complete(function(result, text) {
- var response = JSON.parse(result.responseText);
+
+ // make sure the response it set to something
+ // if there was no response, perhaps something bad happened?
+ var response = JSON.parse('{"code":"500"}');
+ if (result && result.responseText) {
+ response = JSON.parse(result.responseText);
+ }
+ else if (result && result.status) {
+ // no response text? well at least grab the status
+ response.code = JSON.parse(result.status);
+ }
if (response.call) {
$('pre.call', resultContainer)
@@ -270,6 +307,25 @@
.text(formatJSON(response.headers));
}
+ // if we did a POST or PUT, we may have sent along the request body and
+ // content type -- if so, format the body and embed in the result container.
+ if ( httpMethod == 'POST' || httpMethod == 'PUT') {
+ var rawpayloadText='Request Body Not Found';
+ if (response.requestBody) {
+ rawpayloadText=response.requestBody;
+ }
+
+ // json encoding is fairly safe if nothing was sent
+ var requestContentType='application/json';
+ if (response.requestBodyContentType) {
+ requestContentType=response.requestBodyContentType;
+ }
+
+ var requestBodyText = livedocs.formatData(rawpayloadText, requestContentType);
+ $('pre.body', resultContainer).text(requestBodyText);
+ }
+
+
// Syntax highlighting
prettyPrint();
})
diff --git views/api.jade views/api.jade
index 97f5a9f..7b111e5 100644
--- views/api.jade
+++ views/api.jade
@@ -1,4 +1,4 @@
-h1=apiInfo.name
+h1=apiInfo.name
- if (session.authed && apiInfo.oauth && apiInfo.oauth.type =='three-legged')
- var authed ='authed'
- else
@@ -77,7 +77,7 @@ ul
span.description #{method.Synopsis}
br
br
- - if (method.parameters.length > 0)
+ - if (method.parameters && method.parameters.length > 0)
table.parameters
thead
tr
@@ -127,6 +127,114 @@ ul
- each description, choice in parameter.EnumeratedDescription
dt #{choice}
dd #{description}
+ - if (method.elements && method.elements.length > 0)
+ table.elements
+ thead
+ tr
+ th Element
+ th Value
+ th Type
+ th Description
+ tbody
+ - var elCount = -1
+ - each element in method.elements
+ - elCount++
+ div(id='element' + elCount)
+ - if (element.Required =='Y')
+ - var required =true
+ - var className ='required'
+ - else
+ - var required =false
+ - var className =''
+ tr(class=className)
+ td
+ #{element.Name}
+ - if (element.Type != 'parentElement')
+ input(name='elementNames[' + elCount + ']', type='hidden', value=element.Name)
+ td.header
+ - if (element.Type =='parentElement')
+ input(name='parentElement', value=element.Default, type='hidden')
+ #{element.Default}
+ - else if (element.Type =='enumerated')
+ select(name='elementValues[' + elCount + ']', placeholder=className)
+ - if (element.Default =='')
+ option(value='')
+ - each choice in element.EnumeratedList
+ - if (element.Default ==choice)
+ option(value=choice, selected=true) #{choice}
+ - else
+ option(value=choice) #{choice}
+ - else if (element.Type =='boolean')
+ select(name='elementValues[' + elCount + ']', placeholder=className)
+ - if (element.Default =='')
+ option(value='')
+ - each choice in [apiInfo.booleanTrueVal,apiInfo.booleanFalseVal]
+ - if (element.Default ==choice)
+ option(value=choice, selected=true) #{choice}
+ - else
+ option(value=choice) #{choice}
+ - else
+ input(name='elementValues[' + elCount + ']', value=element.Default, placeholder=className)
+ td.type=element.Type
+ td.description
+ p=element.Description || 'No description'
+ - if (element.Type =='enumerated' && element.EnumeratedDescription)
+ dl.clearfix
+ - each description, choice in element.EnumeratedDescription
+ dt #{choice}
+ dd #{description}
+ - if (method.headers && method.headers.length > 0)
+ table.headers
+ thead
+ tr
+ th Request Header
+ th Value
+ th Type
+ th Description
+ tbody
+ - var headerCount =0
+ - each header in method.headers
+ - headerCount++
+ div(id='header' + headerCount)
+ - if (header.Required =='Y')
+ - var required =true
+ - var className ='required'
+ - else
+ - var required =false
+ - var className =''
+ tr(class=className)
+ td
+ #{header.Name}
+ input(name='headerNames[' + headerCount + ']', type='hidden', value=header.Name)
+ td.header
+ - if (header.Type =='enumerated')
+ select(name='headerValues[' + headerCount + ']', placeholder=className)
+ - if (header.Default =='')
+ option(value='')
+ - each choice in header.EnumeratedList
+ - if (header.Default ==choice)
+ option(value=choice, selected=true) #{choice}
+ - else
+ option(value=choice) #{choice}
+ - else if (header.Type =='boolean')
+ select(name='headerValues[' + headerCount + ']', placeholder=className)
+ - if (header.Default =='')
+ option(value='')
+ - each choice in [apiInfo.booleanTrueVal,apiInfo.booleanFalseVal]
+ - if (header.Default ==choice)
+ option(value=choice, selected=true) #{choice}
+ - else
+ option(value=choice) #{choice}
+ - else
+ input(name='headerValues[' + headerCount + ']', value=header.Default, placeholder=className)
+ td.type=header.Type
+ td.description
+ p=header.Description || 'No description'
+ - if (header.Type =='enumerated' && header.EnumeratedDescription)
+ dl.clearfix
+ - each description, choice in header.EnumeratedDescription
+ dt #{choice}
+ dd #{description}
- if (method.headers && method.headers.length > 0)
div.headers
h4.title
@@ -152,5 +260,18 @@ ul
a(href='#', class='remove') Remove
a(href='#', class='add-headers') Add Header
// Create header fields and button to add/remove headers.
+ - if (method.rawpayload)
+ table.rawpayload
+ thead
+ tr
+ th #{method.rawpayload[0].Name}
+ tbody
+ tr
+ td
+ #{method.rawpayload[0].Description}
+ tr
+ td
+ textarea(columns='60', rows='10', style='width:95%', name='rawpayload', placeholder=method.rawpayload[0].Default)
+ | #{method.rawpayload[0].Default}
- if (!method['read-only'])
input(type='submit', id=method.MethodName, value='Try it!')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment