changeset 24:533ca5ca3404

More on reading and writing json messages
author Chris Cannam <c.cannam@qmul.ac.uk>
date Mon, 23 May 2016 16:09:25 +0100
parents d678cd00e593
children 5b9690d18241
files capnproto/VampnProto.h capnproto/vamp.capnp json/VampJson.h utilities/json-cli.cpp utilities/vampipe-convert.cpp
diffstat 5 files changed, 334 insertions(+), 156 deletions(-) [+]
line wrap: on
line diff
--- a/capnproto/VampnProto.h	Fri May 20 18:05:02 2016 +0100
+++ b/capnproto/VampnProto.h	Mon May 23 16:09:25 2016 +0100
@@ -658,10 +658,11 @@
                            const std::vector<Vamp::HostExt::PluginStaticData> &d) {
         b.setSuccess(errorText == "");
         b.setErrorText(errorText);
-        auto r = b.getResponse().initList(d.size());
+        auto r = b.getResponse().initList();
+        auto p = r.initPlugins(d.size());
         for (size_t i = 0; i < d.size(); ++i) {
-            auto rd = r[i];
-            buildPluginStaticData(rd, d[i]);
+            auto pd = p[i];
+            buildPluginStaticData(pd, d[i]);
         }
     }
     
--- a/capnproto/vamp.capnp	Fri May 20 18:05:02 2016 +0100
+++ b/capnproto/vamp.capnp	Mon May 23 16:09:25 2016 +0100
@@ -114,6 +114,10 @@
     blockSize          @4  :Int32;
 }
 
+struct ListResponse {
+    plugins            @0  :List(PluginStaticData);    
+}
+
 struct LoadRequest {
     pluginKey          @0  :Text;
     inputSampleRate    @1  :Float32;
@@ -162,7 +166,7 @@
     success            @0  :Bool;
     errorText          @1  :Text = "";
     response :union {
-	list           @2  :List(PluginStaticData);
+	list           @2  :ListResponse;
 	load           @3  :LoadResponse;
 	configure      @4  :ConfigurationResponse;
 	process        @5  :ProcessResponse;
--- a/json/VampJson.h	Fri May 20 18:05:02 2016 +0100
+++ b/json/VampJson.h	Mon May 23 16:09:25 2016 +0100
@@ -870,6 +870,7 @@
                           const std::vector<Vamp::HostExt::PluginStaticData> &d) {
 
         json11::Json::object jo;
+        jo["type"] = "list";
         jo["success"] = (errorText == "");
         jo["errorText"] = errorText;
 
@@ -877,7 +878,10 @@
         for (const auto &a: d) {
             arr.push_back(fromPluginStaticData(a));
         }
-        jo["response"] = arr;
+        json11::Json::object po;
+        po["plugins"] = arr;
+
+        jo["content"] = po;
         return json11::Json(jo);
     }
     
@@ -895,9 +899,10 @@
                           PluginHandleMapper &mapper) {
 
         json11::Json::object jo;
+        jo["type"] = "load";
         jo["success"] = (resp.plugin != 0);
         jo["errorText"] = "";
-        jo["response"] = fromLoadResponse(resp, mapper);
+        jo["content"] = fromLoadResponse(resp, mapper);
         return json11::Json(jo);
     }
 
@@ -915,9 +920,10 @@
     fromVampResponse_Configure(const Vamp::HostExt::ConfigurationResponse &resp) {
         
         json11::Json::object jo;
+        jo["type"] = "configure";
         jo["success"] = (!resp.outputs.empty());
         jo["errorText"] = "";
-        jo["response"] = fromConfigurationResponse(resp);
+        jo["content"] = fromConfigurationResponse(resp);
         return json11::Json(jo);
     }
     
