annotate vamp-server/convert.cpp @ 91:c897c9a8daf1

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