1 module action_dispatch.route; 2 3 import action_dispatch.all; 4 5 class ActionRoute { 6 protected { 7 string _path; 8 string _controllerName; 9 string _action; 10 HTTPMethod _method; 11 ActionController _controller; 12 } 13 14 public static { 15 enum maxRouteParameters = 64; 16 } 17 18 this(HTTPMethod method, string path, string controllerName, string action) { 19 _controllerName = controllerName; 20 _method = method; 21 _path = path; 22 _action = action; 23 } 24 25 @property string controllerName() { 26 return _controllerName; 27 } 28 29 @property string path() { 30 return _path; 31 } 32 33 @property HTTPMethod method() { 34 return _method; 35 } 36 37 @property string action() { 38 return _action; 39 } 40 41 @property ActionController controller() { 42 if (_controller is null) { 43 _controller = ActionController.loadController(_controllerName); 44 } 45 46 return _controller; 47 } 48 49 override string toString() { 50 string method; 51 if (_method == HTTPMethod.GET) 52 method = "GET"; 53 else if (_method == HTTPMethod.PUT) 54 method = "PUT"; 55 else if (_method == HTTPMethod.POST) 56 method = "POST"; 57 else if (_method == HTTPMethod.PATCH) 58 method = "PATCH"; 59 else if (_method == HTTPMethod.DELETE) 60 method = "DELETE"; 61 62 return "#" ~ method ~ "\t\t" ~ _path ~ "\t\t" ~ _controllerName ~ "\t\t#" ~ _action; 63 } 64 65 bool matches(string url, ref string[string] params) const { 66 size_t i, j; 67 68 Tuple!(string, string)[maxRouteParameters] tmpparams; 69 size_t tmpparams_length = 0; 70 71 // if the url matches the path totally, just return true; 72 // that means there are no variables in the url 73 if (url == _path) 74 return true; 75 76 // if there's not a direct match, loop through looking 77 // for a wildcard or variable declaration 78 for (i = 0, j = 0; i < url.length && j < _path.length;) { 79 80 // if we hit a wildcard add any accumulated tmpparams 81 // to the params dictionary 82 if (_path[j] == '*') { 83 foreach (t; tmpparams[0..tmpparams_length]) 84 params[t[0]] = t[1]; 85 return true; 86 } 87 88 // if the current index in the url and the _path match 89 // continue to the next index for each 90 if (url[i] == _path[j]) { 91 i++; 92 j++; 93 } 94 // if we encounter a variable we need to find out what it 95 // is called, and what the value is. We store it in the tmp 96 // params dictionary until we verify the full match 97 else if (_path[j] == ':') { 98 j++; 99 string name = skipPathNode(_path, j); 100 string match = skipPathNode(url, i); 101 assert(tmpparams_length < maxRouteParameters, "Maximum number of route parameters exceeded."); 102 tmpparams[tmpparams_length++] = tuple(name, urlDecode(match)); 103 } 104 // if we get this far we don't have a match so we exit 105 else return false; 106 } 107 108 // if we exit the for loop and get here we just need to move the 109 // tmp params to the actual params dictionary and return true 110 if ((j < _path.length && _path[j] == '*') || (i == url.length && j == _path.length)) { 111 foreach (t; tmpparams[0..tmpparams_length]) 112 params[t[0]] = t[1]; 113 return true; 114 } 115 116 // falling all the way through gives us a false hit on the match 117 return false; 118 } 119 } 120 121 // helper method to just jump forward a variable name 122 // or a value name 123 private string skipPathNode(string str, ref size_t idx) { 124 size_t start = idx; 125 while ( idx < str.length && str[idx] != '/' ) idx++; 126 return str[start .. idx]; 127 } 128 129 unittest { 130 import dunit.toolkit; 131 132 string[string] params; 133 auto route = new ActionRoute(HTTPMethod.GET, "/", "MainController", "index"); 134 135 route.path.assertEqual("/"); 136 route.controllerName.assertEqual("MainController"); 137 route.method.assertEqual(HTTPMethod.GET); 138 route.action.assertEqual("index"); 139 140 route.matches("/", params).assertEqual(true); 141 route.matches("/foo", params).assertEqual(false); 142 143 auto dynamic_route = new ActionRoute(HTTPMethod.GET, "/users/:id", "UsersController", "show"); 144 dynamic_route.matches("/users/1", params).assertEqual(true); 145 params["id"].assertEqual("1"); 146 147 auto star_route = new ActionRoute(HTTPMethod.GET, "/dog/*", "DogsController", "index"); 148 star_route.matches("/dog/foo/bar", params).assertEqual(true); 149 star_route.matches("/cat/foo/bar", params).assertEqual(false); 150 }