diff --git a/Rest.Net.Example/Program.cs b/Rest.Net.Example/Program.cs
index d9523fc..cc46daa 100644
--- a/Rest.Net.Example/Program.cs
+++ b/Rest.Net.Example/Program.cs
@@ -34,6 +34,10 @@ namespace MontoyaTech.Rest.Net.Example
new Route(HttpRequestMethod.Get, "/json", Json)
);
+ Console.WriteLine(CodeGenerator.GenerateCSharpClient(listener.Routes));
+
+ Console.ReadLine();
+
listener.RequestPreProcessEvent += (HttpListenerContext context) => {
Console.WriteLine("Request start: " + context.Request.RawUrl);
return true;
@@ -56,21 +60,25 @@ namespace MontoyaTech.Rest.Net.Example
listener.Block();
}
+ [RouteGroup("Test")]
public static HttpListenerResponse Status(HttpListenerContext context)
{
return context.Response.WithStatus(HttpStatusCode.OK).WithText("Everything is operational. 👍");
}
+ [RouteGroup("Test")]
public static HttpListenerResponse Add(HttpListenerContext context, double a, double b)
{
return context.Response.WithStatus(HttpStatusCode.OK).WithText((a + b).ToString());
}
+ [RouteGroup("Test")]
public static HttpListenerResponse Signup(HttpListenerContext context, User user)
{
return context.Response.WithStatus(HttpStatusCode.OK).WithText("User:" + user.Name);
}
+ [RouteGroup("Test")]
public static HttpListenerResponse Json(HttpListenerContext context)
{
return context.Response.WithStatus(HttpStatusCode.OK).WithJson(new User("Rest.Net"));
diff --git a/Rest.Net/CodeGenerator.cs b/Rest.Net/CodeGenerator.cs
new file mode 100644
index 0000000..794dd52
--- /dev/null
+++ b/Rest.Net/CodeGenerator.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MontoyaTech.Rest.Net
+{
+ ///
+ /// A class that can take a set of routes and generate code
+ /// for a client that can be used to interact with them.
+ ///
+ public class CodeGenerator
+ {
+ ///
+ /// Generates a CSharp client for a given set of routes.
+ ///
+ ///
+ ///
+ public static string GenerateCSharpClient(IList routes)
+ {
+ var writer = new CodeWriter();
+
+ writer.WriteLine("public class Client").WriteLine("{").Indent();
+ writer.WriteSpacer();
+ writer.WriteLine("public string BaseUrl = null;");
+ writer.WriteSpacer();
+ writer.WriteLine("public Client(string baseUrl)").WriteLine("{").Indent();
+ writer.WriteLine("this.BaseUrl = baseUrl;");
+ writer.Outdent().WriteLine("}");
+
+ GenerateCSharpRouteClasses(routes, writer);
+
+ writer.Outdent().WriteLine("}");
+
+ return writer.ToString();
+ }
+
+ private static void GenerateCSharpRouteClasses(IList routes, CodeWriter writer)
+ {
+ var groups = new Dictionary>();
+
+ //Go through all the routes and group them.
+ foreach (var route in routes)
+ {
+ //Get the method that this route is tied to.
+ var methodInfo = route.Target.GetMethodInfo();
+
+ //See if this method defines the group for us.
+ var routeGroup = methodInfo.GetCustomAttribute();
+
+ //Group this route.
+ string group = (routeGroup != null ? routeGroup.Name : methodInfo.DeclaringType.Name);
+
+ if (groups.ContainsKey(group))
+ groups[group].Add(route);
+ else
+ groups.Add(group, new List() { route });
+ }
+ }
+
+ private static void GenerateCSharpRouteClass(string name, List routes, CodeWriter writer)
+ {
+ }
+
+ ///
+ /// Generates a Javascript client for a given set of routes.
+ ///
+ ///
+ ///
+ public static string GenerateJavascriptClient(IList routes)
+ {
+ return null;
+ }
+ }
+}
diff --git a/Rest.Net/CodeWriter.cs b/Rest.Net/CodeWriter.cs
new file mode 100644
index 0000000..df40ac3
--- /dev/null
+++ b/Rest.Net/CodeWriter.cs
@@ -0,0 +1,398 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MontoyaTech.Rest.Net
+{
+ ///
+ /// The outline of a writer that helps with generating code.
+ ///
+ internal class CodeWriter
+ {
+ ///
+ /// The internal string builder.
+ ///
+ private StringBuilder Builder = new StringBuilder();
+
+ ///
+ /// The current number of characters written..
+ ///
+ public int Length
+ {
+ get
+ {
+ return Builder.Length;
+ }
+ }
+
+ ///
+ /// The current number of indents.
+ ///
+ private int Indents = 0;
+
+ ///
+ /// Whether or not the writer is pending an indent that needs
+ /// to be handled.
+ ///
+ private bool PendingIndent = false;
+
+ ///
+ /// Creates a new default CodeWriter.
+ ///
+ public CodeWriter() { }
+
+ ///
+ /// Creates a new default text writer and copies the indent data from the passed
+ /// text writer.
+ ///
+ ///
+ public CodeWriter(CodeWriter writer)
+ {
+ this.Indents = writer.Indents;
+
+ //If we have indents, then we must set pending indent.
+ if (this.Indents > 0)
+ this.PendingIndent = true;
+ }
+
+ ///
+ /// Writes text to the writer.
+ ///
+ ///
+ public CodeWriter Write(string text)
+ {
+ if (this.PendingIndent)
+ {
+ for (var i = 0; i < Indents; i++)
+ this.Builder.Append(' ');
+
+ this.PendingIndent = false;
+ }
+
+ this.Builder.Append(text);
+
+ return this;
+ }
+
+ ///
+ /// Writes a character to the writer.
+ ///
+ ///
+ public CodeWriter Write(char @char)
+ {
+ if (this.PendingIndent)
+ {
+ for (var i = 0; i < Indents; i++)
+ this.Builder.Append(' ');
+
+ this.PendingIndent = false;
+ }
+
+ this.Builder.Append(@char);
+
+ return this;
+ }
+
+ ///
+ /// Writes text to the writer and a newline.
+ ///
+ ///
+ public CodeWriter WriteLine(string text)
+ {
+ if (this.PendingIndent)
+ {
+ for (var i = 0; i < Indents; i++)
+ this.Builder.Append(' ');
+
+ this.PendingIndent = false;
+ }
+
+ this.Builder.Append(text);
+ this.Builder.Append('\n');
+ this.PendingIndent = true;
+
+ return this;
+ }
+
+ ///
+ /// Writes a character and new line to the writer.
+ ///
+ ///
+ public CodeWriter WriteLine(char @char)
+ {
+ if (this.PendingIndent)
+ {
+ for (var i = 0; i < Indents; i++)
+ this.Builder.Append(' ');
+
+ this.PendingIndent = false;
+ }
+
+ this.Builder.Append(@char);
+ this.Builder.Append('\n');
+ this.PendingIndent = true;
+
+ return this;
+ }
+
+ ///
+ /// Writes text to the writer if the condition is met.
+ ///
+ ///
+ ///
+ ///
+ public CodeWriter WriteAssert(bool condition, string text)
+ {
+ if (condition)
+ return this.Write(text);
+
+ return this;
+ }
+
+ ///
+ /// Writes a character to the writer if the condition is met.
+ ///
+ ///
+ ///
+ ///
+ public CodeWriter WriteAssert(bool condition, char @char)
+ {
+ if (condition)
+ return this.Write(@char);
+
+ return this;
+ }
+
+ ///
+ /// Writes text to the writer and a newline if the condition is met.
+ ///
+ ///
+ ///
+ ///
+ public CodeWriter WriteLineAssert(bool condition, string text)
+ {
+ if (condition)
+ return this.WriteLine(text);
+
+ return this;
+ }
+
+ ///
+ /// Writes a character and new line to the writer if the condition is met.
+ ///
+ ///
+ ///
+ ///
+ public CodeWriter WriteLineAssert(bool condition, char @char)
+ {
+ if (condition)
+ return this.WriteLine(@char);
+
+ return this;
+ }
+
+ ///
+ /// Writes the contents of another text writer into this one.
+ ///
+ ///
+ public CodeWriter Write(CodeWriter writer)
+ {
+ this.Builder.Append(writer.Builder.ToString());
+
+ return this;
+ }
+
+ ///
+ /// Inserts a new line to the writer.
+ ///
+ public CodeWriter NewLine()
+ {
+ this.Builder.Append('\n');
+ this.PendingIndent = true;
+
+ return this;
+ }
+
+ ///
+ /// Inserts a new line to the writer if the condition is met.
+ ///
+ public CodeWriter NewLineAssert(bool condition)
+ {
+ if (condition)
+ {
+ this.Builder.Append('\n');
+ this.PendingIndent = true;
+ }
+
+ return this;
+ }
+
+ ///
+ /// Writes a space to the writer unless there is already one.
+ ///
+ ///
+ public CodeWriter WriteSpacer()
+ {
+ if (this.Builder.Length >= 1 && this.Builder[this.Builder.Length - 1] != ' ')
+ this.Builder.Append(' ');
+
+ return this;
+ }
+
+ ///
+ /// Writes a separator to the writer unless one wouldn't make sense.
+ ///
+ ///
+ public CodeWriter WriteSeparator()
+ {
+ if (this.Builder.Length > 0)
+ {
+ var offset = this.Builder.Length - 1;
+ var prev = this.Builder[offset];
+ while (offset >= 0 && (prev == ' ' || prev == '\t' || prev == '\n' || prev == '\r'))
+ {
+ offset--;
+ prev = this.Builder[offset];
+ }
+
+ if (prev != '(' && prev != '{' && prev != '[' && prev != ',')
+ this.Builder.Append(", ");
+ }
+
+ return this;
+ }
+
+ ///
+ /// Writes a separator to the writer and text if a condition is met unless a separator here isn't needed, then just text is written.
+ ///
+ ///
+ ///
+ ///
+ public CodeWriter WriteSeparatorAssert(bool condition, string text)
+ {
+ if (condition)
+ {
+ if (this.Builder.Length > 0)
+ {
+ var offset = this.Builder.Length - 1;
+ var prev = this.Builder[offset];
+ while (offset >= 0 && (prev == ' ' || prev == '\t' || prev == '\n' || prev == '\r'))
+ {
+ offset--;
+ prev = this.Builder[offset];
+ }
+
+ if (prev != '(' && prev != '{' && prev != '[' && prev != ',')
+ this.Builder.Append(", ");
+ }
+
+ if (text != null)
+ this.Builder.Append(text);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Writes a blank line as a spacer to the writer
+ /// unless there is already one.
+ ///
+ public CodeWriter WriteBreak()
+ {
+ if (this.Builder.Length >= 2)
+ {
+ var prevChar = this.Builder[this.Builder.Length - 2];
+
+ if (prevChar != '\n')
+ {
+ this.Builder.Append('\n');
+ this.PendingIndent = true;
+ }
+ }
+ else
+ {
+ this.Builder.Append('\n');
+ this.PendingIndent = true;
+ }
+
+ return this;
+ }
+
+ ///
+ /// Indents the writer one time.
+ ///
+ public CodeWriter Indent()
+ {
+ this.Indents += 4;
+
+ return this;
+ }
+
+ ///
+ /// Indents the writer one time if the condition is met.
+ ///
+ public CodeWriter IndentAssert(bool condition)
+ {
+ if (condition)
+ this.Indents += 4;
+
+ return this;
+ }
+
+ ///
+ /// Removes one indent from the writer.
+ ///
+ public CodeWriter Outdent()
+ {
+ if (this.Indents > 0)
+ this.Indents -= 4;
+
+ return this;
+ }
+
+ ///
+ /// Removes one indent from the writer if the condition is met.
+ ///
+ public CodeWriter OutdentAssert(bool condition)
+ {
+ if (condition && this.Indents > 0)
+ this.Indents -= 4;
+
+ return this;
+ }
+
+ ///
+ /// Resets all indents from the writer.
+ ///
+ public CodeWriter ResetIndents()
+ {
+ this.Indents = 0;
+
+ return this;
+ }
+
+ ///
+ /// Removes the last character from the writer.
+ ///
+ ///
+ public CodeWriter Remove()
+ {
+ if (this.Builder.Length > 0)
+ this.Builder.Remove(this.Builder.Length - 1, 1);
+
+ return this;
+ }
+
+ ///
+ /// Gets all the written data from the writer.
+ ///
+ ///
+ public override string ToString()
+ {
+ return this.Builder.ToString();
+ }
+ }
+}
diff --git a/Rest.Net/Route.cs b/Rest.Net/Route.cs
index 784f67a..2484fde 100644
--- a/Rest.Net/Route.cs
+++ b/Rest.Net/Route.cs
@@ -25,7 +25,7 @@ namespace MontoyaTech.Rest.Net
///
/// The target function to invoke if this route is invoked.
///
- private Func Target;
+ internal Func Target;
///
/// Whether or not to close the response after the route is invoked.
diff --git a/Rest.Net/RouteGroup.cs b/Rest.Net/RouteGroup.cs
new file mode 100644
index 0000000..3bee727
--- /dev/null
+++ b/Rest.Net/RouteGroup.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MontoyaTech.Rest.Net
+{
+ ///
+ /// The outline of an attribute that controls what group a route belongs to.
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class RouteGroup : Attribute
+ {
+ ///
+ /// The name of the group this Route belongs to.
+ ///
+ public string Name = null;
+
+ ///
+ /// Creates a new default route group.
+ ///
+ public RouteGroup() { }
+
+ ///
+ /// Creates a new route group with the name of the group.
+ ///
+ ///
+ public RouteGroup(string name)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ throw new ArgumentException("Must not be null or empty", nameof(name));
+
+ this.Name = name;
+ }
+ }
+}