Here’s what I’m at:
Parse.uno
using Uno;
using Uno.Collections;
using Fuse;
using Fuse.Elements;
using Uno.Net.Http;
using Uno.Data.Json;
namespace Parse
{
//////////////////////////////////////////////
// EVENTS
//////////////////////////////////////////////
// Event that can bubble up the node-tree
abstract class AbstractEvent : Uno.EventArgs
{
public abstract string Message { get; }
private readonly bool _bubble;
private readonly Node _node;
// Raise the event on given context, and bubble up through nodes
protected AbstractEvent(Node node, bool bubble) : base() {
_node = node;
_bubble = bubble;
}
protected bool IsHandled { private get; set; }
public void Raise() {
assert(!IsHandled);
// Bubble up through nodes
for (var n = _node; n != null; n = n.ParentNode) {
n.Properties.ForeachInList(PropertyHandle, Invoke, this);
if (IsHandled) return;
if (!bubble) break;
}
// shouldn't get here as long as someone handles the event
throw new Uno.Exception("Unhandled Parse-Event: " + Message);
}
// Private propertyhandle identifying this type of event (for Raise)
private abstract PropertyHandle PropertyHandle { get; }
// Private callback to invoke handler and update IsHandled (for Raise)
private abstract void Invoke(object handler, object args);
}
// Event that carries an exception
public class ExceptionEvent : AbstractEvent {
public delegate bool Handler(ExceptionEvent args);
// Exception
public Parse.Exception Exception { get; private set; }
public override string Message { get { return Exception.Message; } }
// Constructor
public ExceptionEvent(Node node, Parse.Exception e) : base(node, true) {
Exception = e;
}
// Invoke
private override void Invoke(object handler, object args)
{
var handler = (Handler)handler;
var args = (ExceptionEvent)args_;
if (handler(args)) IsHandled = true;
}
// The propertyhandle
static readonly PropertyHandle _propertyHandle = Properties.CreateHandle();
private override PropertyHandle PropertyHandle {
get {
return _propertyHandle;
}
}
[Uno.UX.AttachedEventAdder("Parse.ExceptionEvent")]
public static void AddEventHandler(Node n, Handler handler)
{
debug_log "adding " + handler + " to " + n + "'s " + _propertyHandle;
n.Properties.AddToList(_propertyHandle, handler);
}
[Uno.UX.AttachedEventRemover("Parse.ExceptionEvent")]
public static void RemoveEventHandler(Node n, Handler handler)
{
debug_log "removing " + handler + " from " + n;
n.Properties.RemoveFromList(_propertyHandle, handler);
}
}
//////////////////////////////////////////////
// EXCEPTIONS
//////////////////////////////////////////////
// This is our exception base-class
public class Exception : Uno.Exception {
public Exception(string message) : base(message) {}
}
// Response could not be converted to expected format
public class ConversionException : Parse.Exception {
public ConversionException(string message) : base(message) {}
}
// Response status indicates error
public class ResponseStatusException : Parse.Exception {
public int Status { get; private set; }
public object Content { get; private set; }
public ResponseStatusException(int status, JsonString content) : base(status + ": " + (string)content)
{
Status = status;
Content = content;
}
public ResponseStatusException(int status, string content) : base(status + ": " + content)
{
Status = status;
Content = content;
}
public ResponseStatusException(int status) : base("" + status)
{
Status = status;
}
}
// Got an error-callback
public class CallbackErrorException : Parse.Exception {
public CallbackErrorException(string message) : base(message) {}
}
// Aborted
public class CallbackAbortedException : Parse.Exception {
public CallbackAbortedException() : base("aborted") {}
}
// Timed out
public class CallbackTimeoutException : Parse.Exception {
public CallbackTimeoutException() : base("timeout") {}
}
////////////////////////////////////////////////////
// DATA TYPES
////////////////////////////////////////////////////
// Wraps Json and allows returning it as a string
public class JsonString
{
string _string;
JsonReader _jsonReader;
public JsonString(string op) {
_string = op;
_jsonReader = JsonReader.Parse(op);
}
public static explicit operator string(JsonString op) {
return op._string;
}
public static explicit operator JsonReader(JsonString op) {
return op._jsonReader;
}
public JsonReader this[string key] {
get { return _jsonReader[key]; }
}
public JsonReader this[int index] {
get { return _jsonReader[index]; }
}
public int Count {
get { return _jsonReader.Count; }
}
public string[] Keys {
get { return _jsonReader.Keys; }
}
public bool HasKey(string key) {
return _jsonReader.HasKey(key);
}
}
///////////////////////////////////////////////////////////
// INTERFACE
///////////////////////////////////////////////////////////
// Abstract context
public abstract class Context
{
public abstract Request Get(string path);
public abstract Request Put(string path);
public abstract Request Post(string path);
public abstract Request Patch(string path);
public abstract Request Delete(string path);
}
// abstract request
public abstract class Request
{
public abstract Request Body(JsonString op, string content_type="application/json");
public abstract Request Body(string op, string content_type="text/plain");
public abstract Request Body(byte[] op, string content_type);
public abstract Request OnDone(Action<JsonString> op);
public abstract Request OnDone(Action<string> op);
public abstract Request OnDone(Action<byte[]> op);
public abstract Request OnDone(Action op);
public abstract Request Send();
}
// client
public class Client
{
ClientImpl _implementation = new ClientImpl();
public string ApplicationId {
get {
return _implementation.ApplicationId;
}
set {
_implementation.ApplicationId = value;
}
}
public string ClientKey {
get {
return _implementation.ClientKey;
}
set {
_implementation.ClientKey = value;
}
}
public string SessionToken {
get {
return _implementation.SessionToken;
}
set {
_implementation.SessionToken = value;
}
}
public Context CreateContext(Fuse.Node node) {
return _implementation.CreateContext(node);
}
}
///////////////////////////////////////////////////////
// IMPLEMENTATION
///////////////////////////////////////////////////////
class RequestImpl : Request {
Node _node;
ClientImpl _client;
HttpMessageHandlerRequest _httpMessageHandlerRequest;
public RequestImpl(Node node, ClientImpl client, string method, string path)
{
_node = node;
_client = client;
_httpMessageHandlerRequest = _client.HttpMessageHandler.CreateRequest(method, "https://api.parse.com/1/" + path);
_httpMessageHandlerRequest.SetTimeout(100000);
_httpMessageHandlerRequest.SetHeader("X-Parse-Application-Id", _client.ApplicationId);
_httpMessageHandlerRequest.SetHeader("X-Parse-Client-Key", _client.ClientKey);
if (_client.SessionToken!=null)
_httpMessageHandlerRequest.SetHeader("X-Parse-Session-Token", _client.SessionToken);
if (_client.InstallationId!=null)
_httpMessageHandlerRequest.SetHeader("X-Parse-Installation-Id", _client.InstallationId);
_httpMessageHandlerRequest.Done += InternalCallback_Done;
_httpMessageHandlerRequest.Error += InternalCallback_Error;
_httpMessageHandlerRequest.Aborted += InternalCallback_Aborted;
_httpMessageHandlerRequest.Timeout += InternalCallback_Timeout;
_httpMessageHandlerRequest.Progress += InternalCallback_Progress;
_httpMessageHandlerRequest.StateChanged += InternalCallback_StateChanged;
}
object _body;
public override Request Body(JsonString op, string content_type="application/json")
{
_httpMessageHandlerRequest.SetHeader("Content-type", content_type);
_body = op;
return this;
}
public override Request Body(string op, string content_type="text/plain")
{
_httpMessageHandlerRequest.SetHeader("Content-type", content_type);
_body = op;
return this;
}
public override Request Body(byte[] op, string content_type)
{
_httpMessageHandlerRequest.SetHeader("Content-type", content_type);
_body = op;
return this;
}
object _doneCb;
public override Request OnDone(Action<JsonString> op)
{
_httpMessageHandlerRequest.SetResponseType(HttpResponseType.String);
_doneCb = op;
return this;
}
public override Request OnDone(Action<string> op)
{
_httpMessageHandlerRequest.SetResponseType(HttpResponseType.String);
_doneCb = op;
return this;
}
public override Request OnDone(Action<byte[]> op)
{
_httpMessageHandlerRequest.SetResponseType(HttpResponseType.ByteArray);
_doneCb = op;
return this;
}
public override Request OnDone(Action op)
{
_httpMessageHandlerRequest.SetResponseType(HttpResponseType.None);
_doneCb = op;
return this;
}
public override Request Send()
{
// send with correct body type
if (_body==null) {
_httpMessageHandlerRequest.SendAsync();
} else if (_body is JsonString) {
_httpMessageHandlerRequest.SendAsync((string)(JsonString)_body);
} else if (_body is string) {
_httpMessageHandlerRequest.SendAsync((string)_body);
} else if (_body is byte[]) {
_httpMessageHandlerRequest.SendAsync((byte[])_body);
} else {
// should not get here
debug_log "body is of an impossible type!";
assert(false);
}
return this;
}
void InternalCallback_Done(HttpMessageHandlerRequest op) {
assert(op==_httpMessageHandlerRequest);
// attempt to parse result as json, remembering any exception thrown
JsonString jsonString = null;
JsonException jsonException = null;
try {
if (_httpMessageHandlerRequest.HttpResponseType == HttpResponseType.String) {
jsonString = new JsonString(_httpMessageHandlerRequest.GetResponseContentString());
}
} catch (JsonException e) {
jsonException = e;
}
// process
int status = _httpMessageHandlerRequest.GetResponseStatus(); try // capture Parse.Exceptions and raise them as events
{
if (status>=200 && status<300) // request was a success
{
if (_doneCb == null) {} // no callback - nothing to do!
else if (_doneCb is Action) // callback expects no response data
{ var cb = (Action)_doneCb;
if (_httpMessageHandlerRequest.HttpResponseType == HttpResponseType.None)
cb();
else
throw new ConversionException("Response received when none was expected");
}
else if (_doneCb is Action<string>) // callback expects string data
{ var cb = (Action<string>)_doneCb;
if (_httpMessageHandlerRequest.HttpResponseType == HttpResponseType.String)
cb(_httpMessageHandlerRequest.GetResponseContentString());
else
throw new ConversionException("Received " + _httpMessageHandlerRequest.HttpResponseType + " when string was expected");
}
else if (_doneCb is Action<JsonString>) // callback expects Json data
{ var cb = (Action<JsonString>)_doneCb;
if (_httpMessageHandlerRequest.HttpResponseType == HttpResponseType.String && jsonException==null)
cb(jsonString);
else
throw new ConversionException("Received " + _httpMessageHandlerRequest.HttpResponseType + " which could not be parsed as Json");
}
else if (_doneCb is Action<byte[]>) // callback expects byte[] data
{ var cb = (Action<byte[]>)_doneCb;
if (_httpMessageHandlerRequest.HttpResponseType == HttpResponseType.ByteArray)
cb(_httpMessageHandlerRequest.GetResponseContentByteArray());
else
throw new ConversionException("Received " + _httpMessageHandlerRequest.HttpResponseType + " when byte[] was expected");
}
else {
// should not get here
debug_log "_doneCb has an impossible type";
assert(false);
}
}
else // request was a failure
{
// try to parse response data as info about the failure, and throw a ResponseStatusException
if (jsonString != null) // json in response
throw new ResponseStatusException(status, jsonString);
else if (_httpMessageHandlerRequest.HttpResponseType == HttpResponseType.String) // string in response
throw new ResponseStatusException(status, _httpMessageHandlerRequest.GetResponseContentString());
else // no response
throw new ResponseStatusException(status);
}
}
catch (Parse.Exception e) // a parse-exception has been trown, raise an event!
{
new ExceptionEvent(_node, e).Raise();
}
}
void InternalCallback_Error(HttpMessageHandlerRequest rq, string error) {
new ExceptionEvent(_node, new CallbackErrorException(error)).Raise();
}
void InternalCallback_Aborted(HttpMessageHandlerRequest rq) {
new ExceptionEvent(_node, new CallbackAbortedException()).Raise();
}
void InternalCallback_Timeout(HttpMessageHandlerRequest rq) {
new ExceptionEvent(_node, new CallbackTimeoutException()).Raise();
}
// do nothing for now
void InternalCallback_Progress(HttpMessageHandlerRequest rq, int a, int b, bool c) {
debug_log("progress callback: " + a + ", " + b + ", " + c);
}
// do nothing
void InternalCallback_StateChanged(HttpMessageHandlerRequest rq) {
debug_log("stateChanged callback, state: " + rq.State);
}
}
class ContextImpl : Context {
ClientImpl _client;
Node _node;
public ContextImpl(ClientImpl client, Node node) {
_client = client;
_node = node;
}
public override Request Get(string path) {
return new RequestImpl(_node, _client, "GET", path);
}
public override Request Put(string path) {
return new RequestImpl(_node, _client, "PUT", path);
}
public override Request Post(string path) {
return new RequestImpl(_node, _client, "POST", path);
}
public override Request Patch(string path) {
return new RequestImpl(_node, _client, "PATCH", path);
}
public override Request Delete(string path) {
return new RequestImpl(_node, _client, "DELETE", path);
}
}
// note: forwarding, rather than extension
class ClientImpl {
public string ApplicationId { get; set; }
public string ClientKey { get; set; }
public string SessionToken { get; set; }
public string InstallationId { get; set; }
HttpMessageHandler _httpMessageHandler;
public HttpMessageHandler HttpMessageHandler {
get {
if (_httpMessageHandler==null)
_httpMessageHandler = new HttpMessageHandler();
return _httpMessageHandler;
}
}
public Context CreateContext(Fuse.Node node) {
return new ContextImpl(this, node);
}
}
}