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