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