1 module action_dispatch.controller;
2 
3 import vibe.d;
4 public import dynamic_loader.dynamic_class;
5 
6 import std.regex;
7 import action_dispatch.format;
8 
9 class ActionController : DynamicClass {
10   mixin DynamicClassImplementation!();
11 
12   protected {
13     HTTPServerRequest  _request;
14     HTTPServerResponse _response;
15     string             _format;
16   }
17 
18   @property {
19     HTTPServerRequest request() {
20       return _request;
21     }
22 
23     HTTPServerResponse response() {
24       return _response;
25     }
26 
27     void request(HTTPServerRequest request) {
28       _request = request;
29     }
30 
31     void response(HTTPServerResponse response) {
32       _response = response;
33     }
34 
35     void format(string format) {
36       _format = format;
37     }
38   }
39 
40   void handleRequest(HTTPServerRequest req, HTTPServerResponse res, string action) {
41     try {
42       _request  = req;
43       _response = res;
44       _format   = req.params["format"];
45       __send__(action);
46     } finally {
47       _request  = null;
48       _response = null;
49       _format   = null;
50     }
51   }
52 
53   @DynamicallyAvailable
54   void assets() {
55     serveStaticFiles("./public/")(_request, _response);
56   }
57 
58   protected {
59     void respondTo(string format, void delegate() yield) {
60       if (_format == format)
61         yield();
62     }
63 
64     void respondTo(void delegate(Format) yield) {
65       auto format = new Format(_format);
66       yield(format);
67     }
68   }
69 
70   static {
71     ActionController loadController(string controllerName, string prefix = "action_dispatch.controller") {
72       string[] builder;
73       if (prefix.length > 0)
74         builder ~= prefix;
75 
76       builder ~= controllerName;
77 
78       string factory = join(builder, ".");
79       auto   controller = cast(ActionController) Object.factory(factory);
80 
81       if (controller is null) {
82         foreach (string cName; getAllDynamicClasses()) {
83           if (match(cName, controllerName)) {
84             return loadController(cName, "");
85           }
86         }
87         throw new Exception("Your class " ~ prefix ~ "." ~ controllerName ~ " isn't callable dynamically. Classes are: " ~ to!string(getAllDynamicClasses()));
88       }
89 
90       return controller;
91     }
92   }
93 }
94 
95 unittest {
96   import dunit.toolkit;
97 
98   struct F { int a; }
99 
100   class FooController : ActionController {
101     mixin DynamicClassImplementation!();
102 
103     void foo(out F _foo) {
104       respondTo(delegate void(Format format) {
105         format.html(delegate void() { _foo.a = 3; });
106       });
107     }
108   }
109 
110   // This is a super janky test; basically we're testing that the
111   // new version of #respondTo works correctly by setting up
112   // the FooController which modifies the `f` variable only
113   // when the format is "html"
114   F f = { a: 0 };
115   auto foo = new FooController;
116   foo.format = "html";
117   foo.foo(f);
118   f.a.assertEqual(3);
119 
120   // The negative side of the test just makes sure that it doesn't
121   // modify the z struct when the format is set to "json"
122   F z = { a: 0 };
123   foo.format = "json";
124   foo.foo(z);
125   z.a.assertEqual(0);
126 }