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