@@ -935,17 +941,22 @@
     fromVampResponse_Process(const Vamp::HostExt::ProcessResponse &resp) {
         
         json11::Json::object jo;
+        jo["type"] = "process";
         jo["success"] = true;
         jo["errorText"] = "";
-        jo["response"] = fromFeatureSet(resp.features);
+        jo["content"] = fromFeatureSet(resp.features);
         return json11::Json(jo);
     }
     
     static json11::Json
-    fromVampRequest_Finish() {
+    fromVampRequest_Finish(Vamp::Plugin *p,
+                           PluginHandleMapper &mapper) {
 
         json11::Json::object jo;
         jo["type"] = "finish";
+        json11::Json::object fo;
+        fo["pluginHandle"] = mapper.pluginToHandle(p);
+        jo["content"] = fo;
         return json11::Json(jo);
     }    
     
@@ -953,11 +964,119 @@
     fromVampResponse_Finish(const Vamp::HostExt::ProcessResponse &resp) {
 
         json11::Json::object jo;
+        jo["type"] = "finish";
         jo["success"] = true;
         jo["errorText"] = "";
-        jo["response"] = fromFeatureSet(resp.features);
+        jo["content"] = fromFeatureSet(resp.features);
         return json11::Json(jo);
     }
+
+private: // go private briefly for a couple of helper functions
+    
+    static void
+    checkTypeField(json11::Json j, std::string expected) {
+        if (!j["type"].is_string()) {
+            throw Failure("string expected for type");
+        }
+        if (j["type"].string_value() != expected) {
+            throw Failure("expected value \"" + expected + "\" for type");
+        }
+    }
+
+    static bool
+    successful(json11::Json j) {
+        if (!j["success"].is_bool()) {
+            throw Failure("bool expected for success");
+        }
+        return j["success"].bool_value();
+    }
+
+public:
+    static void
+    toVampRequest_List(json11::Json j) {
+        
+        checkTypeField(j, "list");
+    }
+
+    static std::vector<Vamp::HostExt::PluginStaticData>
+    toVampResponse_List(json11::Json j) {
+
+        std::vector<Vamp::HostExt::PluginStaticData> arr;
+        if (successful(j)) {
+            for (const auto &a: j["content"]["plugins"].array_items()) {
+                arr.push_back(toPluginStaticData(a));
+            }
+        }
+        return arr;
+    }
+
+    static Vamp::HostExt::LoadRequest
+    toVampRequest_Load(json11::Json j) {
+        
+        checkTypeField(j, "load");
+        return toLoadRequest(j["content"]);
+    }
+    
+    static Vamp::HostExt::LoadResponse
+    toVampResponse_Load(json11::Json j, PluginHandleMapper &mapper) {
+        
+        Vamp::HostExt::LoadResponse resp;
+        if (successful(j)) {
+            resp = toLoadResponse(j["content"], mapper);
+        }
+        return resp;
+    }
+    
+    static Vamp::HostExt::ConfigurationRequest
+    toVampRequest_Configure(json11::Json j, PluginHandleMapper &mapper) {
+        
+        checkTypeField(j, "configure");
+        return toConfigurationRequest(j["content"], mapper);
+    }
+    
+    static Vamp::HostExt::ConfigurationResponse
+    toVampResponse_Configure(json11::Json j) {
+        
+        Vamp::HostExt::ConfigurationResponse resp;
+        if (successful(j)) {
+            resp = toConfigurationResponse(j["content"]);
+        }
+        return resp;
+    }
+    
+    static Vamp::HostExt::ProcessRequest
+    toVampRequest_Process(json11::Json j, PluginHandleMapper &mapper) {
+        
+        checkTypeField(j, "process");
+        return toProcessRequest(j["content"], mapper);
+    }
+    
+    static Vamp::HostExt::ProcessResponse
+    toVampResponse_Process(json11::Json j) {
+        
+        Vamp::HostExt::ProcessResponse resp;
+        if (successful(j)) {
+            resp.features = toFeatureSet(j["content"]);
+        }
+        return resp;
+    }
+    
+    static Vamp::Plugin *
+    toVampRequest_Finish(json11::Json j, PluginHandleMapper &mapper) {
+        
+        checkTypeField(j, "finish");
+        return mapper.handleToPlugin(j["content"]["pluginHandle"].int_value());
+    }
+    
+    static Vamp::HostExt::ProcessResponse
+    toVampResponse_Finish(json11::Json j) {
+        
+        Vamp::HostExt::ProcessResponse resp;
+        if (successful(j)) {
+            resp.features = toFeatureSet(j["content"]);
+        }
+        return resp;
+    }
 };
 
 }
