/* * RetroShare JSON API * Copyright (C) 2018 Gioacchino Mazzurco * * 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 . */ #include #include #include #include #include #include #include #include struct MethodParam { MethodParam() : in(false), out(false), isMultiCallback(false), isSingleCallback(false){} QString type; QString name; QString defval; bool in; bool out; bool isMultiCallback; bool isSingleCallback; }; int main(int argc, char *argv[]) { if(argc != 3) { qDebug() << "Usage: jsonapi-generator SOURCE_PATH OUTPUT_PATH"; return EINVAL; } QString sourcePath(argv[1]); QString outputPath(argv[2]); QString doxPrefix(outputPath+"/xml/"); 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 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 cppApiIncludesSet; auto fatalError = [&]( std::initializer_list errors, int ernum = EINVAL ) { QString errorMsg; for(const QVariant& error: errors) errorMsg += error.toString() + " "; errorMsg.chop(1); QByteArray cppError(QString("#error "+errorMsg+"\n").toLocal8Bit()); wrappersDefFile.write(cppError); cppApiIncludesFile.write(cppError); qDebug() << errorMsg; return ernum; }; QDirIterator it(doxPrefix, QStringList() << "*8h.xml", QDir::Files); while(it.hasNext()) { QDomDocument hDoc; QString hFilePath(it.next()); QString parseError; int line, column; QFile hFile(hFilePath); if (!hFile.open(QIODevice::ReadOnly) || !hDoc.setContent(&hFile, &parseError, &line, &column)) { qWarning() << "Error parsing:" << hFilePath << parseError << line << column; continue; } QFileInfo headerFileInfo(hDoc.elementsByTagName("location").at(0) .attributes().namedItem("file").nodeValue()); QString headerRelPath = headerFileInfo.dir().dirName() + "/" + headerFileInfo.fileName(); QDomNodeList sectiondefs = hDoc.elementsByTagName("memberdef"); for(int j = 0; j < sectiondefs.size(); ++j) { QDomElement sectDef = sectiondefs.item(j).toElement(); if( sectDef.attributes().namedItem("kind").nodeValue() != "variable" || sectDef.elementsByTagName("jsonapi").isEmpty() ) continue; QString instanceName = sectDef.elementsByTagName("name").item(0).toElement().text(); QString typeName = sectDef.elementsByTagName("ref").item(0).toElement().text(); QString typeFilePath(doxPrefix); typeFilePath += sectDef.elementsByTagName("ref").item(0) .attributes().namedItem("refid").nodeValue(); typeFilePath += ".xml"; QDomDocument typeDoc; QFile typeFile(typeFilePath); if ( !typeFile.open(QIODevice::ReadOnly) || !typeDoc.setContent(&typeFile, &parseError, &line, &column) ) { qWarning() << "Error parsing:" << typeFilePath << parseError << line << column; continue; } QDomNodeList members = typeDoc.elementsByTagName("member"); for (int i = 0; i < members.size(); ++i) { QDomNode member = members.item(i); QString refid(member.attributes().namedItem("refid").nodeValue()); QString methodName(member.firstChildElement("name").toElement().text()); bool requiresAuth = true; QString defFilePath(doxPrefix + refid.split('_')[0] + ".xml"); qDebug() << "Looking for" << typeName << methodName << "into" << typeFilePath; QDomDocument defDoc; QFile defFile(defFilePath); if ( !defFile.open(QIODevice::ReadOnly) || !defDoc.setContent(&defFile, &parseError, &line, &column) ) { qWarning() << "Error parsing:" << defFilePath << parseError << line << column; continue; } QDomElement memberdef; QDomNodeList memberdefs = defDoc.elementsByTagName("memberdef"); for (int k = 0; k < memberdefs.size(); ++k) { QDomElement tmpMBD = memberdefs.item(k).toElement(); QString tmpId = tmpMBD.attributes().namedItem("id").nodeValue(); QString tmpKind = tmpMBD.attributes().namedItem("kind").nodeValue(); QDomNodeList tmpJsonApiTagList(tmpMBD.elementsByTagName("jsonapi")); if( tmpJsonApiTagList.count() && tmpId == refid && tmpKind == "function") { QDomElement tmpJsonApiTag = tmpJsonApiTagList.item(0).toElement(); QString tmpAccessValue; if(tmpJsonApiTag.hasAttribute("access")) tmpAccessValue = tmpJsonApiTag.attributeNode("access").nodeValue(); requiresAuth = "unauthenticated" != tmpAccessValue; if("manualwrapper" != tmpAccessValue) memberdef = tmpMBD; break; } } if(memberdef.isNull()) continue; QString apiPath("/" + instanceName + "/" + methodName); QString retvalType = memberdef.firstChildElement("type").text(); QMap paramsMap; QStringList orderedParamNames; 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; QString& pName(tmpParam.name); QString& pType(tmpParam.type); pName = tmpPE.firstChildElement("declname").text(); QDomElement tmpDefval = tmpPE.firstChildElement("defval"); if(!tmpDefval.isNull()) tmpParam.defval = tmpDefval.text(); QDomElement tmpType = tmpPE.firstChildElement("type"); pType = tmpType.text(); if(pType.startsWith("const ")) pType.remove(0,6); 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); } QDomNodeList parameternames = memberdef.elementsByTagName("parametername"); for (int k = 0; k < parameternames.size(); ++k) { QDomElement tmpPN = parameternames.item(k).toElement(); MethodParam& tmpParam = paramsMap[tmpPN.text()]; QString tmpD = tmpPN.attributes().namedItem("direction").nodeValue(); if(tmpD.contains("in")) { tmpParam.in = true; hasInput = true; } if(tmpD.contains("out")) { tmpParam.out = true; hasOutput = true; } } // Params sanity check for( const MethodParam& pm : paramsMap) if( !(pm.isMultiCallback || pm.isSingleCallback || pm.in || pm.out) ) return fatalError( { "Parameter:", pm.name, "of:", apiPath, "declared in:", headerRelPath, "miss doxygen parameter direction attribute!", defFile.fileName() }); QString functionCall("\t\t"); if(retvalType != "void") { functionCall += retvalType + " retval = "; hasOutput = true; } functionCall += instanceName + "->" + methodName + "("; functionCall += orderedParamNames.join(", ") + ");\n"; qDebug() << instanceName << apiPath << retvalType << typeName << methodName; for (const QString& pn : orderedParamNames) { const MethodParam& mp(paramsMap[pn]); qDebug() << "\t" << mp.type << mp.name << mp.in << mp.out; } QString inputParamsDeserialization; if(hasInput) { inputParamsDeserialization += "\t\t{\n" "\t\t\tRsGenericSerializer::SerializeContext& ctx(cReq);\n" "\t\t\tRsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);\n"; } QString outputParamsSerialization; if(hasOutput) { outputParamsSerialization += "\t\t{\n" "\t\t\tRsGenericSerializer::SerializeContext& ctx(cAns);\n" "\t\t\tRsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);\n"; } QString paramsDeclaration; for (const QString& pn : orderedParamNames) { const MethodParam& mp(paramsMap[pn]); paramsDeclaration += "\t\t" + mp.type + " " + mp.name; if(!mp.defval.isEmpty()) paramsDeclaration += "(" + mp.defval + ")"; paramsDeclaration += ";\n"; if(mp.in) inputParamsDeserialization += "\t\t\tRS_SERIAL_PROCESS(" + mp.name + ");\n"; if(mp.out) outputParamsSerialization += "\t\t\tRS_SERIAL_PROCESS(" + mp.name + ");\n"; } if(hasInput) inputParamsDeserialization += "\t\t}\n"; if(retvalType != "void") outputParamsSerialization += "\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 += "\t\t\tRsGenericSerializer::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 substitutionsMap; substitutionsMap.insert("paramsDeclaration", paramsDeclaration); substitutionsMap.insert("inputParamsDeserialization", inputParamsDeserialization); substitutionsMap.insert("outputParamsSerialization", outputParamsSerialization); substitutionsMap.insert("instanceName", instanceName); 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); substitutionsMap.insert("requiresAuth", requiresAuth ? "true" : "false"); 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()); QMap::iterator it; for ( it = substitutionsMap.begin(); it != substitutionsMap.end(); ++it ) wrapperDef.replace(QString("$%"+it.key()+"%$"), QString(it.value()), Qt::CaseSensitive); wrappersDefFile.write(wrapperDef.toLocal8Bit()); cppApiIncludesSet.insert("#include \"" + headerRelPath + "\"\n"); } } } for(const QString& incl : cppApiIncludesSet) cppApiIncludesFile.write(incl.toLocal8Bit()); return 0; }