Implement JSON API generation for async API calls

Move JSON helpers to util/rsjson.* for better usability
Implement JSON ostream manipulator to print compact and pretty JSON
Use lambdas for API wrappers, integrate better and avoid namespace
  pollution
Removed experimental JSON API for notify client wrapper, notifications
  can be implemented automatically with moderns async API calls
Implement and automatically expose to JSON API
  RsGxsChannels::turtleSearchRequest(
        const std::string& matchString,
        const std::function<void (const RsGxsGroupSummary&)>& multiCallback,
        std::time_t maxWait )
This commit is contained in:
Gioacchino Mazzurco 2018-08-16 23:34:29 +02:00
parent b7f5d4286f
commit 4b6f751b09
No known key found for this signature in database
GPG key ID: A1FBCA3872E87051
15 changed files with 444 additions and 177 deletions

View file

@ -0,0 +1,73 @@
/*
* RetroShare JSON API
* Copyright (C) 2018 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
registerHandler("$%apiPath%$",
[$%captureVars%$](const std::shared_ptr<rb::Session> session)
{
const std::multimap<std::string, std::string> headers
{
{ "Connection", "keep-alive" },
{ "Content-Type", "text/event-stream" }
};
session->yield(rb::OK, headers);
size_t reqSize = session->get_request()->get_header("Content-Length", 0);
session->fetch( reqSize, [$%captureVars%$](
const std::shared_ptr<rb::Session> session,
const rb::Bytes& body )
{
RsGenericSerializer::SerializeContext cReq(
nullptr, 0,
RsGenericSerializer::SERIALIZATION_FLAG_YIELDING );
RsJson& jReq(cReq.mJson);
jReq.Parse(reinterpret_cast<const char*>(body.data()), body.size());
RsGenericSerializer::SerializeContext cAns;
RsJson& jAns(cAns.mJson);
// if caller specified caller_data put it back in the answhere
const char kcd[] = "caller_data";
if(jReq.HasMember(kcd))
jAns.AddMember(kcd, jReq[kcd], jAns.GetAllocator());
$%paramsDeclaration%$
$%inputParamsDeserialization%$
$%callbackName%$ = [session]($%callbackParams%$)
{
$%callbackParamsSerialization%$
std::stringstream message;
message << "data: " << compactJSON << ctx.mJson << "\n\n";
session->yield(message.str());
$%sessionEarlyClose%$
};
$%functionCall%$
$%outputParamsSerialization%$
// return them to the API caller
std::stringstream message;
message << "data: " << compactJSON << cAns.mJson << "\n\n";
session->yield(message.str());
$%sessionDelayedClose%$
} );
});

View file

@ -25,12 +25,15 @@
struct MethodParam
{
MethodParam() : in(false), out(false) {}
MethodParam() :
in(false), out(false), isMultiCallback(false), isSingleCallback(false){}
QString type;
QString name;
bool in;
bool out;
bool isMultiCallback;
bool isSingleCallback;
};
int main(int argc, char *argv[])
@ -42,24 +45,18 @@ int main(int argc, char *argv[])
QString outputPath(argv[2]);
QString doxPrefix(outputPath+"/xml/");
QString wrappersDefFilePath(outputPath + "/jsonapi-wrappers.cpp");
QString wrappersDefFilePath(outputPath + "/jsonapi-wrappers.inl");
QFile wrappersDefFile(wrappersDefFilePath);
wrappersDefFile.remove();
if(!wrappersDefFile.open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text))
qFatal(QString("Can't open: " + wrappersDefFilePath).toLatin1().data());
QString wrappersDeclFilePath(outputPath + "/jsonapi-wrappers.h");
QFile wrappersDeclFile(wrappersDeclFilePath);
wrappersDeclFile.remove();
if(!wrappersDeclFile.open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text))
qFatal(QString("Can't open: " + wrappersDeclFilePath).toLatin1().data());
QString wrappersRegisterFilePath(outputPath + "/jsonapi-register.inl");
QFile wrappersRegisterFile(wrappersRegisterFilePath);
wrappersRegisterFile.remove();
if(!wrappersRegisterFile.open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text))
qFatal(QString("Can't open: " + wrappersRegisterFilePath).toLatin1().data());
QString cppApiIncludesFilePath(outputPath + "/jsonapi-includes.inl");
QFile cppApiIncludesFile(cppApiIncludesFilePath);
cppApiIncludesFile.remove();
if(!cppApiIncludesFile.open(QIODevice::WriteOnly|QIODevice::Append|QIODevice::Text))
qFatal(QString("Can't open: " + cppApiIncludesFilePath).toLatin1().data());
QSet<QString> cppApiIncludesSet;
QDirIterator it(doxPrefix, QStringList() << "*8h.xml", QDir::Files);
while(it.hasNext())
@ -151,21 +148,45 @@ int main(int argc, char *argv[])
QString retvalType = memberdef.firstChildElement("type").text();
QMap<QString,MethodParam> paramsMap;
QStringList orderedParamNames;
uint hasInput = false;
uint hasOutput = false;
bool hasInput = false;
bool hasOutput = false;
bool hasSingleCallback = false;
bool hasMultiCallback = false;
QString callbackName;
QString callbackParams;
QDomNodeList params = memberdef.elementsByTagName("param");
for (int k = 0; k < params.size(); ++k)
{
QDomElement tmpPE = params.item(k).toElement();
MethodParam tmpParam;
tmpParam.name = tmpPE.firstChildElement("declname").text();
QDomElement tmpType = tmpPE.firstChildElement("type");
QString& pName(tmpParam.name);
QString& pType(tmpParam.type);
pName = tmpPE.firstChildElement("declname").text();
QDomElement tmpType = tmpPE.firstChildElement("type");
pType = tmpType.text();
if(pType.startsWith("const ")) pType.remove(0,6);
pType.replace(QString("&"), QString());
pType.replace(QString(" "), QString());
if(pType.startsWith("std::function"))
{
if(pType.endsWith('&')) pType.chop(1);
if(pName.startsWith("multiCallback"))
{
tmpParam.isMultiCallback = true;
hasMultiCallback = true;
}
else if(pName.startsWith("callback"))
{
tmpParam.isSingleCallback = true;
hasSingleCallback = true;
}
callbackName = pName;
callbackParams = pType;
}
else
{
pType.replace(QString("&"), QString());
pType.replace(QString(" "), QString());
}
paramsMap.insert(tmpParam.name, tmpParam);
orderedParamNames.push_back(tmpParam.name);
}
@ -242,6 +263,58 @@ int main(int argc, char *argv[])
"\t\t\tRS_SERIAL_PROCESS(retval);\n";
if(hasOutput) outputParamsSerialization += "\t\t}\n";
QString captureVars;
QString sessionEarlyClose;
if(hasSingleCallback)
sessionEarlyClose = "session->close();";
QString sessionDelayedClose;
if(hasMultiCallback)
{
sessionDelayedClose = "mService.schedule( [session](){session->close();}, std::chrono::seconds(maxWait+120) );";
captureVars = "this";
}
QString callbackParamsSerialization;
if(hasSingleCallback || hasMultiCallback ||
((callbackParams.indexOf('(')+2) < callbackParams.indexOf(')')))
{
QString& cbs(callbackParamsSerialization);
callbackParams = callbackParams.split('(')[1];
callbackParams = callbackParams.split(')')[0];
cbs += "RsGenericSerializer::SerializeContext ctx;\n";
for (QString cbPar : callbackParams.split(','))
{
bool isConst(cbPar.startsWith("const "));
QChar pSep(' ');
bool isRef(cbPar.contains('&'));
if(isRef) pSep = '&';
int sepIndex = cbPar.lastIndexOf(pSep)+1;
QString cpt(cbPar.mid(0, sepIndex));
cpt.remove(0,6);
QString cpn(cbPar.mid(sepIndex));
cbs += "\t\t\tRsTypeSerializer::serial_process(";
cbs += "RsGenericSerializer::TO_JSON, ctx, ";
if(isConst)
{
cbs += "const_cast<";
cbs += cpt;
cbs += ">(";
}
cbs += cpn;
if(isConst) cbs += ")";
cbs += ", \"";
cbs += cpn;
cbs += "\" );\n";
}
}
QMap<QString,QString> substitutionsMap;
substitutionsMap.insert("paramsDeclaration", paramsDeclaration);
substitutionsMap.insert("inputParamsDeserialization", inputParamsDeserialization);
@ -249,8 +322,20 @@ int main(int argc, char *argv[])
substitutionsMap.insert("wrapperName", wrapperName);
substitutionsMap.insert("headerFileName", headerFileName);
substitutionsMap.insert("functionCall", functionCall);
substitutionsMap.insert("apiPath", apiPath);
substitutionsMap.insert("sessionEarlyClose", sessionEarlyClose);
substitutionsMap.insert("sessionDelayedClose", sessionDelayedClose);
substitutionsMap.insert("captureVars", captureVars);
substitutionsMap.insert("callbackName", callbackName);
substitutionsMap.insert("callbackParams", callbackParams);
substitutionsMap.insert("callbackParamsSerialization", callbackParamsSerialization);
QFile templFile(sourcePath + "/method-wrapper-template.cpp.tmpl");
QString templFilePath(sourcePath);
if(hasMultiCallback || hasSingleCallback)
templFilePath.append("/async-method-wrapper-template.cpp.tmpl");
else templFilePath.append("/method-wrapper-template.cpp.tmpl");
QFile templFile(templFilePath);
templFile.open(QIODevice::ReadOnly);
QString wrapperDef(templFile.readAll());
@ -261,15 +346,13 @@ int main(int argc, char *argv[])
wrappersDefFile.write(wrapperDef.toLocal8Bit());
QString wrapperDecl("void " + instanceName + methodName + "Wrapper(const std::shared_ptr<rb::Session> session);\n");
wrappersDeclFile.write(wrapperDecl.toLocal8Bit());
QString wrapperReg("registerHandler(\""+apiPath+"\", "+wrapperName+");\n");
wrappersRegisterFile.write(wrapperReg.toLocal8Bit());
cppApiIncludesSet.insert("#include \"retroshare/" + headerFileName + "\"\n");
}
}
}
for(const QString& incl : cppApiIncludesSet)
cppApiIncludesFile.write(incl.toLocal8Bit());
return 0;
}

View file

@ -16,14 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sstream>
#include <memory>
#include <restbed>
#include "retroshare/$%headerFileName%$"
namespace rb = restbed;
void $%wrapperName%$(const std::shared_ptr<rb::Session> session)
registerHandler("$%apiPath%$",
[$%captureVars%$](const std::shared_ptr<rb::Session> session)
{
size_t reqSize = session->get_request()->get_header("Content-Length", 0);
session->fetch( reqSize, [](
@ -66,5 +60,5 @@ $%outputParamsSerialization%$
};
session->close(rb::OK, ans, headers);
} );
}
});