1 module vibrant.routes;
2 
3 mixin template Routes(string ResourceName = "") {
4 
5 	import std.typetuple;
6 	import dquery.d;
7 
8 	private {
9 
10 		/++
11 		 + The current request parameters.
12 		 ++/
13 		Parameter params;
14 
15 		/++
16 		 + The current HTTP Request object.
17 		 ++/
18 		HTTPServerRequest request;
19 
20 		/++
21 		 + The current HTTP Response object.
22 		 ++/
23 		HTTPServerResponse response;
24 
25 		/++
26 		 + Content type definitions for render!().
27 		 ++/
28 		enum {
29 			TEXT = "text/plain",
30 			JS = "application/javascript",
31 			JSON = "application/json",
32 			XML = "application/xml",
33 			XHTML = "application/xhtml+xml",
34 			HTML = "text/html",
35 			CSS = "text/css",
36 			EMPTY = "@empty"
37 		}
38 
39 		@property
40 		static string resourceName(this This)() {
41 			static if (ResourceName == "") {
42 				// Prefix from type name.
43 				return "/" ~ This
44 					.stringof
45 					.toSnakeCase
46 					.stripSuffix!"_controller";
47 			} else {
48 				return ResourceName;
49 			}
50 		}
51 
52 		@property
53 		template hasFunction(This, string name) {
54 			enum hasFunction = Alias!(
55 					!query!This()
56 						.functions
57 						.arity!(0)
58 						.name!name
59 						.empty
60 				);
61 		}
62 
63 		@property
64 		template getFunction(This, string name) if (hasFunction!(This, name)) {
65 			alias getFunction = Alias!(
66 				query!This()
67 					.functions
68 					.arity!(0)
69 					.name!name
70 					.first
71 					.value
72 			);
73 		}
74 
75 		static VoidCallback createCallback(This, string name)() {
76 			return delegate void(HTTPServerRequest req, HTTPServerResponse res) {
77 				// Create the controller.
78 				This controller = new This;
79 
80 				// Setup request and response.
81 				controller.request = req;
82 				controller.response = res;
83 				controller.params = req.createParameters;
84 
85 				// Call the controller action.
86 				controller.getFunction!(This, name)();
87 			};
88 		}
89 
90 	}
91 
92 	/++
93 	 + Renders a plaintext response.
94 	 ++/
95 	@property
96 	public void render(Body)(Body content) {
97 		return render!(Text, Body)(content);
98 	}
99 
100 	/++
101 	 + Renders a response with a given content type.
102 	 ++/
103 	@property
104 	public void render(string contentType, Body)(Body content)
105 			if (contentType.length > 0 && contentType[0] != '@') {
106 		import std.conv : to;
107 
108 		response.writeBody(to!string(content), contentType);
109 	}
110 
111 	/++
112 	 + Renders a response with a given content type and status code.
113 	 ++/
114 	@property
115 	public void render(string contentType, Body)(Body content, int code)
116 			if (contentType.length > 0 && contentType[0] != '@') {
117 		response.statusCode = code;
118 		render!(contentType, Body)(content);
119 	}
120 
121 	/++
122 	 + Renders an empty response.
123 	 ++/
124 	@property
125 	public void render(string contentType)() if (contentType == EMPTY) {
126 		response.writeBody(""); // Write an empty body.
127 	}
128 
129 	/++
130 	 + Renders an empty response and the given status code.
131 	 ++/
132 	@property
133 	public void render(string contentType)(int code) if (contentType == EMPTY) {
134 		response.statusCode = code;
135 		render!contentType;
136 	}
137 
138 	/++
139 	 + Installs the controller routes into a Vibrant router.
140 	 ++/
141 	public static void install(bool Bool)(VibrantRouter!Bool router) {
142 		alias This = Alias!(typeof(this));
143 
144 		// 'index' route; display all.
145 		static if (hasFunction!(This, "index")) {
146 			router.Get(resourceName!This, createCallback!(This, "index"));
147 		}
148 
149 		// 'me' route; display my object.
150 		static if (hasFunction!(This, "me")) {
151 			router.Get(resourceName!This, createCallback!(This, "me"));
152 		}
153 
154 		// 'new' route; show new form.
155 		static if (hasFunction!(This, "new")) {
156 			router.Get(resourceName!This ~ "/me", createCallback!(This, "new"));
157 		}
158 
159 		// 'create' route; create object.
160 		static if (hasFunction!(This, "create")) {
161 			router.Post(resourceName!This, createCallback!(This, "create"));
162 		}
163 
164 		// 'show' route; display an object.
165 		static if (hasFunction!(This, "show")) {
166 			router.Get(resourceName!This ~ "/:id", createCallback!(This, "show"));
167 		}
168 
169 		// 'edit' route; display edit form.
170 		static if (hasFunction!(This, "edit")) {
171 			router.Get(resourceName!This ~ "/:id/edit", createCallback!(This, "edit"));
172 		}
173 
174 		// 'update' route; edit an object.
175 		static if (hasFunction!(This, "update")) {
176 			router.Put(resourceName!This ~ "/:id", createCallback!(This, "update"));
177 
178 			router.Patch(resourceName!This ~ "/:id", createCallback!(This, "update"));
179 		}
180 
181 		// 'destroy' route; delete an object.
182 		static if (hasFunction!(This, "destroy")) {
183 			router.Delete(resourceName!This ~ "/:id", createCallback!(This, "destroy"));
184 		}
185 	}
186 
187 }