2008-02-09

TODO: REST Describe & Compile Javascript code generation

The REST Describe & Compile generates code for PHP, Ruby, Python and Java. I'm working on a Javascript generator for REST Compile and decided to first write a working piece of code that I could use as a reference while implementing the actual code generator. This is the code so far (note: the term "class" used in comments is misleading because we're not really dealing with classes here):

// class auto-generated by REST Compile
function RestRequest() {
  // provide user and password for HTTP AUTH
  this.user = null;
  this.password = null;
  this.epoch = new Date(0);  // for If-Modified-Since brutality
}

// XXX: check this works with non-ascii characters etc.
RestRequest.prototype.urlencode = function(str) {
  return escape(str).replace('+', '%2B').replace('%20', '+').replace('*', '%2A').replace('/', '%2F').replace('@', '%40');
}

// the GET function
RestRequest.prototype.doGetCall = function(uri, callback) {

  // XXX: seems to works on major browsers, check harder
  var request = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();

  var async = (callback === undefined) ? false : true;

  // XXX: username, password not tested
  // XXX: isn't the user logged in already?
  if(this.user && this.password)
    request.open('GET', uri, async, this.user, this.password);
  else if(this.user)
    request.open('GET', uri, async, this.user);
  else
    request.open('GET', uri, async);

  // skip cache
  request.setRequestHeader('If-Modified-Since', this.epoch);

  if(async) {
    request.onreadystatechange = function() {
      window.status = request.readyState;
      if(request.readyState == 4) {
        callback((request.status == 200) ? request.responseText : null);
      }
      return;
    }
    request.send();
  }
  else {
    request.send();
    return (request.status == 200) ? request.responseText : null;
  }
}

// the POST function
RestRequest.prototype.doPostCall = function(uri, XXXDATA) {
    // XXX content-type
    // ...etc...
}

// class auto-generated by REST Compile
function Sleep(t) {
  this.params = {};

  // XXX: this should handle required params
  this.params['t'] = t;
}

Sleep.prototype = new RestRequest;

Sleep.prototype.prepareParams = function() {
  pStr = '';
  for(var i in this.params) {
    pStr += '&' + this.urlencode(i) + '=' + this.urlencode(this.params[i]);
  }

  // strip off the first '&'
  return pStr.length > 0 ? pStr.substring(1) : pStr;
}

// the optional Javascript callback function is given at this stage
// so that the constructor can be fed with just request parameters.
Sleep.prototype.submit = function(callback) {
  var requestUri = "sleep.php";

  if(undefined === callback)
    return this.doGetCall(requestUri + '?' + this.prepareParams());
  else
    this.doGetCall(requestUri + '?' + this.prepareParams(), callback);

  return;
}

The usage would be like (using jQuery to manipulate elements):

function synccall() {
  s = new Sleep(1);
  $('#sync').append('<p>' + s.submit() + '</p>');
}

function asynccall() {
  var s = new Sleep(2);
  s.submit(asynccallback);
}

function asynccallback(response) {
  $('#async').append('<p>' + response + '</p>');
}

Several questions came to mind while writing. Should there be some kind of error handling mechanism (definetely!)? An (optional) errback() function that would be fed with HTTP status and headers in case something went wrong, maybe? Because at this point the user would only get null back as a response and that is not too informative (and raising exceptions in a callback doesn't sound too clever either).