1 module cucumber.server; 2 3 public import cucumber.keywords; 4 public import cucumber.feature; 5 6 import cucumber.reflection; 7 import cucumber.feature: PendingException; 8 import vibe.d; 9 import std.stdio; 10 import std.conv; 11 public import std.typecons: Flag, Yes, No; 12 13 alias DetailsFlag = Flag!"details"; 14 MatchResult[int] gMatches; 15 16 17 void runCucumberServer(ModuleNames...)(in ushort port, in DetailsFlag details = No.details) { 18 debug writeln("Running the Cucumber server on port ", port, " details ", details); 19 listenTCP(54321, (tcpConnection) { accept!ModuleNames(tcpConnection, details); }); 20 } 21 22 23 private void accept(ModuleNames...)(TCPConnection tcpConnection, in DetailsFlag details) { 24 debug writeln("Accepting a connection"); 25 while(tcpConnection.connected) { 26 auto bytes = tcpConnection.readLine(size_t.max, "\n"); 27 handleTcpRequest!ModuleNames(tcpConnection, (cast(string)bytes).strip(), details); 28 } 29 30 if(tcpConnection.connected) tcpConnection.close(); 31 } 32 33 private void send(TCPConnection tcpConnection, in string str) { 34 tcpConnection.write(str ~ "\n"); //I don't know why writeln doesn't work 35 } 36 37 private void handleTcpRequest(ModuleNames...)(TCPConnection tcpConnection, in string request, 38 in Flag!"details" details) { 39 const reply = handleRequest!ModuleNames(request, details); 40 debug writeln("Reply: ", reply); 41 tcpConnection.send(reply); 42 } 43 44 string handleRequest(ModuleNames...)(string request, in DetailsFlag details = No.details) { 45 import cucumber.feature: sanitize; 46 47 debug writeln("Request: ", request); 48 const fail = `["fail"]`; 49 50 try { 51 const json = parseJson(request); 52 const command = json[0].get!string; 53 switch(command) { 54 case "begin_scenario": return `["success"]`; 55 case "end_scenario": return `["success"]`; 56 case "step_matches": return handleStepMatches!ModuleNames(json, details); 57 case "invoke": return handleInvoke(json); 58 default: return fail; 59 } 60 } catch(Throwable ex) { 61 stderr.writeln("Error processing request: ", request); 62 stderr.writeln("Exception: ", ex.toString().sanitize()); 63 return fail; 64 } 65 } 66 67 private string handleStepMatches(ModuleNames...)(in Json json, in DetailsFlag details) { 68 const nameToMatch = json[1]["name_to_match"].get!string; 69 auto func = findMatch!ModuleNames(nameToMatch); 70 if(!func) return `["success",[]]`; 71 gMatches[func.id] = func; 72 73 auto infoElem = Json.emptyObject; 74 infoElem["id"] = func.id.to!string; 75 infoElem["args"] = Json.emptyArray; 76 77 if(details) { 78 infoElem["regexp"] = func.regex; 79 infoElem["source"] = func.source; 80 } 81 82 auto info = Json.emptyArray; 83 info ~= infoElem; 84 85 return `["success",` ~ info.toString ~ `]`; 86 } 87 88 89 private string handleInvoke (in Json json) { 90 debug writeln("handleInvoke for json ", json); 91 const invokeArgs = json[1]; 92 const id = invokeArgs["id"].to!int; 93 if(id !in gMatches) throw new Exception(text("Could not find match for id ", id)); 94 try { 95 gMatches[id](); 96 } catch(PendingException ex) { 97 return `["pending", "` ~ ex.msg ~ `"]`; 98 } catch(Throwable ex) { 99 return `["fail",{"message":"` ~ ex.msg ~ `", "exception": "` ~ ex.classinfo.name ~ `"}]`; 100 } 101 return `["success"]`; 102 }