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