annotate vamp-server/convert.cpp @ 116:d15cb1151d76

Add JSON support directly to the server. Had hoped to avoid this (using Capnp as canonical in the server and then converting externally as necessary) but it's just too useful for debugging purposes when bundled with client app
author Chris Cannam <c.cannam@qmul.ac.uk>
date Thu, 27 Oct 2016 11:39:41 +0100
parents 427c4c725085
children ff3fd8d1b2dc
rev   line source
c@116 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
c@75 2
c@75 3 #include "vamp-json/VampJson.h"
c@75 4 #include "vamp-capnp/VampnProto.h"
c@75 5 #include "vamp-support/RequestOrResponse.h"
c@75 6 #include "vamp-support/PreservingPluginHandleMapper.h"
c@75 7
c@75 8 #include <iostream>
c@75 9 #include <sstream>
c@75 10 #include <stdexcept>
c@75 11
c@91 12 #include <capnp/serialize.h>
c@91 13
c@75 14 using namespace std;
c@75 15 using namespace json11;
c@97 16 using namespace piper_vamp;
c@75 17
c@75 18 void usage()
c@75 19 {
c@75 20 string myname = "piper-convert";
c@75 21 cerr << "\n" << myname <<
c@116 22 ": Validate and convert Piper request and response messages\n\n"
c@116 23 " Usage: " << myname << " [-i <informat>] [-o <outformat>] request\n"
c@116 24 " " << myname << " [-i <informat>] [-o <outformat>] response\n\n"
c@116 25 " where\n"
c@116 26 " <informat>: the format to read from stdin\n"
c@116 27 " (\"json\" or \"capnp\", default is \"json\")\n"
c@116 28 " <outformat>: the format to convert to and write to stdout\n"
c@116 29 " (\"json\", \"json-b64\" or \"capnp\", default is \"json\")\n"
c@116 30 " request|response: whether messages are Vamp request or response type\n\n"
c@116 31 "If <informat> and <outformat> differ, convert from <informat> to <outformat>.\n"
c@116 32 "If <informat> and <outformat> are the same, just check validity of incoming\n"
c@116 33 "messages and pass them to output.\n\n"
c@116 34 "Specifying \"json-b64\" as output format forces base64 encoding for process and\n"
c@116 35 "feature blocks, unlike the \"json\" output format which uses text encoding.\n"
c@116 36 "The \"json\" input format accepts either.\n\n";
c@75 37
c@75 38 exit(2);
c@75 39 }
c@75 40
c@75 41 Json
c@75 42 convertRequestJson(string input, string &err)
c@75 43 {
c@75 44 Json j = Json::parse(input, err);
c@75 45 if (err != "") {
c@116 46 err = "invalid json: " + err;
c@116 47 return {};
c@75 48 }
c@75 49 if (!j.is_object()) {
c@116 50 err = "object expected at top level";
c@75 51 } else if (!j["method"].is_string()) {
c@116 52 err = "string expected for method field";
c@75 53 } else if (!j["params"].is_null() && !j["params"].is_object()) {
c@116 54 err = "object expected for params field";
c@75 55 }
c@75 56 return j;
c@75 57 }
c@75 58
c@75 59 Json
c@75 60 convertResponseJson(string input, string &err)
c@75 61 {
c@75 62 Json j = Json::parse(input, err);
c@75 63 if (err != "") {
c@116 64 err = "invalid json: " + err;
c@116 65 return {};
c@75 66 }
c@75 67 if (!j.is_object()) {
c@116 68 err = "object expected at top level";
c@75 69 } else {
c@75 70 if (!j["result"].is_object()) {
c@75 71 if (!j["error"].is_object()) {
c@75 72 err = "expected either result or error object";
c@75 73 }
c@75 74 }
c@75 75 }
c@75 76 return j;
c@75 77 }
c@75 78
c@75 79 //!!! Lots of potential for refactoring the conversion classes based
c@75 80 //!!! on the common matter in the following eight functions...
c@75 81
c@75 82 PreservingPluginHandleMapper mapper;
c@75 83
c@75 84 static RequestOrResponse::RpcId
c@75 85 readJsonId(const Json &j)
c@75 86 {
c@75 87 RequestOrResponse::RpcId id;
c@75 88
c@75 89 if (j["id"].is_number()) {
c@75 90 id.type = RequestOrResponse::RpcId::Number;
c@75 91 id.number = j["id"].number_value();
c@75 92 } else if (j["id"].is_string()) {
c@75 93 id.type = RequestOrResponse::RpcId::Tag;
c@75 94 id.tag = j["id"].string_value();
c@75 95 } else {
c@75 96 id.type = RequestOrResponse::RpcId::Absent;
c@75 97 }
c@75 98
c@75 99 return id;
c@75 100 }
c@75 101
c@75 102 static Json
c@75 103 writeJsonId(const RequestOrResponse::RpcId &id)
c@75 104 {
c@75 105 if (id.type == RequestOrResponse::RpcId::Number) {
c@75 106 return id.number;
c@75 107 } else if (id.type == RequestOrResponse::RpcId::Tag) {
c@75 108 return id.tag;
c@75 109 } else {
c@75 110 return Json();
c@75 111 }
c@75 112 }
c@75 113
c@75 114 template <typename Reader>
c@75 115 static RequestOrResponse::RpcId
c@75 116 readCapnpId(const Reader &r)
c@75 117 {
c@75 118 int number;
c@75 119 string tag;
c@75 120 switch (r.getId().which()) {
c@97 121 case piper::RpcRequest::Id::Which::NUMBER:
c@75 122 number = r.getId().getNumber();
c@75 123 return { RequestOrResponse::RpcId::Number, number, "" };
c@97 124 case piper::RpcRequest::Id::Which::TAG:
c@75 125 tag = r.getId().getTag();
c@75 126 return { RequestOrResponse::RpcId::Tag, 0, tag };
c@97 127 case piper::RpcRequest::Id::Which::NONE:
c@75 128 return { RequestOrResponse::RpcId::Absent, 0, "" };
c@75 129 }
c@75 130 return {};
c@75 131 }
c@75 132
c@75 133 template <typename Builder>
c@75 134 static void
c@75 135 buildCapnpId(Builder &b, const RequestOrResponse::RpcId &id)
c@75 136 {
c@75 137 switch (id.type) {
c@75 138 case RequestOrResponse::RpcId::Number:
c@75 139 b.getId().setNumber(id.number);
c@75 140 break;
c@75 141 case RequestOrResponse::RpcId::Tag:
c@75 142 b.getId().setTag(id.tag);
c@75 143 break;
c@75 144 case RequestOrResponse::RpcId::Absent:
c@75 145 b.getId().setNone();
c@75 146 break;
c@75 147 }
c@75 148 }
c@75 149
c@75 150 RequestOrResponse
c@75 151 readRequestJson(string &err)
c@75 152 {
c@75 153 RequestOrResponse rr;
c@75 154 rr.direction = RequestOrResponse::Request;
c@75 155
c@75 156 string input;
c@75 157 if (!getline(cin, input)) {
c@116 158 // the EOF case, not actually an error
c@116 159 rr.type = RRType::NotValid;
c@116 160 return rr;
c@75 161 }
c@75 162
c@75 163 Json j = convertRequestJson(input, err);
c@75 164 if (err != "") return {};
c@75 165
c@75 166 rr.type = VampJson::getRequestResponseType(j, err);
c@75 167 if (err != "") return {};
c@75 168
c@75 169 rr.id = readJsonId(j);
c@75 170
c@75 171 VampJson::BufferSerialisation serialisation =
c@75 172 VampJson::BufferSerialisation::Array;
c@75 173
c@75 174 switch (rr.type) {
c@75 175
c@75 176 case RRType::List:
c@116 177 VampJson::toRpcRequest_List(j, err); // type check only
c@116 178 break;
c@75 179 case RRType::Load:
c@116 180 rr.loadRequest = VampJson::toRpcRequest_Load(j, err);
c@116 181 break;
c@75 182 case RRType::Configure:
c@116 183 rr.configurationRequest = VampJson::toRpcRequest_Configure(j, mapper, err);
c@116 184 break;
c@75 185 case RRType::Process:
c@116 186 rr.processRequest = VampJson::toRpcRequest_Process(j, mapper, serialisation, err);
c@116 187 break;
c@75 188 case RRType::Finish:
c@116 189 rr.finishRequest = VampJson::toRpcRequest_Finish(j, mapper, err);
c@116 190 break;
c@75 191 case RRType::NotValid:
c@116 192 break;
c@75 193 }
c@75 194
c@75 195 return rr;
c@75 196 }
c@75 197
c@75 198 void
c@75 199 writeRequestJson(RequestOrResponse &rr, bool useBase64)
c@75 200 {
c@75 201 Json j;
c@75 202
c@75 203 VampJson::BufferSerialisation serialisation =
c@75 204 (useBase64 ?
c@75 205 VampJson::BufferSerialisation::Base64 :
c@75 206 VampJson::BufferSerialisation::Array);
c@75 207
c@75 208 Json id = writeJsonId(rr.id);
c@75 209
c@75 210 switch (rr.type) {
c@75 211
c@75 212 case RRType::List:
c@116 213 j = VampJson::fromRpcRequest_List(id);
c@116 214 break;
c@75 215 case RRType::Load:
c@116 216 j = VampJson::fromRpcRequest_Load(rr.loadRequest, id);
c@116 217 break;
c@75 218 case RRType::Configure:
c@116 219 j = VampJson::fromRpcRequest_Configure(rr.configurationRequest, mapper, id);
c@116 220 break;
c@75 221 case RRType::Process:
c@116 222 j = VampJson::fromRpcRequest_Process
c@116 223 (rr.processRequest, mapper, serialisation, id);
c@116 224 break;
c@75 225 case RRType::Finish:
c@116 226 j = VampJson::fromRpcRequest_Finish(rr.finishRequest, mapper, id);
c@116 227 break;
c@75 228 case RRType::NotValid:
c@116 229 break;
c@75 230 }
c@75 231
c@75 232 cout << j.dump() << endl;
c@75 233 }
c@75 234
c@75 235 RequestOrResponse
c@75 236 readResponseJson(string &err)
c@75 237 {
c@75 238 RequestOrResponse rr;
c@75 239 rr.direction = RequestOrResponse::Response;
c@75 240
c@75 241 string input;
c@75 242 if (!getline(cin, input)) {
c@116 243 // the EOF case, not actually an error
c@116 244 rr.type = RRType::NotValid;
c@116 245 return rr;
c@75 246 }
c@75 247
c@75 248 Json j = convertResponseJson(input, err);
c@75 249 if (err != "") return {};
c@75 250
c@75 251 rr.type = VampJson::getRequestResponseType(j, err);
c@75 252 if (err != "") return {};
c@75 253
c@75 254 rr.id = readJsonId(j);
c@75 255
c@75 256 VampJson::BufferSerialisation serialisation =
c@75 257 VampJson::BufferSerialisation::Array;
c@75 258
c@75 259 rr.success = j["success"].bool_value();
c@75 260 rr.errorText = j["errorText"].string_value();
c@75 261
c@75 262 switch (rr.type) {
c@75 263
c@75 264 case RRType::List:
c@116 265 rr.listResponse = VampJson::toRpcResponse_List(j, err);
c@116 266 break;
c@75 267 case RRType::Load:
c@116 268 rr.loadResponse = VampJson::toRpcResponse_Load(j, mapper, err);
c@116 269 break;
c@75 270 case RRType::Configure:
c@116 271 rr.configurationResponse = VampJson::toRpcResponse_Configure(j, mapper, err);
c@116 272 break;
c@75 273 case RRType::Process:
c@116 274 rr.processResponse = VampJson::toRpcResponse_Process(j, mapper, serialisation, err);
c@116 275 break;
c@75 276 case RRType::Finish:
c@116 277 rr.finishResponse = VampJson::toRpcResponse_Finish(j, mapper, serialisation, err);
c@116 278 break;
c@75 279 case RRType::NotValid:
c@116 280 break;
c@75 281 }
c@75 282
c@75 283 return rr;
c@75 284 }
c@75 285
c@75 286 void
c@75 287 writeResponseJson(RequestOrResponse &rr, bool useBase64)
c@75 288 {
c@75 289 Json j;
c@75 290
c@75 291 VampJson::BufferSerialisation serialisation =
c@75 292 (useBase64 ?
c@75 293 VampJson::BufferSerialisation::Base64 :
c@75 294 VampJson::BufferSerialisation::Array);
c@75 295
c@75 296 Json id = writeJsonId(rr.id);
c@75 297
c@75 298 if (!rr.success) {
c@75 299
c@116 300 j = VampJson::fromError(rr.errorText, rr.type, id);
c@75 301
c@75 302 } else {
c@75 303
c@116 304 switch (rr.type) {
c@75 305
c@116 306 case RRType::List:
c@116 307 j = VampJson::fromRpcResponse_List(rr.listResponse, id);
c@116 308 break;
c@116 309 case RRType::Load:
c@116 310 j = VampJson::fromRpcResponse_Load(rr.loadResponse, mapper, id);
c@116 311 break;
c@116 312 case RRType::Configure:
c@116 313 j = VampJson::fromRpcResponse_Configure(rr.configurationResponse,
c@75 314 mapper, id);
c@116 315 break;
c@116 316 case RRType::Process:
c@116 317 j = VampJson::fromRpcResponse_Process
c@116 318 (rr.processResponse, mapper, serialisation, id);
c@116 319 break;
c@116 320 case RRType::Finish:
c@116 321 j = VampJson::fromRpcResponse_Finish
c@116 322 (rr.finishResponse, mapper, serialisation, id);
c@116 323 break;
c@116 324 case RRType::NotValid:
c@116 325 break;
c@116 326 }
c@75 327 }
c@75 328
c@75 329 cout << j.dump() << endl;
c@75 330 }
c@75 331
c@75 332 RequestOrResponse
c@75 333 readRequestCapnp(kj::BufferedInputStreamWrapper &buffered)
c@75 334 {
c@75 335 RequestOrResponse rr;
c@75 336 rr.direction = RequestOrResponse::Request;
c@75 337
c@97 338 capnp::InputStreamMessageReader message(buffered);
c@97 339 piper::RpcRequest::Reader reader = message.getRoot<piper::RpcRequest>();
c@75 340
c@75 341 rr.type = VampnProto::getRequestResponseType(reader);
c@75 342 rr.id = readCapnpId(reader);
c@75 343
c@75 344 switch (rr.type) {
c@75 345
c@75 346 case RRType::List:
c@116 347 VampnProto::readRpcRequest_List(reader); // type check only
c@116 348 break;
c@75 349 case RRType::Load:
c@116 350 VampnProto::readRpcRequest_Load(rr.loadRequest, reader);
c@116 351 break;
c@75 352 case RRType::Configure:
c@116 353 VampnProto::readRpcRequest_Configure(rr.configurationRequest,
c@116 354 reader, mapper);
c@116 355 break;
c@75 356 case RRType::Process:
c@116 357 VampnProto::readRpcRequest_Process(rr.processRequest, reader, mapper);
c@116 358 break;
c@75 359 case RRType::Finish:
c@116 360 VampnProto::readRpcRequest_Finish(rr.finishRequest, reader, mapper);
c@116 361 break;
c@75 362 case RRType::NotValid:
c@116 363 break;
c@75 364 }
c@75 365
c@75 366 return rr;
c@75 367 }
c@75 368
c@75 369 void
c@75 370 writeRequestCapnp(RequestOrResponse &rr)
c@75 371 {
c@97 372 capnp::MallocMessageBuilder message;
c@97 373 piper::RpcRequest::Builder builder = message.initRoot<piper::RpcRequest>();
c@75 374
c@75 375 buildCapnpId(builder, rr.id);
c@75 376
c@75 377 switch (rr.type) {
c@75 378
c@75 379 case RRType::List:
c@116 380 VampnProto::buildRpcRequest_List(builder);
c@116 381 break;
c@75 382 case RRType::Load:
c@116 383 VampnProto::buildRpcRequest_Load(builder, rr.loadRequest);
c@116 384 break;
c@75 385 case RRType::Configure:
c@116 386 VampnProto::buildRpcRequest_Configure(builder,
c@75 387 rr.configurationRequest, mapper);
c@116 388 break;
c@75 389 case RRType::Process:
c@116 390 VampnProto::buildRpcRequest_Process(builder, rr.processRequest, mapper);
c@116 391 break;
c@75 392 case RRType::Finish:
c@116 393 VampnProto::buildRpcRequest_Finish(builder, rr.finishRequest, mapper);
c@116 394 break;
c@75 395 case RRType::NotValid:
c@116 396 break;
c@75 397 }
c@75 398
c@75 399 writeMessageToFd(1, message);
c@75 400 }
c@75 401
c@75 402 RequestOrResponse
c@75 403 readResponseCapnp(kj::BufferedInputStreamWrapper &buffered)
c@75 404 {
c@75 405 RequestOrResponse rr;
c@75 406 rr.direction = RequestOrResponse::Response;
c@75 407
c@97 408 capnp::InputStreamMessageReader message(buffered);
c@97 409 piper::RpcResponse::Reader reader = message.getRoot<piper::RpcResponse>();
c@75 410
c@75 411 rr.type = VampnProto::getRequestResponseType(reader);
c@75 412 rr.success = true;
c@75 413 rr.errorText = "";
c@75 414 rr.id = readCapnpId(reader);
c@75 415 int errorCode = 0;
c@75 416
c@75 417 switch (rr.type) {
c@75 418
c@75 419 case RRType::List:
c@116 420 VampnProto::readRpcResponse_List(rr.listResponse, reader);
c@116 421 break;
c@75 422 case RRType::Load:
c@116 423 VampnProto::readRpcResponse_Load(rr.loadResponse, reader, mapper);
c@116 424 break;
c@75 425 case RRType::Configure:
c@116 426 VampnProto::readRpcResponse_Configure(rr.configurationResponse,
c@116 427 reader, mapper);
c@116 428 break;
c@75 429 case RRType::Process:
c@116 430 VampnProto::readRpcResponse_Process(rr.processResponse, reader, mapper);
c@116 431 break;
c@75 432 case RRType::Finish:
c@116 433 VampnProto::readRpcResponse_Finish(rr.finishResponse, reader, mapper);
c@116 434 break;
c@75 435 case RRType::NotValid:
c@75 436 // error
c@75 437 rr.success = false;
c@75 438 VampnProto::readRpcResponse_Error(errorCode, rr.errorText, reader);
c@116 439 break;
c@75 440 }
c@75 441
c@75 442 return rr;
c@75 443 }
c@75 444
c@75 445 void
c@75 446 writeResponseCapnp(RequestOrResponse &rr)
c@75 447 {
c@97 448 capnp::MallocMessageBuilder message;
c@97 449 piper::RpcResponse::Builder builder = message.initRoot<piper::RpcResponse>();
c@75 450
c@75 451 buildCapnpId(builder, rr.id);
c@75 452
c@75 453 if (!rr.success) {
c@75 454
c@116 455 VampnProto::buildRpcResponse_Error(builder, rr.errorText, rr.type);
c@75 456
c@75 457 } else {
c@116 458
c@116 459 switch (rr.type) {
c@75 460
c@116 461 case RRType::List:
c@116 462 VampnProto::buildRpcResponse_List(builder, rr.listResponse);
c@116 463 break;
c@116 464 case RRType::Load:
c@116 465 VampnProto::buildRpcResponse_Load(builder, rr.loadResponse, mapper);
c@116 466 break;
c@116 467 case RRType::Configure:
c@116 468 VampnProto::buildRpcResponse_Configure(builder, rr.configurationResponse, mapper);
c@116 469 break;
c@116 470 case RRType::Process:
c@116 471 VampnProto::buildRpcResponse_Process(builder, rr.processResponse, mapper);
c@116 472 break;
c@116 473 case RRType::Finish:
c@116 474 VampnProto::buildRpcResponse_Finish(builder, rr.finishResponse, mapper);
c@116 475 break;
c@116 476 case RRType::NotValid:
c@116 477 break;
c@116 478 }
c@75 479 }
c@75 480
c@75 481 writeMessageToFd(1, message);
c@75 482 }
c@75 483
c@75 484 RequestOrResponse
c@75 485 readInputJson(RequestOrResponse::Direction direction, string &err)
c@75 486 {
c@75 487 if (direction == RequestOrResponse::Request) {
c@116 488 return readRequestJson(err);
c@75 489 } else {
c@116 490 return readResponseJson(err);
c@75 491 }
c@75 492 }
c@75 493
c@75 494 RequestOrResponse
c@75 495 readInputCapnp(RequestOrResponse::Direction direction)
c@75 496 {
c@75 497 static kj::FdInputStream stream(0); // stdin
c@75 498 static kj::BufferedInputStreamWrapper buffered(stream);
c@75 499
c@75 500 if (buffered.tryGetReadBuffer() == nullptr) {
c@116 501 return {};
c@75 502 }
c@75 503
c@75 504 if (direction == RequestOrResponse::Request) {
c@116 505 return readRequestCapnp(buffered);
c@75 506 } else {
c@116 507 return readResponseCapnp(buffered);
c@75 508 }
c@75 509 }
c@75 510
c@75 511 RequestOrResponse
c@75 512 readInput(string format, RequestOrResponse::Direction direction)
c@75 513 {
c@75 514 if (format == "json") {
c@116 515 string err;
c@116 516 auto result = readInputJson(direction, err);
c@116 517 if (err != "") throw runtime_error(err);
c@116 518 else return result;
c@75 519 } else if (format == "capnp") {
c@116 520 return readInputCapnp(direction);
c@75 521 } else {
c@116 522 throw runtime_error("unknown input format \"" + format + "\"");
c@75 523 }
c@75 524 }
c@75 525
c@75 526 void
c@75 527 writeOutput(string format, RequestOrResponse &rr)
c@75 528 {
c@75 529 if (format == "json") {
c@116 530 if (rr.direction == RequestOrResponse::Request) {
c@116 531 writeRequestJson(rr, false);
c@116 532 } else {
c@116 533 writeResponseJson(rr, false);
c@116 534 }
c@75 535 } else if (format == "json-b64") {
c@116 536 if (rr.direction == RequestOrResponse::Request) {
c@116 537 writeRequestJson(rr, true);
c@116 538 } else {
c@116 539 writeResponseJson(rr, true);
c@116 540 }
c@75 541 } else if (format == "capnp") {
c@116 542 if (rr.direction == RequestOrResponse::Request) {
c@116 543 writeRequestCapnp(rr);
c@116 544 } else {
c@116 545 writeResponseCapnp(rr);
c@116 546 }
c@75 547 } else {
c@116 548 throw runtime_error("unknown output format \"" + format + "\"");
c@75 549 }
c@75 550 }
c@75 551
c@75 552 int main(int argc, char **argv)
c@75 553 {
c@75 554 if (argc < 2) {
c@116 555 usage();
c@75 556 }
c@75 557
c@75 558 string informat = "json", outformat = "json";
c@75 559 RequestOrResponse::Direction direction = RequestOrResponse::Request;
c@75 560 bool haveDirection = false;
c@75 561
c@75 562 for (int i = 1; i < argc; ++i) {
c@75 563
c@116 564 string arg = argv[i];
c@116 565 bool final = (i + 1 == argc);
c@116 566
c@116 567 if (arg == "-i") {
c@116 568 if (final) usage();
c@116 569 else informat = argv[++i];
c@75 570
c@116 571 } else if (arg == "-o") {
c@116 572 if (final) usage();
c@116 573 else outformat = argv[++i];
c@75 574
c@116 575 } else if (arg == "request") {
c@116 576 direction = RequestOrResponse::Request;
c@116 577 haveDirection = true;
c@75 578
c@116 579 } else if (arg == "response") {
c@116 580 direction = RequestOrResponse::Response;
c@116 581 haveDirection = true;
c@116 582
c@116 583 } else {
c@116 584 usage();
c@116 585 }
c@75 586 }
c@75 587
c@75 588 if (informat == "" || outformat == "" || !haveDirection) {
c@116 589 usage();
c@75 590 }
c@75 591
c@75 592 while (true) {
c@75 593
c@116 594 try {
c@75 595
c@116 596 RequestOrResponse rr = readInput(informat, direction);
c@75 597
c@116 598 // NotValid without an exception indicates EOF:
c@116 599 if (rr.type == RRType::NotValid) break;
c@75 600
c@116 601 writeOutput(outformat, rr);
c@116 602
c@116 603 } catch (std::exception &e) {
c@75 604
c@116 605 cerr << "Error: " << e.what() << endl;
c@116 606 exit(1);
c@116 607 }
c@75 608 }
c@75 609
c@75 610 exit(0);
c@75 611 }
c@75 612
c@75 613