1 module tinyredis.response; 2 3 /** 4 * Authors: Adil Baig, adil.baig@aidezigns.com 5 */ 6 7 private: 8 import std.array : split, replace, join; 9 import std.string : strip, format; 10 import std.algorithm : find; 11 import std.conv : to, text, ConvOverflowException; 12 import std.traits; 13 14 public : 15 16 const string CRLF = "\r\n"; 17 18 enum ResponseType : byte 19 { 20 Invalid, 21 Status, 22 Error, 23 Integer, 24 Bulk, 25 MultiBulk, 26 Nil 27 } 28 29 /** 30 * The Response struct represents returned data from Redis. 31 * 32 * Stores values true to form. Allows user code to query, cast, iterate, print, and log strings, ints, errors and all other return types. 33 * 34 * The role of the Response struct is to make it simple, yet accurate to retrieve returned values from Redis. To aid this 35 * it implements D op* functions as well as little helper methods that simplify user facing code. 36 */ 37 struct Response 38 { 39 ResponseType type; 40 int count; //Used for multibulk only. -1 is a valid multibulk. Indicates nil 41 42 private int curr; 43 44 union{ 45 string value; 46 long intval; 47 Response[] values; 48 } 49 50 bool isString() 51 { 52 return (type == ResponseType.Bulk); 53 } 54 55 bool isInt() 56 { 57 return (type == ResponseType.Integer); 58 } 59 60 bool isArray() 61 { 62 return (type == ResponseType.MultiBulk); 63 } 64 65 bool isError() 66 { 67 return (type == ResponseType.Error); 68 } 69 70 bool isNil() 71 { 72 return (type == ResponseType.Nil); 73 } 74 75 bool isStatus() 76 { 77 return (type == ResponseType.Status); 78 } 79 80 bool isValid() 81 { 82 return (type != ResponseType.Invalid); 83 } 84 85 /* 86 * Response is a BidirectionalRange 87 */ 88 @property bool empty() 89 { 90 if(!isArray()) { 91 return true; 92 } 93 94 return (curr == values.length); 95 } 96 97 @property auto front() 98 { 99 return values[curr]; 100 } 101 102 @property void popFront() 103 { 104 curr++; 105 } 106 107 @property auto back() 108 { 109 return values[values.length - 1]; 110 } 111 112 @property void popBack() 113 { 114 curr--; 115 } 116 117 // Response is a ForwardRange 118 @property auto save() 119 { 120 // Returning a copy of this struct object 121 return this; 122 } 123 124 /** 125 * Support foreach(k, v; response) 126 */ 127 int opApply(int delegate(size_t, Response) dg) 128 { 129 if(!isArray()) { 130 return 1; 131 } 132 133 foreach(k, v; values) { 134 dg(k, v); 135 } 136 137 return 0; 138 } 139 140 /** 141 * Support foreach_reverse(k, v; response) 142 */ 143 int opApplyReverse(int delegate(size_t, Response) dg) 144 { 145 if(!isArray()) { 146 return 1; 147 } 148 149 foreach_reverse(k, v; values) { 150 dg(k, v); 151 } 152 153 return 0; 154 } 155 156 /** 157 * Support foreach(v; response) 158 */ 159 int opApply(int delegate(Response) dg) 160 { 161 if(!isArray()) { 162 return 1; 163 } 164 165 foreach(v; values) { 166 dg(v); 167 } 168 169 return 0; 170 } 171 172 /** 173 * Support foreach_reverse(v; response) 174 */ 175 int opApplyReverse(int delegate(Response) dg) 176 { 177 if(!isArray()) { 178 return 1; 179 } 180 181 foreach_reverse(v; values) { 182 dg(v); 183 } 184 185 return 0; 186 } 187 188 /** 189 * Allows casting a Response to an integral, bool or string 190 */ 191 T opCast(T)() 192 if(is(T == bool) 193 || is(T == byte) 194 || is(T == short) 195 || is(T == int) 196 || is(T == long) 197 || is(T == string) 198 ) 199 { 200 static if(is(T == bool)) 201 return toBool(); 202 else static if(is(T == byte) || is(T == short) || is(T == int) || is(T == long)) 203 return toInt!(T)(); 204 else static if(is(T == string)) 205 return toString(); 206 } 207 208 /** 209 * Allows casting a Response to (u)byte[] 210 */ 211 C[] opCast(C : C[])() if(is(C == byte) || is(C == ubyte)) 212 { 213 return toBytes!(C)(); 214 } 215 216 /** 217 * Attempts to convert a response to an array of bytes 218 * 219 * For intvals - converts to an array of bytes that is Response.intval.sizeof long 220 * For Bulk - casts the string to C[] 221 * 222 * Returns an empty array in all other cases; 223 */ 224 @property @trusted C[] toBytes(C)() if(is(C == byte) || is(C == ubyte)) 225 { 226 switch(type) 227 { 228 case ResponseType.Integer : 229 C[] ret = new C[intval.sizeof]; 230 C* bytes = cast(C*)&intval; 231 for(C i = 0; i < intval.sizeof; i++) { 232 ret[i] = bytes[i]; 233 } 234 235 return ret; 236 237 case ResponseType.Bulk : 238 return cast(C[]) value; 239 240 default: 241 return []; 242 } 243 } 244 245 /** 246 * Attempts to check for truthiness of a Response. 247 * 248 * Returns false on failure. 249 */ 250 @property @trusted bool toBool() 251 { 252 switch(type) 253 { 254 case ResponseType.Integer : 255 return (intval > 0); 256 257 case ResponseType.Status : 258 return (value == "OK"); 259 260 case ResponseType.Bulk : 261 return (value.length > 0); 262 263 case ResponseType.MultiBulk : 264 return (values.length > 0); 265 266 default: 267 return false; 268 } 269 } 270 271 /** 272 * Converts a Response to an integral (byte to long) 273 * 274 * Only works with ResponseType.Integer and ResponseType.Bulk 275 * 276 * Throws : ConvOverflowException, RedisCastException 277 */ 278 @property @trusted T toInt(T = int)() 279 if(is(T == byte) || is(T == short) || is(T == int) || is(T == long)) 280 { 281 switch(type) 282 { 283 case ResponseType.Integer : 284 if(intval <= T.max) 285 return cast(T)intval; 286 else 287 throw new ConvOverflowException("Cannot convert " ~ to!string(intval) ~ " to " ~ to!(string)(typeid(T))); 288 // break; 289 290 case ResponseType.Bulk : 291 try{ 292 return to!(T)(value); 293 }catch(ConvOverflowException e) 294 { 295 e.msg = "Cannot convert " ~ value ~ " to " ~ to!(string)(typeid(T)); 296 throw e; 297 } 298 // break; 299 300 default: 301 throw new RedisCastException("Cannot cast " ~ type ~ " to " ~ to!(string)(typeid(T))); 302 } 303 } 304 305 /** 306 * Returns the value of this Response as a string 307 */ 308 @property @trusted string toString() 309 { 310 switch(type) 311 { 312 case ResponseType.Integer : 313 return to!(string)(intval); 314 315 case ResponseType.Error : 316 case ResponseType.Status : 317 case ResponseType.Bulk : 318 return value; 319 320 case ResponseType.MultiBulk : 321 return text(values); 322 323 default: 324 return ""; 325 } 326 } 327 328 /** 329 * Returns the value of this Response as a string, along with type information 330 */ 331 @property @trusted string toDiagnosticString() 332 { 333 final switch(type) 334 { 335 case ResponseType.Nil : 336 return "(Nil)"; 337 338 case ResponseType.Error : 339 return "(Err) " ~ value; 340 341 case ResponseType.Integer : 342 return "(Integer) " ~ to!(string)(intval); 343 344 case ResponseType.Status : 345 return "(Status) " ~ value; 346 347 case ResponseType.Bulk : 348 return value; 349 350 case ResponseType.MultiBulk : 351 string[] t; 352 353 foreach(v; values) 354 t ~= v.toDiagnosticString(); 355 356 return text(t); 357 358 case ResponseType.Invalid : 359 return "(Invalid)"; 360 } 361 } 362 } 363 364 /* ----------- EXCEPTIONS ------------- */ 365 366 class RedisCastException : Exception { 367 this(string msg) { super(msg); } 368 } 369