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