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