1 module dscord.api.util;
2 
3 import std.uri,
4        std.array,
5        std.format,
6        std.algorithm.iteration;
7 
8 import vibe.http.client,
9        vibe.stream.operations;
10 
11 import dscord.types.all,
12        dscord.util.errors;
13 
14 /**
15   An error returned from the `APIClient` based on HTTP response code.
16 */
17 class APIError : BaseError {
18   this(int code, string msg) {
19     super("[%s] %s", code, msg);
20   }
21 }
22 
23 /**
24   Simple URL constructor to help building routes
25 */
26 struct U {
27   private {
28     string          _bucket;
29     string[]        paths;
30     string[string]  params;
31   }
32 
33   @property string value() {
34     string url = this.paths.join("/");
35 
36     if (this.params.length) {
37       string[] parts;
38       foreach (ref key, ref value; this.params) {
39         parts ~= format("%s=%s", key, encodeComponent(value));
40       }
41       url ~= "?" ~ parts.join("&");
42     }
43 
44     return "/" ~ url;
45   }
46 
47   this(string url) {
48     this.paths ~= url;
49   }
50 
51   this(string paramKey, string paramValue) {
52     this.params[paramKey] = paramValue;
53   }
54 
55   string getBucket() {
56     return this._bucket;
57   }
58 
59   U opCall(string url) {
60     this.paths ~= url;
61     return this;
62   }
63 
64   U opCall(Snowflake s) {
65     this.paths ~= s.toString();
66     return this;
67   }
68 
69   U opCall(string paramKey, string paramValue) {
70     this.params[paramKey] = paramValue;
71     return this;
72   }
73 
74   U bucket(string bucket) {
75     this._bucket = bucket;
76     return this;
77   }
78 
79   U param(string name, string value) {
80     this.params[name] = value;
81     return this;
82   }
83 }
84 
85 unittest {
86   assert(U("this")("is")("a")("test")("key", "value").value == "/this/is/a/test/?key=value");
87 }
88 
89 /**
90   Wrapper for HTTP REST Responses.
91 */
92 class APIResponse {
93   private {
94     HTTPClientResponse res;
95   }
96 
97   // We cache content so HTTPClientResponse can be GC'd without pain
98   string content;
99 
100   this(HTTPClientResponse res) {
101     this.res = res;
102     this.content = res.bodyReader.readAllUTF8();
103     this.res.disconnect();
104   }
105 
106   /**
107     Raises an APIError exception if the request failed.
108   */
109   APIResponse ok() {
110     if (100 < this.statusCode && this.statusCode < 400) {
111       return this;
112     }
113 
114     throw new APIError(this.statusCode, this.content);
115   }
116 
117   @property string contentType() {
118     return this.res.contentType;
119   }
120 
121   @property int statusCode() {
122     return this.res.statusCode;
123   }
124 
125   @property VibeJSON vibeJSON() {
126     return parseJsonString(this.content);
127   }
128 
129   @property JSON fastJSON() {
130     return parseTrustedJSON(this.content);
131   }
132 
133   string header(string name, string def="") {
134     if (name in this.res.headers) {
135       return this.res.headers[name];
136     }
137 
138     return def;
139   }
140 }
141