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