view utilities/vampipe-convert.cpp @ 67:db17657ac875

Validation fixes
author Chris Cannam <c.cannam@qmul.ac.uk>
date Wed, 05 Oct 2016 13:53:08 +0100
parents 6f160dee1192
children a5ba837bca28
line wrap: on
line source

#include "VampJson.h"
#include "VampnProto.h"

#include "bits/RequestOrResponse.h"
#include "bits/PreservingPluginHandleMapper.h"

#include <iostream>
#include <sstream>
#include <stdexcept>

using namespace std;
using namespace json11;
using namespace vampipe;

void usage()
{
    string myname = "vampipe-convert";
    cerr << "\n" << myname <<
	": 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\", \"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;

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 {};
    
    VampJson::BufferSerialisation serialisation =
        VampJson::BufferSerialisation::Array;

    switch (rr.type) {

    case RRType::List:
	VampJson::toVampRequest_List(j, err); // type check only
	break;
    case RRType::Load:
	rr.loadRequest = VampJson::toVampRequest_Load(j, err);
	break;
    case RRType::Configure:
	rr.configurationRequest = VampJson::toVampRequest_Configure(j, mapper, err);
	break;
    case RRType::Process:
	rr.processRequest = VampJson::toVampRequest_Process(j, mapper, serialisation, err);
	break;
    case RRType::Finish:
	rr.finishRequest = VampJson::toVampRequest_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);

    switch (rr.type) {

    case RRType::List:
	j = VampJson::fromVampRequest_List();
	break;
    case RRType::Load:
	j = VampJson::fromVampRequest_Load(rr.loadRequest);
	break;
    case RRType::Configure:
	j = VampJson::fromVampRequest_Configure(rr.configurationRequest, mapper);
	break;
    case RRType::Process:
	j = VampJson::fromVampRequest_Process
	    (rr.processRequest, mapper, serialisation);
	break;
    case RRType::Finish:
	j = VampJson::fromVampRequest_Finish(rr.finishRequest, mapper);
	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 {};
    
    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::toVampResponse_List(j, err);
	break;
    case RRType::Load:
	rr.loadResponse = VampJson::toVampResponse_Load(j, mapper, err);
	break;
    case RRType::Configure:
	rr.configurationResponse = VampJson::toVampResponse_Configure(j, mapper, err);
	break;
    case RRType::Process: 
	rr.processResponse = VampJson::toVampResponse_Process(j, mapper, serialisation, err);
	break;
    case RRType::Finish:
	rr.finishResponse = VampJson::toVampResponse_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);

    if (!rr.success) {

	j = VampJson::fromError(rr.errorText, rr.type);

    } else {
    
	switch (rr.type) {

	case RRType::List:
	    j = VampJson::fromVampResponse_List(rr.listResponse);
	    break;
	case RRType::Load:
	    j = VampJson::fromVampResponse_Load(rr.loadResponse, mapper);
	    break;
	case RRType::Configure:
	    j = VampJson::fromVampResponse_Configure(rr.configurationResponse,
                                                     mapper);
	    break;
	case RRType::Process:
	    j = VampJson::fromVampResponse_Process
		(rr.processResponse, mapper, serialisation);
	    break;
	case RRType::Finish:
	    j = VampJson::fromVampResponse_Finish
		(rr.finishResponse, mapper, serialisation);
	    break;
	case RRType::NotValid:
	    break;
	}
    }
    
    cout << j.dump() << endl;
}

RequestOrResponse
readRequestCapnp(kj::BufferedInputStreamWrapper &buffered)
{
    RequestOrResponse rr;
    rr.direction = RequestOrResponse::Request;

    ::capnp::InputStreamMessageReader message(buffered);
    VampRequest::Reader reader = message.getRoot<VampRequest>();
    
    rr.type = VampnProto::getRequestResponseType(reader);

    switch (rr.type) {

    case RRType::List:
	VampnProto::readVampRequest_List(reader); // type check only
	break;
    case RRType::Load:
	VampnProto::readVampRequest_Load(rr.loadRequest, reader);
	break;
    case RRType::Configure:
	VampnProto::readVampRequest_Configure(rr.configurationRequest,
					      reader, mapper);
	break;
    case RRType::Process:
	VampnProto::readVampRequest_Process(rr.processRequest, reader, mapper);
	break;
    case RRType::Finish:
	VampnProto::readVampRequest_Finish(rr.finishRequest, reader, mapper);
	break;
    case RRType::NotValid:
	break;
    }

    return rr;
}

void
writeRequestCapnp(RequestOrResponse &rr)
{
    ::capnp::MallocMessageBuilder message;
    VampRequest::Builder builder = message.initRoot<VampRequest>();

    switch (rr.type) {

    case RRType::List:
	VampnProto::buildVampRequest_List(builder);
	break;
    case RRType::Load:
	VampnProto::buildVampRequest_Load(builder, rr.loadRequest);
	break;
    case RRType::Configure:
	VampnProto::buildVampRequest_Configure(builder,
					       rr.configurationRequest, mapper);
	break;
    case RRType::Process:
	VampnProto::buildVampRequest_Process(builder, rr.processRequest, mapper);
	break;
    case RRType::Finish:
	VampnProto::buildVampRequest_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);
    VampResponse::Reader reader = message.getRoot<VampResponse>();
    
    rr.type = VampnProto::getRequestResponseType(reader);
    rr.success = reader.getSuccess();
    rr.errorText = reader.getErrorText();

    switch (rr.type) {

    case RRType::List:
	VampnProto::readVampResponse_List(rr.listResponse, reader);
	break;
    case RRType::Load:
	VampnProto::readVampResponse_Load(rr.loadResponse, reader, mapper);
	break;
    case RRType::Configure:
	VampnProto::readVampResponse_Configure(rr.configurationResponse,
					       reader, mapper);
	break;
    case RRType::Process:
	VampnProto::readVampResponse_Process(rr.processResponse, reader, mapper);
	break;
    case RRType::Finish:
	VampnProto::readVampResponse_Finish(rr.finishResponse, reader, mapper);
	break;
    case RRType::NotValid:
	break;
    }

    return rr;
}

void
writeResponseCapnp(RequestOrResponse &rr)
{
    ::capnp::MallocMessageBuilder message;
    VampResponse::Builder builder = message.initRoot<VampResponse>();

    if (!rr.success) {

	VampnProto::buildVampResponse_Error(builder, rr.errorText, rr.type);

    } else {
	
	switch (rr.type) {

	case RRType::List:
	    VampnProto::buildVampResponse_List(builder, rr.listResponse);
	    break;
	case RRType::Load:
	    VampnProto::buildVampResponse_Load(builder, rr.loadResponse, mapper);
	    break;
	case RRType::Configure:
	    VampnProto::buildVampResponse_Configure(builder, rr.configurationResponse, mapper);
	    break;
	case RRType::Process:
	    VampnProto::buildVampResponse_Process(builder, rr.processResponse, mapper);
	    break;
	case RRType::Finish:
	    VampnProto::buildVampResponse_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);
}