1 /*
2  * Archttp - A highly performant web framework written in D.
3  *
4  * Copyright (C) 2021-2022 Kerisy.com
5  *
6  * Website: https://www.kerisy.com
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module archttp.Router;
13 
14 public import archttp.Route;
15 public import archttp.HttpMethod;
16 
17 import std.stdio;
18 
19 import std.regex : regex, match, matchAll;
20 import std.array : replaceFirst;
21 import std.uri : decode;
22 
23 class Router(RoutingHandler)
24 {
25     private
26     {
27         Route!RoutingHandler[string] _routes;
28         Route!RoutingHandler[] _regexRoutes;
29     }
30     
31     Router add(string path, HttpMethod method, RoutingHandler handler)
32     {
33         auto route = CreateRoute(path, method, handler);
34 
35         if (route.regular)
36         {
37             _regexRoutes ~= route;
38         }
39         else
40         {
41             _routes[path] = route;
42         }
43 
44         return this;
45     }
46 
47     private Route!RoutingHandler CreateRoute(string path, HttpMethod method, RoutingHandler handler)
48     {
49         auto route = new Route!RoutingHandler(path, method, handler);
50 
51         auto matches = path.matchAll(regex(`\{(\w+)(:([^\}]+))?\}`));
52         if (matches)
53         {
54             string[uint] paramKeys;
55             int paramCount = 0;
56             string pattern = path;
57             string urlTemplate = path;
58 
59             foreach (m; matches)
60             {
61                 paramKeys[paramCount] = m[1];
62                 string reg = m[3].length ? m[3] : "\\w+";
63                 pattern = pattern.replaceFirst(m[0], "(" ~ reg ~ ")");
64                 urlTemplate = urlTemplate.replaceFirst(m[0], "{" ~ m[1] ~ "}");
65                 paramCount++;
66             }
67 
68             route.pattern = pattern;
69             route.paramKeys = paramKeys;
70             route.regular = true;
71             route.urlTemplate = urlTemplate;
72         }
73 
74         return route;
75     }
76 
77     RoutingHandler match(string path, HttpMethod method, ref string[string] params)
78     {
79         auto route = _routes.get(path, null);
80 
81         if (route is null)
82         {
83             foreach ( r ; _regexRoutes )
84             {
85                 auto matched = path.match(regex(r.pattern));
86 
87                 if (matched)
88                 {
89                     route = r;
90                     
91                     foreach ( i, key ; route.paramKeys )
92                     {
93                         params[key] = decode(matched.captures[i + 1]);
94                     }
95                 }
96             }
97         }
98 
99         if (route is null)
100         {
101             writeln(path, " is Not Found.");
102             return cast(RoutingHandler) null;
103         }
104 
105         RoutingHandler handler;
106         handler = route.find(method);
107 
108         if (handler is null)
109         {
110             writeln(method, " method is Not Allowed.");
111             return cast(RoutingHandler) null;
112         }
113 
114         return handler;
115     }
116 }