annotate vamp-server/convert.cpp @ 117:5dffc5147176

Small simplification
author Chris Cannam <c.cannam@qmul.ac.uk>
date Thu, 27 Oct 2016 11:40:57 +0100
parents d15cb1151d76
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