diff vamp-server/convert.cpp @ 75:81e1c48e97f9

Rearrange and rename to Piper C++ structure
author Chris Cannam <c.cannam@qmul.ac.uk>
date Mon, 10 Oct 2016 16:31:09 +0100
parents
children c897c9a8daf1
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vamp-server/convert.cpp	Mon Oct 10 16:31:09 2016 +0100
@@ -0,0 +1,610 @@
+
+#include "vamp-json/VampJson.h"
+#include "vamp-capnp/VampnProto.h"
+#include "vamp-support/RequestOrResponse.h"
+#include "vamp-support/PreservingPluginHandleMapper.h"
+
+#include <iostream>
+#include <sstream>
+#include <stdexcept>
+
+using namespace std;
+using namespace json11;
+using namespace piper;
+
+void usage()
+{
+    string myname = "piper-convert";
+    cerr << "\n" << myname <<
+	": Validate and convert Piper request and response messages\n\n"
+	"    Usage: " << myname << " [-i <informat>] [-o <outformat>] request\n"
+	"           " << myname << " [-i <informat>] [-o <outformat>] response\n\n"
+	"    where\n"
+	"       <informat>: the format to read from stdin\n"
+	"           (\"json\" or \"capnp\", default is \"json\")\n"
+	"       <outformat>: the format to convert to and write to stdout\n"
+	"           (\"json\", \"json-b64\" or \"capnp\", default is \"json\")\n"
+	"       request|response: whether messages are Vamp request or response type\n\n"
+	"If <informat> and <outformat> differ, convert from <informat> to <outformat>.\n"
+	"If <informat> and <outformat> are the same, just check validity of incoming\n"
+	"messages and pass them to output.\n\n"
+	"Specifying \"json-b64\" as output format forces base64 encoding for process and\n"
+	"feature blocks, unlike the \"json\" output format which uses text encoding.\n"
+	"The \"json\" input format accepts either.\n\n";
+
+    exit(2);
+}
+
+Json
+convertRequestJson(string input, string &err)
+{
+    Json j = Json::parse(input, err);
+    if (err != "") {
+	err = "invalid json: " + err;
+	return {};
+    }
+    if (!j.is_object()) {
+	err = "object expected at top level";
+    } else if (!j["method"].is_string()) {
+	err = "string expected for method field";
+    } else if (!j["params"].is_null() && !j["params"].is_object()) {
+	err = "object expected for params field";
+    }
+    return j;
+}
+
+Json
+convertResponseJson(string input, string &err)
+{
+    Json j = Json::parse(input, err);
+    if (err != "") {
+	err = "invalid json: " + err;
+	return {};
+    }
+    if (!j.is_object()) {
+	err = "object expected at top level";
+    } else {
+        if (!j["result"].is_object()) {
+            if (!j["error"].is_object()) {
+                err = "expected either result or error object";
+            }
+        }
+    }
+    return j;
+}
+
+//!!! Lots of potential for refactoring the conversion classes based
+//!!! on the common matter in the following eight functions...
+
+PreservingPluginHandleMapper mapper;
+
+static RequestOrResponse::RpcId
+readJsonId(const Json &j)
+{
+    RequestOrResponse::RpcId id;
+
+    if (j["id"].is_number()) {
+        id.type = RequestOrResponse::RpcId::Number;
+        id.number = j["id"].number_value();
+    } else if (j["id"].is_string()) {
+        id.type = RequestOrResponse::RpcId::Tag;
+        id.tag = j["id"].string_value();
+    } else {
+        id.type = RequestOrResponse::RpcId::Absent;
+    }
+
+    return id;
+}
+
+static Json
+writeJsonId(const RequestOrResponse::RpcId &id)
+{
+    if (id.type == RequestOrResponse::RpcId::Number) {
+        return id.number;
+    } else if (id.type == RequestOrResponse::RpcId::Tag) {
+        return id.tag;
+    } else {
+        return Json();
+    }
+}
+
+template <typename Reader>
+static RequestOrResponse::RpcId
+readCapnpId(const Reader &r)
+{
+    int number;
+    string tag;
+    switch (r.getId().which()) {
+    case RpcRequest::Id::Which::NUMBER:
+        number = r.getId().getNumber();
+        return { RequestOrResponse::RpcId::Number, number, "" };
+    case RpcRequest::Id::Which::TAG:
+        tag = r.getId().getTag();
+        return { RequestOrResponse::RpcId::Tag, 0, tag };
+    case RpcRequest::Id::Which::NONE:
+        return { RequestOrResponse::RpcId::Absent, 0, "" };
+    }
+    return {};
+}
+
+template <typename Builder>
+static void
+buildCapnpId(Builder &b, const RequestOrResponse::RpcId &id)
+{
+    switch (id.type) {
+    case RequestOrResponse::RpcId::Number:
+        b.getId().setNumber(id.number);
+        break;
+    case RequestOrResponse::RpcId::Tag:
+        b.getId().setTag(id.tag);
+        break;
+    case RequestOrResponse::RpcId::Absent:
+        b.getId().setNone();
+        break;
+    }
+}
+
+RequestOrResponse
+readRequestJson(string &err)
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Request;
+
+    string input;
+    if (!getline(cin, input)) {
+	// the EOF case, not actually an error
+	rr.type = RRType::NotValid;
+	return rr;
+    }
+    
+    Json j = convertRequestJson(input, err);
+    if (err != "") return {};
+
+    rr.type = VampJson::getRequestResponseType(j, err);
+    if (err != "") return {};
+
+    rr.id = readJsonId(j);
+
+    VampJson::BufferSerialisation serialisation =
+        VampJson::BufferSerialisation::Array;
+
+    switch (rr.type) {
+
+    case RRType::List:
+	VampJson::toRpcRequest_List(j, err); // type check only
+	break;
+    case RRType::Load:
+	rr.loadRequest = VampJson::toRpcRequest_Load(j, err);
+	break;
+    case RRType::Configure:
+	rr.configurationRequest = VampJson::toRpcRequest_Configure(j, mapper, err);
+	break;
+    case RRType::Process:
+	rr.processRequest = VampJson::toRpcRequest_Process(j, mapper, serialisation, err);
+	break;
+    case RRType::Finish:
+	rr.finishRequest = VampJson::toRpcRequest_Finish(j, mapper, err);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeRequestJson(RequestOrResponse &rr, bool useBase64)
+{
+    Json j;
+
+    VampJson::BufferSerialisation serialisation =
+        (useBase64 ?
+         VampJson::BufferSerialisation::Base64 :
+         VampJson::BufferSerialisation::Array);
+
+    Json id = writeJsonId(rr.id);
+    
+    switch (rr.type) {
+
+    case RRType::List:
+	j = VampJson::fromRpcRequest_List(id);
+	break;
+    case RRType::Load:
+	j = VampJson::fromRpcRequest_Load(rr.loadRequest, id);
+	break;
+    case RRType::Configure:
+	j = VampJson::fromRpcRequest_Configure(rr.configurationRequest, mapper, id);
+	break;
+    case RRType::Process:
+	j = VampJson::fromRpcRequest_Process
+	    (rr.processRequest, mapper, serialisation, id);
+	break;
+    case RRType::Finish:
+	j = VampJson::fromRpcRequest_Finish(rr.finishRequest, mapper, id);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    cout << j.dump() << endl;
+}
+
+RequestOrResponse
+readResponseJson(string &err)
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Response;
+
+    string input;
+    if (!getline(cin, input)) {
+	// the EOF case, not actually an error
+	rr.type = RRType::NotValid;
+	return rr;
+    }
+
+    Json j = convertResponseJson(input, err);
+    if (err != "") return {};
+
+    rr.type = VampJson::getRequestResponseType(j, err);
+    if (err != "") return {};
+
+    rr.id = readJsonId(j);
+    
+    VampJson::BufferSerialisation serialisation =
+        VampJson::BufferSerialisation::Array;
+
+    rr.success = j["success"].bool_value();
+    rr.errorText = j["errorText"].string_value();
+
+    switch (rr.type) {
+
+    case RRType::List:
+	rr.listResponse = VampJson::toRpcResponse_List(j, err);
+	break;
+    case RRType::Load:
+	rr.loadResponse = VampJson::toRpcResponse_Load(j, mapper, err);
+	break;
+    case RRType::Configure:
+	rr.configurationResponse = VampJson::toRpcResponse_Configure(j, mapper, err);
+	break;
+    case RRType::Process: 
+	rr.processResponse = VampJson::toRpcResponse_Process(j, mapper, serialisation, err);
+	break;
+    case RRType::Finish:
+	rr.finishResponse = VampJson::toRpcResponse_Finish(j, mapper, serialisation, err);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeResponseJson(RequestOrResponse &rr, bool useBase64)
+{
+    Json j;
+
+    VampJson::BufferSerialisation serialisation =
+        (useBase64 ?
+         VampJson::BufferSerialisation::Base64 :
+         VampJson::BufferSerialisation::Array);
+
+    Json id = writeJsonId(rr.id);
+
+    if (!rr.success) {
+
+	j = VampJson::fromError(rr.errorText, rr.type, id);
+
+    } else {
+    
+	switch (rr.type) {
+
+	case RRType::List:
+	    j = VampJson::fromRpcResponse_List(rr.listResponse, id);
+	    break;
+	case RRType::Load:
+	    j = VampJson::fromRpcResponse_Load(rr.loadResponse, mapper, id);
+	    break;
+	case RRType::Configure:
+	    j = VampJson::fromRpcResponse_Configure(rr.configurationResponse,
+                                                    mapper, id);
+	    break;
+	case RRType::Process:
+	    j = VampJson::fromRpcResponse_Process
+		(rr.processResponse, mapper, serialisation, id);
+	    break;
+	case RRType::Finish:
+	    j = VampJson::fromRpcResponse_Finish
+		(rr.finishResponse, mapper, serialisation, id);
+	    break;
+	case RRType::NotValid:
+	    break;
+	}
+    }
+    
+    cout << j.dump() << endl;
+}
+
+RequestOrResponse
+readRequestCapnp(kj::BufferedInputStreamWrapper &buffered)
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Request;
+
+    ::capnp::InputStreamMessageReader message(buffered);
+    RpcRequest::Reader reader = message.getRoot<RpcRequest>();
+    
+    rr.type = VampnProto::getRequestResponseType(reader);
+    rr.id = readCapnpId(reader);
+
+    switch (rr.type) {
+
+    case RRType::List:
+	VampnProto::readRpcRequest_List(reader); // type check only
+	break;
+    case RRType::Load:
+	VampnProto::readRpcRequest_Load(rr.loadRequest, reader);
+	break;
+    case RRType::Configure:
+	VampnProto::readRpcRequest_Configure(rr.configurationRequest,
+					      reader, mapper);
+	break;
+    case RRType::Process:
+	VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper);
+	break;
+    case RRType::Finish:
+	VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeRequestCapnp(RequestOrResponse &rr)
+{
+    ::capnp::MallocMessageBuilder message;
+    RpcRequest::Builder builder = message.initRoot<RpcRequest>();
+
+    buildCapnpId(builder, rr.id);
+    
+    switch (rr.type) {
+
+    case RRType::List:
+	VampnProto::buildRpcRequest_List(builder);
+	break;
+    case RRType::Load:
+	VampnProto::buildRpcRequest_Load(builder, rr.loadRequest);
+	break;
+    case RRType::Configure:
+	VampnProto::buildRpcRequest_Configure(builder,
+                                              rr.configurationRequest, mapper);
+	break;
+    case RRType::Process:
+	VampnProto::buildRpcRequest_Process(builder, rr.processRequest, mapper);
+	break;
+    case RRType::Finish:
+	VampnProto::buildRpcRequest_Finish(builder, rr.finishRequest, mapper);
+	break;
+    case RRType::NotValid:
+	break;
+    }
+
+    writeMessageToFd(1, message);
+}
+
+RequestOrResponse
+readResponseCapnp(kj::BufferedInputStreamWrapper &buffered)
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Response;
+
+    ::capnp::InputStreamMessageReader message(buffered);
+    RpcResponse::Reader reader = message.getRoot<RpcResponse>();
+    
+    rr.type = VampnProto::getRequestResponseType(reader);
+    rr.success = true;
+    rr.errorText = "";
+    rr.id = readCapnpId(reader);
+    int errorCode = 0;
+
+    switch (rr.type) {
+
+    case RRType::List:
+	VampnProto::readRpcResponse_List(rr.listResponse, reader);
+	break;
+    case RRType::Load:
+	VampnProto::readRpcResponse_Load(rr.loadResponse, reader, mapper);
+	break;
+    case RRType::Configure:
+	VampnProto::readRpcResponse_Configure(rr.configurationResponse,
+					       reader, mapper);
+	break;
+    case RRType::Process:
+	VampnProto::readRpcResponse_Process(rr.processResponse, reader, mapper);
+	break;
+    case RRType::Finish:
+	VampnProto::readRpcResponse_Finish(rr.finishResponse, reader, mapper);
+	break;
+    case RRType::NotValid:
+        // error
+        rr.success = false;
+        VampnProto::readRpcResponse_Error(errorCode, rr.errorText, reader);
+	break;
+    }
+
+    return rr;
+}
+
+void
+writeResponseCapnp(RequestOrResponse &rr)
+{
+    ::capnp::MallocMessageBuilder message;
+    RpcResponse::Builder builder = message.initRoot<RpcResponse>();
+
+    buildCapnpId(builder, rr.id);
+
+    if (!rr.success) {
+
+	VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type);
+
+    } else {
+	
+	switch (rr.type) {
+
+	case RRType::List:
+	    VampnProto::buildRpcResponse_List(builder, rr.listResponse);
+	    break;
+	case RRType::Load:
+	    VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper);
+	    break;
+	case RRType::Configure:
+	    VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper);
+	    break;
+	case RRType::Process:
+	    VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper);
+	    break;
+	case RRType::Finish:
+	    VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper);
+	    break;
+	case RRType::NotValid:
+	    break;
+	}
+    }
+    
+    writeMessageToFd(1, message);
+}
+
+RequestOrResponse
+readInputJson(RequestOrResponse::Direction direction, string &err)
+{
+    if (direction == RequestOrResponse::Request) {
+	return readRequestJson(err);
+    } else {
+	return readResponseJson(err);
+    }
+}
+
+RequestOrResponse
+readInputCapnp(RequestOrResponse::Direction direction)
+{
+    static kj::FdInputStream stream(0); // stdin
+    static kj::BufferedInputStreamWrapper buffered(stream);
+
+    if (buffered.tryGetReadBuffer() == nullptr) {
+	return {};
+    }
+    
+    if (direction == RequestOrResponse::Request) {
+	return readRequestCapnp(buffered);
+    } else {
+	return readResponseCapnp(buffered);
+    }
+}
+
+RequestOrResponse
+readInput(string format, RequestOrResponse::Direction direction)
+{
+    if (format == "json") {
+	string err;
+	auto result = readInputJson(direction, err);
+	if (err != "") throw runtime_error(err);
+	else return result;
+    } else if (format == "capnp") {
+	return readInputCapnp(direction);
+    } else {
+	throw runtime_error("unknown input format \"" + format + "\"");
+    }
+}
+
+void
+writeOutput(string format, RequestOrResponse &rr)
+{
+    if (format == "json") {
+	if (rr.direction == RequestOrResponse::Request) {
+	    writeRequestJson(rr, false);
+	} else {
+	    writeResponseJson(rr, false);
+	}
+    } else if (format == "json-b64") {
+	if (rr.direction == RequestOrResponse::Request) {
+	    writeRequestJson(rr, true);
+	} else {
+	    writeResponseJson(rr, true);
+	}
+    } else if (format == "capnp") {
+	if (rr.direction == RequestOrResponse::Request) {
+	    writeRequestCapnp(rr);
+	} else {
+	    writeResponseCapnp(rr);
+	}
+    } else {
+	throw runtime_error("unknown output format \"" + format + "\"");
+    }
+}
+
+int main(int argc, char **argv)
+{
+    if (argc < 2) {
+	usage();
+    }
+
+    string informat = "json", outformat = "json";
+    RequestOrResponse::Direction direction = RequestOrResponse::Request;
+    bool haveDirection = false;
+    
+    for (int i = 1; i < argc; ++i) {
+
+	string arg = argv[i];
+	bool final = (i + 1 == argc);
+	
+	if (arg == "-i") {
+	    if (final) usage();
+	    else informat = argv[++i];
+
+	} else if (arg == "-o") {
+	    if (final) usage();
+	    else outformat = argv[++i];
+
+	} else if (arg == "request") {
+	    direction = RequestOrResponse::Request;
+	    haveDirection = true;
+
+	} else if (arg == "response") {
+	    direction = RequestOrResponse::Response;
+	    haveDirection = true;
+	    
+	} else {
+	    usage();
+	}
+    }
+
+    if (informat == "" || outformat == "" || !haveDirection) {
+	usage();
+    }
+
+    while (true) {
+
+	try {
+
+	    RequestOrResponse rr = readInput(informat, direction);
+
+	    // NotValid without an exception indicates EOF:
+	    if (rr.type == RRType::NotValid) break;
+
+	    writeOutput(outformat, rr);
+	    
+	} catch (std::exception &e) {
+
+	    cerr << "Error: " << e.what() << endl;
+	    exit(1);
+	}
+    }
+
+    exit(0);
+}
+
+