1 module dcord.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 dcord.types,
12        dcord.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 /**
86   Wrapper for HTTP REST Responses.
87 */
88 class APIResponse {
89   private {
90     HTTPClientResponse res;
91   }
92 
93   // The library caches content so HTTPClientResponse can be GC'd without pain
94   string content;
95 
96   this(HTTPClientResponse res) {
97     this.res = res;
98     this.content = res.bodyReader.readAllUTF8();
99     this.res.disconnect();
100   }
101 
102   /**
103     Raises an APIError exception if the request failed.
104   */
105   APIResponse ok() {
106     if (100 < this.statusCode && this.statusCode < 400) {
107       return this;
108     }
109 
110     throw new APIError(this.statusCode, this.content);
111   }
112 
113   @property string contentType() {
114     return this.res.contentType;
115   }
116 
117   @property int statusCode() {
118     return this.res.statusCode;
119   }
120 
121   @property VibeJSON vibeJSON() {
122     return parseJsonString(this.content);
123   }
124 
125   string header(string name, string def="") {
126     if (name in this.res.headers) {
127       return this.res.headers[name];
128     }
129 
130     return def;
131   }
132 }
133