--- a/utilities/json-cli.cpp	Fri May 20 18:05:02 2016 +0100
+++ b/utilities/json-cli.cpp	Mon May 23 16:09:25 2016 +0100
@@ -160,8 +160,8 @@
 	throw VampJson::Failure("invalid request: " + err);
     }
 
-    if (!j["verb"].is_string()) {
-	throw VampJson::Failure("verb expected in request");
+    if (!j["type"].is_string()) {
+	throw VampJson::Failure("type expected in request");
     }
 
     if (!j["content"].is_null() &&
@@ -169,7 +169,7 @@
 	throw VampJson::Failure("object expected for content");
     }
 
-    string verb = j["verb"].string_value();
+    string verb = j["type"].string_value();
     Json content = j["content"];
     Json result;
 
--- a/utilities/vampipe-convert.cpp	Fri May 20 18:05:02 2016 +0100
+++ b/utilities/vampipe-convert.cpp	Mon May 23 16:09:25 2016 +0100
@@ -10,31 +10,11 @@
 using namespace json11;
 using namespace vampipe;
 
-// Accepting JSON objects with two fields, "type" and "payload". The
+// Accepting JSON objects with two fields, "type" and "content". The
 // "type" string corresponds to the JSON schema filename
-// (e.g. "outputdescriptor") and the "payload" is the JSON object
+// (e.g. "outputdescriptor") and the "content" is the JSON object
 // encoded with that schema.
 
-Json
-json_input(string input)
-{
-    string err;
-    Json j = Json::parse(input, err);
-    if (err != "") {
-	throw VampJson::Failure("invalid json: " + err);
-    }
-    if (!j.is_object()) {
-	throw VampJson::Failure("object expected at top level");
-    }
-    if (!j["type"].is_string()) {
-	throw VampJson::Failure("string expected for type field");
-    }
-    if (!j["payload"].is_object()) {
-	throw VampJson::Failure("object expected for payload field");
-    }
-    return j;
-}
-
 class PreservingPluginHandleMapper : public PluginHandleMapper
 {
 public:
@@ -56,112 +36,63 @@
     Vamp::Plugin *m_plugin;
 };
 
-void 
-handle_input(::capnp::MallocMessageBuilder &message, string input)
-{
-    string err;
-
-    Json j = json_input(input);
-    string type = j["type"].string_value();
-    Json payload = j["payload"];
-
-    if (type == "configurationrequest") {
-	auto req = message.initRoot<ConfigurationRequest>();
-	PreservingPluginHandleMapper mapper;
-	VampnProto::buildConfigurationRequest
-	    (req, VampJson::toConfigurationRequest(payload, mapper), mapper);
-
-    } else if (type == "configurationresponse") {
-	auto resp = message.initRoot<ConfigurationResponse>();
-	VampnProto::buildConfigurationResponse
-	    (resp, VampJson::toConfigurationResponse(payload));
-
-    } else if (type == "feature") {
-	auto f = message.initRoot<Feature>();
-	VampnProto::buildFeature
-	    (f, VampJson::toFeature(payload));
-
-    } else if (type == "featureset") {
-	auto fs = message.initRoot<FeatureSet>();
-	VampnProto::buildFeatureSet
-	    (fs, VampJson::toFeatureSet(payload));
-
-    } else if (type == "loadrequest") {
-	auto req = message.initRoot<LoadRequest>();
-	VampnProto::buildLoadRequest
-	    (req, VampJson::toLoadRequest(payload));
-	
-    } else if (type == "loadresponse") {
-	auto resp = message.initRoot<LoadResponse>();
-	PreservingPluginHandleMapper mapper;
-	VampnProto::buildLoadResponse
-	    (resp, VampJson::toLoadResponse(payload, mapper), mapper);
-
-    } else if (type == "outputdescriptor") {
-	auto od = message.initRoot<OutputDescriptor>();
-	VampnProto::buildOutputDescriptor
-	    (od, VampJson::toOutputDescriptor(payload));
-
-    } else if (type == "parameterdescriptor") {
-	auto pd = message.initRoot<ParameterDescriptor>();
-	VampnProto::buildParameterDescriptor
-	    (pd, VampJson::toParameterDescriptor(payload));
-
-    } else if (type == "pluginconfiguration") {
-	auto pc = message.initRoot<PluginConfiguration>();
-	auto config = VampJson::toPluginConfiguration(payload);
-	VampnProto::buildPluginConfiguration(pc, config);
-
-    } else if (type == "pluginstaticdata") {
-	auto pc = message.initRoot<PluginStaticData>();
-	auto sd = VampJson::toPluginStaticData(payload);
- 	VampnProto::buildPluginStaticData(pc, sd);
-
-    } else if (type == "processrequest") {
-	auto p = message.initRoot<ProcessRequest>();
-	PreservingPluginHandleMapper mapper;
-	VampnProto::buildProcessRequest
-	    (p, VampJson::toProcessRequest(payload, mapper), mapper);
-
-    } else if (type == "realtime") {
-	auto b = message.initRoot<RealTime>();
-	VampnProto::buildRealTime
-	    (b, VampJson::toRealTime(payload));
-	
-    } else {
-	throw VampJson::Failure("unknown or unsupported JSON schema type " +
-				type);
-    }
-}
-
 void usage()
 {
     string myname = "vampipe-convert";
     cerr << "\n" << myname <<
-	": Convert Vamp request and response messages between formats\n\n"
-	"    Usage:  " << myname << " -i <informat> -o <outformat>\n\n"
-	"Where <informat> and <outformat> may be \"json\" or \"capnp\".\n"
-	"Messages are read from stdin and written to stdout.\n" << endl;
+	": Validate and convert Vamp 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\" or \"capnp\", default is \"json\")\n"
+	"       request|response: whether to expect Vamp request or response messages\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";
+
     exit(2);
 }
 
 class RequestOrResponse
 {
 public:
+    enum Direction {
+	Request, Response
+    };
     enum Type {
 	List, Load, Configure, Process, Finish, Eof
     };
+
+    static Type typeForName(string name) {
+	if (name == "list") return List;
+	else if (name == "load") return Load;
+	else if (name == "configure") return Configure;
+	else if (name == "process") return Process;
+	else if (name == "finish") return Finish;
+	else if (name == "eof") return Eof;
+	else {
+	    throw runtime_error("unknown or unexpected request/response type \"" +
+				name + "\"");
+	}
+    }
+    
     RequestOrResponse() : // nothing by default
+	direction(Request),
 	type(Eof),
 	success(false),
 	finishPlugin(0) { }
 
+    Direction direction;
     Type type;
     bool success;
     string errorText;
 
     PreservingPluginHandleMapper mapper;
-    
+
+    vector<Vamp::HostExt::PluginStaticData> listResponse;
     Vamp::HostExt::LoadRequest loadRequest;
     Vamp::HostExt::LoadResponse loadResponse;
     Vamp::HostExt::ConfigurationRequest configurationRequest;
@@ -170,96 +101,219 @@
     Vamp::HostExt::ProcessResponse processResponse;
     Vamp::Plugin *finishPlugin;
     Vamp::HostExt::ProcessResponse finishResponse;
-    
 };
 
+Json
+convertRequestJson(string input)
+{
+    string err;
+    Json j = Json::parse(input, err);
+    if (err != "") {
+	throw VampJson::Failure("invalid json: " + err);
+    }
+    if (!j.is_object()) {
+	throw VampJson::Failure("object expected at top level");
+    }
+    if (!j["type"].is_string()) {
+	throw VampJson::Failure("string expected for type field");
+    }
+    if (!j["content"].is_object()) {
+	throw VampJson::Failure("object expected for content field");
+    }
+    return j;
+}
+
+Json
+convertResponseJson(string input)
+{
+    string err;
+    Json j = Json::parse(input, err);
+    if (err != "") {
+	throw VampJson::Failure("invalid json: " + err);
+    }
+    if (!j.is_object()) {
+	throw VampJson::Failure("object expected at top level");
+    }
+    if (!j["success"].is_bool()) {
+	throw VampJson::Failure("bool expected for success field");
+    }
+    if (!j["content"].is_object()) {
+	throw VampJson::Failure("object expected for content field");
+    }
+    return j;
+}
+
 RequestOrResponse
-readInputJson()
+readRequestJson()
 {
     RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Request;
+
+    string input;
+    if (!getline(cin, input)) {
+	rr.type = RequestOrResponse::Eof;
+	return rr;
+    }
+    
+    Json j = convertRequestJson(input);
+    string type = j["type"].string_value();
+    rr.type = RequestOrResponse::typeForName(type);
+
+    if (rr.type == RequestOrResponse::Load) {
+	rr.loadRequest = VampJson::toVampRequest_Load(j);
+
+    } else if (rr.type == RequestOrResponse::Configure) {
+	rr.configurationRequest = VampJson::toVampRequest_Configure(j, rr.mapper);
+
+    } else if (rr.type == RequestOrResponse::Process) {
+	rr.processRequest = VampJson::toVampRequest_Process(j, rr.mapper);
+
+    } else if (rr.type == RequestOrResponse::Finish) {
+	rr.finishPlugin = VampJson::toVampRequest_Finish(j, rr.mapper);
+    }
+
+    return rr;
+}
+
+void
+writeRequestJson(RequestOrResponse &rr)
+{
+    Json j;
+
+    if (rr.type == RequestOrResponse::List) {
+	j = VampJson::fromVampRequest_List();
+	
+    } else if (rr.type == RequestOrResponse::Load) {
+	j = VampJson::fromVampRequest_Load(rr.loadRequest);
+	
+    } else if (rr.type == RequestOrResponse::Configure) {
+	j = VampJson::fromVampRequest_Configure(rr.configurationRequest, rr.mapper);
+	
+    } else if (rr.type == RequestOrResponse::Process) {
+	j = VampJson::fromVampRequest_Process(rr.processRequest, rr.mapper);
+	
+    } else if (rr.type == RequestOrResponse::Finish) {
+	j = VampJson::fromVampRequest_Finish(rr.finishPlugin, rr.mapper);
+    }
+
+    cout << j.dump() << endl;
+}
+
+RequestOrResponse
+readResponseJson()
+{
+    RequestOrResponse rr;
+    rr.direction = RequestOrResponse::Response;
+
     string input;
     if (!getline(cin, input)) {
 	rr.type = RequestOrResponse::Eof;
 	return rr;
     }
 
-    Json j = json_input(input);
+    Json j = convertResponseJson(input);
     string type = j["type"].string_value();
+    rr.type = RequestOrResponse::typeForName(type);
 
-    if (type == "list") {
-	rr.type = RequestOrResponse::List;
+    if (rr.type == RequestOrResponse::List) {
+	rr.listResponse = VampJson::toVampResponse_List(j);
+	
+    } else if (rr.type == RequestOrResponse::Load) {
+	rr.loadResponse = VampJson::toVampResponse_Load(j, rr.mapper);
 
-    } else if (type == "load") {
-	//!!! ah, we need a way to know whether we're dealing with a request or response here
-	rr.type = RequestOrResponse::Load;
-	rr.loadRequest = VampJson::toLoadRequest(j["content"]);
+    } else if (rr.type == RequestOrResponse::Configure) {
+	rr.configurationResponse = VampJson::toVampResponse_Configure(j);
 
-    } else if (type == "configure") {
-	rr.type = RequestOrResponse::Configure;
-	rr.configurationRequest =
-	    VampJson::toConfigurationRequest(j["content"], rr.mapper);
+    } else if (rr.type == RequestOrResponse::Process) {
+	rr.processResponse = VampJson::toVampResponse_Process(j);
 
-    } else if (type == "process") {
-	rr.type = RequestOrResponse::Process;
-	rr.processRequest =
-	    VampJson::toProcessRequest(j["content"], rr.mapper);
-
-    } else if (type == "finish") {
-	rr.type = RequestOrResponse::Finish;
-	//!!! VampJsonify
-	rr.finishPlugin = rr.mapper.handleToPlugin(j["content"]["pluginHandle"].int_value());
-
-    } else {
-	throw runtime_error("unknown or unexpected request/response type \"" +
-			    type + "\"");
+    } else if (rr.type == RequestOrResponse::Finish) {
+	rr.finishResponse = VampJson::toVampResponse_Finish(j);
     }
 
     return rr;
 }
 
+void
+writeResponseJson(RequestOrResponse &rr)
+{
+    Json j;
+
+    if (rr.type == RequestOrResponse::List) {
+	j = VampJson::fromVampResponse_List("", rr.listResponse);
+	
+    } else if (rr.type == RequestOrResponse::Load) {
+	j = VampJson::fromVampResponse_Load(rr.loadResponse, rr.mapper);
+    }
+
+    cout << j.dump() << endl;
+}
+
 RequestOrResponse
-readInput(string format)
+readInput(string format, RequestOrResponse::Direction direction)
 {
     if (format == "json") {
-	return readInputJson();
+	if (direction == RequestOrResponse::Request) {
+	    return readRequestJson();
+	} else {
+	    return readResponseJson();
+	}
     } else {
 	throw runtime_error("unknown or unimplemented format \"" + format + "\"");
     }
 }
 
 void
-writeOutput(string format, const RequestOrResponse &rr)
+writeOutput(string format, RequestOrResponse &rr)
 {
-    throw runtime_error("writeOutput not implemented yet");
-    
+    if (format == "json") {
+	if (rr.direction == RequestOrResponse::Request) {
+	    writeRequestJson(rr);
+	} else {
+	    writeResponseJson(rr);
+	}
+    } else {
+	throw runtime_error("unknown or unimplemented format \"" + format + "\"");
+    }
 }
 
 int main(int argc, char **argv)
 {
-    if (argc != 5) {
+    if (argc < 2) {
 	usage();
     }
 
-    string informat, outformat;
+    string informat = "json", outformat = "json";
+    RequestOrResponse::Direction direction;
+    bool haveDirection = false;
     
-    for (int i = 1; i + 1 < argc; ++i) {
+    for (int i = 1; i < argc; ++i) {
 
 	string arg = argv[i];
+	bool final = (i + 1 == argc);
 	
 	if (arg == "-i") {
-	    if (informat != "") usage();
+	    if (informat != "" || final) usage();
 	    else informat = argv[++i];
 
 	} else if (arg == "-o") {
-	    if (outformat != "") usage();
+	    if (outformat != "" || 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 == "") {
+    if (informat == "" || outformat == "" || !haveDirection) {
 	usage();
     }
 
@@ -267,7 +321,7 @@
 
 	try {
 
-	    RequestOrResponse rr = readInput(informat);
+	    RequestOrResponse rr = readInput(informat, direction);
 	    if (rr.type == RequestOrResponse::Eof) break;
 	    writeOutput(outformat, rr);