1 module vibed_rest_axios.rest_axios;
2 
3 import vibe.web.internal.rest.common;
4 import vibe.web.rest: RestInterfaceSettings;
5 
6 
7 void genRestAxios (I) (RestInterfaceSettings vibeRestSettings, string dir = "./", string name = null) {
8     import std.file: copy;
9     import std.stdio: File;
10 
11     if (name == null) name = I.stringof;
12 
13     string outData = generateInterface!I(vibeRestSettings);
14     outData = genMainTemplate!"main.mixin.js"(name, outData);
15 
16     // create files
17     auto f = File(dir~"/"~name~".js", "w");
18     f.write(outData);
19     f.close();
20 }
21 
22 string generateInterface (I) (RestInterfaceSettings vibeRestSettings)
23     if (is(I == interface))
24 {
25     import std.conv : to;
26     import std.traits : FunctionTypeOf, ReturnType;
27 
28     import vibe.http.common : HTTPMethod;
29     import vibe.inet.url: URL;
30     import vibe.data.json;
31     import vibe.data.serialization: serialize;
32 
33     string getFmtParam(ref const PathPart p)
34     {
35         if (!p.isParameter) return serialize!JsonSerializer(p.text).toString();
36         return "toRestString("~p.text~")";
37     }
38 
39     string outData = "";
40     auto vibeIntf = RestInterface!I(vibeRestSettings, true);
41 
42     foreach (i, SI; vibeIntf.SubInterfaceTypes) {
43         string name = __traits(identifier, vibeIntf.SubInterfaceFunctions[i]);
44         if (i != 0) outData ~= ",\n";
45         outData ~= "'" ~ name ~ "': { " ~ generateInterface!SI(vibeIntf.subInterfaces[i].settings) ~ " }";
46     }
47     if (vibeIntf.SubInterfaceTypes.length != 0) outData ~= ",\n";
48 
49     foreach (i, F; vibeIntf.RouteFunctions) {
50         alias FT = FunctionTypeOf!F;
51         auto route = vibeIntf.routes[i];
52 
53 
54         string url = "";
55 
56         // url assembly
57         if (route.pathHasPlaceholders) {
58             auto burl = URL(vibeIntf.baseURL);
59             if (burl.host.length) {
60                 // extract the server part of the URL
61                 burl.pathString = "/";
62                 url ~= serialize!JsonSerializer((burl.toString()[0 .. $-1])).toString() ~ " + ";
63             }
64             // and then assemble the full path piece-wise
65 
66             // if route.pathHasPlaceholders no need to check route.fullPathParts.length
67             // because it fills in module vibe.web.internal.rest.common at 208 line only
68             url ~= getFmtParam(route.fullPathParts[0]);
69             foreach (p; route.fullPathParts[1..$]) {
70                 url ~= " + "~ getFmtParam(p);
71             }
72         } else {
73             url = "'"~concatURL(vibeIntf.baseURL, route.pattern)~"'";
74         }
75 
76         // query parameters
77         if (route.queryParameters.length) {
78             foreach (j, p; route.queryParameters) {
79                 url ~= ` + "` ~ (j == 0 ? `?` : `&`) ~ p.fieldName ~ `=" + toRestString(` ~ p.name ~ `)`;
80             }
81         }
82 
83         // body parameters
84         string bodyParams = "";
85         if (route.wholeBodyParameter.name.length) {
86             bodyParams = "'"~route.wholeBodyParameter.name~"'";
87         } else if (route.bodyParameters.length) {
88             bodyParams = paramsToStr(route.bodyParameters);
89         }
90 
91         // header parameters
92         string headers = "";
93         if (route.headerParameters.length > 0) {
94             headers = paramsToStr(route.headerParameters);
95         }
96 
97         string axiosTmpl = genAxiosTemplate!"axios.mixin.js"(url, route.method.to!string, headers, bodyParams);
98         string funParams = "";
99         foreach (j, p; route.parameters) {
100             if (j != 0) funParams ~= ",";
101             funParams ~= p.name;
102         }
103         if (i != 0) outData ~= ",\n";
104         outData ~= genMethodTemplate!"method.mixin.js"(route.functionName, funParams, axiosTmpl);
105     }
106 
107     return outData;
108 }
109 
110 string paramsToStr (in Parameter[] params) {
111     import vibe.data.json : Json;
112 
113     string str = "";
114     foreach (j, p; params) {
115         if (j != 0) str ~= ", ";
116         str ~= Json(p.fieldName).toString()~": "~p.name;
117     }
118     return "{" ~ str ~ "}";
119 }
120 
121 private string genAxiosTemplate (string path)(in string url, in string method, in string headers, in string data)
122 {
123     return mixin("`" ~ import(path) ~ "`");
124 }
125 
126 private string genMainTemplate (string path)(in string name, in string obBody)
127 {
128     return mixin("`" ~ import(path) ~ "`");
129 }
130 
131 private string genMethodTemplate (string path)(in string name, in string params, in string result)
132 {
133     return mixin("`" ~ import(path) ~ "`");
134 }
135 
136 
137 package string concatURL(string prefix, string url, bool trailing = false)
138 @safe {
139     import std.algorithm : startsWith, endsWith;
140 
141     auto pre = prefix.endsWith("/");
142     auto post = url.startsWith("/");
143 
144     if (!url.length) return trailing && !pre ? prefix ~ "/" : prefix;
145 
146     auto suffix = trailing && !url.endsWith("/") ? "/" : null;
147 
148     if (pre) {
149         // "/" is ASCII, so can just slice
150         if (post) return prefix ~ url[1 .. $] ~ suffix;
151         else return prefix ~ url ~ suffix;
152     } else {
153         if (post) return prefix ~ url ~ suffix;
154         else return prefix ~ "/" ~ url ~ suffix;
155     }
156 }