1 module tinyredis.parser; 2 3 /** 4 * Authors: Adil Baig, adil.baig@aidezigns.com 5 */ 6 7 private: 8 import std.conv : to; 9 import std.range : isInputRange, isForwardRange, isBidirectionalRange, retro; 10 11 public : 12 13 import tinyredis.response; 14 15 /** 16 * Parse a byte stream into a Response struct. 17 * 18 * The parser works to identify a minimum complete Response. If successful, it removes that chunk from "mb" and returns a Response struct. 19 * On failure it returns a ResponseType.Invalid Response and leaves "mb" untouched. 20 */ 21 @trusted Response parseResponse(ref byte[] mb) 22 { 23 Response response; 24 response.type = ResponseType.Invalid; 25 26 if(mb.length < 4) 27 return response; 28 29 char type = mb[0]; 30 31 byte[] bytes; 32 if(!getData(mb[1 .. $], bytes)) //This could be an int value (:), a bulk byte length ($), a status message (+) or an error value (-) 33 return response; 34 35 size_t tpos = 1 + bytes.length; 36 37 if(tpos + 2 > mb.length) 38 return response; 39 else 40 tpos += 2; //for "\r\n" 41 42 switch(type) 43 { 44 case '+' : 45 response.type = ResponseType.Status; 46 response.value = cast(string)bytes; 47 break; 48 49 case '-' : 50 throw new RedisResponseException(cast(string)bytes); 51 // break; 52 53 case ':' : 54 response.type = ResponseType.Integer; 55 response.intval = to!long(cast(char[])bytes); 56 break; 57 58 case '$' : 59 int l = to!int(cast(char[])bytes); 60 if(l == -1) 61 { 62 response.type = ResponseType.Nil; 63 break; 64 } 65 66 if(tpos + l >= mb.length) //We don't have enough data. Let's return an invalid response. 67 return response; 68 else 69 { 70 response.value = cast(string)mb[tpos .. tpos + l]; 71 tpos += l; 72 73 if(tpos + 2 > mb.length) 74 return response; 75 else 76 tpos += 2; 77 } 78 79 response.type = ResponseType.Bulk; 80 break; 81 82 case '*' : 83 int l = to!int(cast(char[])bytes); 84 if(l == -1) 85 { 86 response.type = ResponseType.Nil; 87 break; 88 } 89 90 response.type = ResponseType.MultiBulk; 91 response.count = l; 92 93 break; 94 95 default : 96 return response; 97 } 98 99 mb = mb[tpos .. $]; 100 return response; 101 } 102 103 104 105 /* ----------- EXCEPTIONS ------------- */ 106 107 class RedisResponseException : Exception { 108 this(string msg) { super(msg); } 109 } 110 111 112 private : 113 @safe pure bool getData(const(byte[]) mb, ref byte[] data) 114 { 115 foreach(p, byte c; mb) 116 if(c == 13) //'\r' 117 return true; 118 else 119 data ~= c; 120 121 return false; 122 } 123 124 125 unittest 126 { 127 //Test Nil bulk 128 byte[] stream = cast(byte[])"$-1\r\n"; 129 auto response = parseResponse(stream); 130 assert(response.toString == ""); 131 assert(response.toBool == false); 132 assert(cast(bool)response == false); 133 try{ 134 cast(int)response; 135 assert(false); 136 }catch(RedisCastException e) 137 { 138 assert(true); 139 } 140 141 //Test Nil multibulk 142 stream = cast(byte[])"*-1\r\n"; 143 response = parseResponse(stream); 144 assert(response.toString == ""); 145 assert(response.toBool == false); 146 assert(cast(bool)response == false); 147 try{ 148 cast(int)response; 149 assert(false); 150 }catch(RedisCastException e) 151 { 152 assert(true); 153 } 154 155 //Empty Bulk 156 stream = cast(byte[])"$0\r\n\r\n"; 157 response = parseResponse(stream); 158 assert(response.toString == ""); 159 assert(response.toBool == false); 160 assert(cast(bool)response == false); 161 162 stream = cast(byte[])"*4\r\n$3\r\nGET\r\n$1\r\n*\r\n:123\r\n+A Status Message\r\n"; 163 164 response = parseResponse(stream); 165 assert(response.type == ResponseType.MultiBulk); 166 assert(response.count == 4); 167 assert(response.values.length == 0); 168 169 response = parseResponse(stream); 170 assert(response.type == ResponseType.Bulk); 171 assert(response.value == "GET"); 172 assert(cast(string)response == "GET"); 173 174 response = parseResponse(stream); 175 assert(response.type == ResponseType.Bulk); 176 assert(response.value == "*"); 177 assert(cast(bool)response == true); 178 179 response = parseResponse(stream); 180 assert(response.type == ResponseType.Integer); 181 assert(response.intval == 123); 182 assert(cast(string)response == "123"); 183 assert(cast(int)response == 123); 184 185 response = parseResponse(stream); 186 assert(response.type == ResponseType.Status); 187 assert(response.value == "A Status Message"); 188 assert(cast(string)response == "A Status Message"); 189 try{ 190 cast(int)response; 191 }catch(RedisCastException e) 192 { 193 //Exception caught 194 } 195 196 //Stream should have been used up, verify 197 assert(stream.length == 0); 198 assert(parseResponse(stream).type == ResponseType.Invalid); 199 200 //Long overflow checking 201 stream = cast(byte[])":9223372036854775808\r\n"; 202 try{ 203 parseResponse(stream); 204 assert(false, "Tried to convert long.max+1 to long"); 205 } 206 catch(ConvOverflowException e){} 207 208 Response r = {type : ResponseType.Bulk, value : "9223372036854775807"}; 209 try{ 210 r.toInt(); //Default int 211 assert(false, "Tried to convert long.max to int"); 212 } 213 catch(ConvOverflowException e) 214 { 215 //Ok, exception thrown as expected 216 } 217 218 r.value = "127"; 219 assert(r.toInt!byte() == 127); 220 assert(r.toInt!short() == 127); 221 assert(r.toInt!int() == 127); 222 assert(r.toInt!long() == 127); 223 224 stream = cast(byte[])"*0\r\n"; 225 response = parseResponse(stream); 226 assert(response.count == 0); 227 assert(response.values.length == 0); 228 assert(response.values == []); 229 assert(response.toString == "[]"); 230 assert(response.toBool == false); 231 assert(cast(bool)response == false); 232 try{ 233 cast(int)response; 234 }catch(RedisCastException e) 235 { 236 assert(true); 237 } 238 239 //Testing opApply 240 stream = cast(byte[])"*0\r\n"; 241 response = parseResponse(stream); 242 foreach(k, v; response) 243 assert(false, "opApply is broken"); 244 foreach(v; response) 245 assert(false, "opApply is broken"); 246 247 stream = cast(byte[])"$2\r\n$2\r\n"; 248 response = parseResponse(stream); 249 foreach(k, v; response) 250 assert(false, "opApply is broken"); 251 foreach(v; response) 252 assert(false, "opApply is broken"); 253 254 stream = cast(byte[])":1000\r\n"; 255 response = parseResponse(stream); 256 foreach(k, v; response) 257 assert(false, "opApply is broken"); 258 foreach(v; response) 259 assert(false, "opApply is broken"); 260 261 //Testing opApplyReverse 262 stream = cast(byte[])"*0\r\n"; 263 response = parseResponse(stream); 264 foreach_reverse(k, v; response) 265 assert(false, "opApplyReverse is broken"); 266 foreach_reverse(v; response) 267 assert(false, "opApplyReverse is broken"); 268 269 270 //Testing ranges for Response 271 assert(isInputRange!Response); 272 assert(isForwardRange!Response); 273 assert(isBidirectionalRange!Response); 274 }