diff options
-rw-r--r-- | icespider/common/routes.ice | 12 | ||||
-rw-r--r-- | icespider/compile/routeCompiler.cpp | 193 | ||||
-rw-r--r-- | icespider/compile/routeCompiler.h | 7 | ||||
-rw-r--r-- | icespider/unittests/test-api.ice | 12 | ||||
-rw-r--r-- | icespider/unittests/testApp.cpp | 35 | ||||
-rw-r--r-- | icespider/unittests/testCompile.cpp | 12 | ||||
-rw-r--r-- | icespider/unittests/testRoutes.json | 44 |
7 files changed, 276 insertions, 39 deletions
diff --git a/icespider/common/routes.ice b/icespider/common/routes.ice index bc2406a..511629c 100644 --- a/icespider/common/routes.ice +++ b/icespider/common/routes.ice @@ -5,6 +5,7 @@ module IceSpider { sequence<string> StringSeq; + dictionary<string, string> StringMap; class Parameter { string name; @@ -28,12 +29,21 @@ module IceSpider { sequence<OutputSerializer> OutputSerializers; + class Operation { + string operation; + StringMap paramOverrides; + }; + + dictionary<string, Operation> Operations; + class Route { string name; string path; HttpMethod method = GET; - string operation; + optional(0) string operation; Parameters params; + Operations operations; + string type; OutputSerializers outputSerializers; }; diff --git a/icespider/compile/routeCompiler.cpp b/icespider/compile/routeCompiler.cpp index 50aa3b2..e38f240 100644 --- a/icespider/compile/routeCompiler.cpp +++ b/icespider/compile/routeCompiler.cpp @@ -73,21 +73,77 @@ namespace IceSpider { throw std::runtime_error("Find operation " + on + " failed."); } + RouteCompiler::Type + RouteCompiler::findType(const std::string & tn, const Slice::ContainerPtr & c, const Ice::StringSeq & ns) + { + for (const auto & strct : c->structs()) { + auto fqon = boost::algorithm::join(ns + strct->name(), "."); + if (fqon == tn) return { strct, NULL }; + auto t = findType(tn, strct, ns + strct->name()); + } + for (const auto & cls : c->classes()) { + auto fqon = boost::algorithm::join(ns + cls->name(), "."); + if (fqon == tn) return { NULL, cls->declaration() }; + auto t = findType(tn, cls, ns + cls->name()); + if (t.first || t.second) return t; + } + for (const auto & m : c->modules()) { + auto t = findType(tn, m, ns + m->name()); + if (t.first || t.second) return t; + } + return { NULL, NULL }; + } + + RouteCompiler::Type + RouteCompiler::findType(const std::string & tn, const Units & us) + { + for (const auto & u : us) { + auto t = findType(tn, u.second); + if (t.first || t.second) return t; + } + throw std::runtime_error("Find type " + tn + " failed."); + } + + RouteCompiler::ParameterMap + RouteCompiler::findParameters(RoutePtr r, const Units & us) + { + RouteCompiler::ParameterMap pm; + for (const auto & o : r->operations) { + auto op = findOperation(o.second->operation, us); + if (!op) { + throw std::runtime_error("Find operator failed for " + r->name); + } + for (const auto & p : op->parameters()) { + auto po = o.second->paramOverrides.find(p->name()); + if (po != o.second->paramOverrides.end()) { + pm[po->second] = p; + } + else { + pm[p->name()] = p; + } + } + } + return pm; + } + void RouteCompiler::applyDefaults(RouteConfigurationPtr c, const Units & u) const { for (const auto & r : c->routes) { - auto o = findOperation(r->operation, u); - for (const auto & p : o->parameters()) { + if (r->operation) { + r->operations[std::string()] = new Operation(*r->operation, {}); + } + auto ps = findParameters(r, u); + for (const auto & p : ps) { auto defined = std::find_if(r->params.begin(), r->params.end(), [p](const auto & rp) { - return p->name() == rp->name; + return p.first == rp->name; }); if (defined != r->params.end()) { auto d = *defined; if (!d->key) d->key = d->name; } else { - r->params.push_back(new Parameter(p->name(), ParameterSource::URL, p->name(), false, IceUtil::Optional<std::string>(), false)); + r->params.push_back(new Parameter(p.first, ParameterSource::URL, p.first, false, IceUtil::Optional<std::string>(), false)); defined = --r->params.end(); } auto d = *defined; @@ -248,9 +304,6 @@ namespace IceSpider { fprintbf(output, "namespace %s {\n", c->name); fprintbf(1, output, "// Implementation classes.\n\n"); for (const auto & r : c->routes) { - auto proxyName = r->operation.substr(0, r->operation.find_last_of('.')); - auto operation = r->operation.substr(r->operation.find_last_of('.') + 1); - boost::algorithm::replace_all(proxyName, ".", "::"); std::string methodName = getEnumString(r->method); fprintbf(1, output, "// Route name: %s\n", r->name); @@ -294,10 +347,10 @@ namespace IceSpider { fprintbf(3, output, "}\n\n"); fprintbf(3, output, "void execute(IceSpider::IHttpRequest * request) const\n"); fprintbf(3, output, "{\n"); - auto o = findOperation(r->operation, units); + auto ps = findParameters(r, units); for (const auto & p : r->params) { if (p->hasUserSource) { - auto ip = *std::find_if(o->parameters().begin(), o->parameters().end(), [p](const auto & ip) { return ip->name() == p->name; }); + auto ip = ps.find(p->name)->second; fprintbf(4, output, "auto _p_%s(request->get%sParam<%s>(_p%c_%s)", p->name, getEnumString(p->source), Slice::typeToString(ip->type()), p->source == ParameterSource::URL ? 'i' : 'n', @@ -316,31 +369,11 @@ namespace IceSpider { fprintbf(0, output, ");\n"); } } - fprintbf(4, output, "auto prx = getProxy<%s>(request);\n", proxyName); - if (o->returnsData()) { - fprintbf(4, output, "request->response(this, prx->%s(", operation); + if (r->operation) { + addSingleOperation(output, r, findOperation(*r->operation, units)); } else { - fprintbf(4, output, "prx->%s(", operation); - } - for (const auto & p : o->parameters()) { - auto rp = *std::find_if(r->params.begin(), r->params.end(), [p](const auto & rp) { - return rp->name == p->name(); - }); - if (rp->hasUserSource) { - fprintbf(output, "_p_%s, ", p->name()); - } - else { - fprintbf(output, "_pd_%s, ", p->name()); - } - } - fprintbf(output, "request->getContext())"); - if (o->returnsData()) { - fprintbf(output, ")"); - } - fprintbf(output, ";\n"); - if (!o->returnsData()) { - fprintbf(4, output, "request->response(200, \"OK\");\n"); + addMashupOperations(output, r, units); } fprintbf(3, output, "}\n\n"); fprintbf(2, output, "private:\n"); @@ -354,7 +387,7 @@ namespace IceSpider { } } if (p->defaultExpr) { - auto ip = *std::find_if(o->parameters().begin(), o->parameters().end(), [p](const auto & ip) { return ip->name() == p->name; }); + auto ip = ps.find(p->name)->second; fprintbf(3, output, "const %s _pd_%s;\n", Slice::typeToString(ip->type()), p->name); @@ -369,6 +402,100 @@ namespace IceSpider { } fprintf(output, "\n// End generated code.\n"); } + + void + RouteCompiler::addSingleOperation(FILE * output, RoutePtr r, Slice::OperationPtr o) const + { + auto proxyName = r->operation->substr(0, r->operation->find_last_of('.')); + auto operation = r->operation->substr(r->operation->find_last_of('.') + 1); + boost::algorithm::replace_all(proxyName, ".", "::"); + fprintbf(4, output, "auto prx = getProxy<%s>(request);\n", proxyName); + if (o->returnsData()) { + fprintbf(4, output, "request->response(this, prx->%s(", operation); + } + else { + fprintbf(4, output, "prx->%s(", operation); + } + for (const auto & p : o->parameters()) { + auto rp = *std::find_if(r->params.begin(), r->params.end(), [p](const auto & rp) { + return rp->name == p->name(); + }); + if (rp->hasUserSource) { + fprintbf(output, "_p_%s, ", p->name()); + } + else { + fprintbf(output, "_pd_%s, ", p->name()); + } + } + fprintbf(output, "request->getContext())"); + if (o->returnsData()) { + fprintbf(output, ")"); + } + fprintbf(output, ";\n"); + if (!o->returnsData()) { + fprintbf(4, output, "request->response(200, \"OK\");\n"); + } + } + + void + RouteCompiler::addMashupOperations(FILE * output, RoutePtr r, const Units & us) const + { + int n = 0; + typedef std::map<std::string, int> Proxies; + Proxies proxies; + for (const auto & o : r->operations) { + auto proxyName = o.second->operation.substr(0, o.second->operation.find_last_of('.')); + if (proxies.find(proxyName) == proxies.end()) { + proxies[proxyName] = n; + fprintbf(4, output, "auto prx%d = getProxy<%s>(request);\n", n, boost::algorithm::replace_all_copy(proxyName, ".", "::")); + n += 1; + } + } + for (const auto & o : r->operations) { + auto proxyName = o.second->operation.substr(0, o.second->operation.find_last_of('.')); + auto operation = o.second->operation.substr(o.second->operation.find_last_of('.') + 1); + fprintbf(4, output, "auto _ar_%s = prx%s->begin_%s(", o.first, proxies.find(proxyName)->second, operation); + auto so = findOperation(o.second->operation, us); + for (const auto & p : so->parameters()) { + auto po = o.second->paramOverrides.find(p->name()); + fprintbf(output, "_p_%s, ", (po != o.second->paramOverrides.end() ? po->second : p->name())); + } + fprintbf(output, "request->getContext());\n"); + } + auto t = findType(r->type, us); + Slice::DataMemberList members; + if (t.second) { + fprintbf(4, output, "request->response<%s>(this, new %s(", + Slice::typeToString(t.second), + t.second->scoped()); + members = t.second->definition()->dataMembers(); + } + else { + fprintbf(4, output, "request->response<%s>(this, {", + Slice::typeToString(t.first)); + members = t.first->dataMembers(); + } + for (auto mi = members.begin(); mi != members.end(); mi++) { + for (const auto & o : r->operations) { + auto proxyName = o.second->operation.substr(0, o.second->operation.find_last_of('.')); + auto operation = o.second->operation.substr(o.second->operation.find_last_of('.') + 1); + if ((*mi)->name() == o.first) { + if (mi != members.begin()) { + fprintbf(output, ","); + } + fprintbf(output, "\n"); + fprintbf(6, output, "prx%s->end_%s(_ar_%s)", proxies.find(proxyName)->second, operation, o.first); + } + } + } + if (t.second) { + fprintf(output, ")"); + } + else { + fprintf(output, " }"); + } + fprintf(output, ");\n"); + } } } diff --git a/icespider/compile/routeCompiler.h b/icespider/compile/routeCompiler.h index 63b7c38..f0da545 100644 --- a/icespider/compile/routeCompiler.h +++ b/icespider/compile/routeCompiler.h @@ -28,8 +28,15 @@ namespace IceSpider { void processConfiguration(FILE * output, RouteConfigurationPtr, const Units &) const; void registerOutputSerializers(FILE * output, RoutePtr) const; void releaseOutputSerializers(FILE * output, RoutePtr) const; + void addSingleOperation(FILE * output, RoutePtr, Slice::OperationPtr) const; + void addMashupOperations(FILE * output, RoutePtr, const Units &) const; + typedef std::map<std::string, Slice::ParamDeclPtr> ParameterMap; + static ParameterMap findParameters(RoutePtr, const Units &); static Slice::OperationPtr findOperation(const std::string &, const Units &); static Slice::OperationPtr findOperation(const std::string &, const Slice::ContainerPtr &, const Ice::StringSeq & = Ice::StringSeq()); + typedef std::pair<Slice::StructPtr, Slice::ClassDeclPtr> Type; + static Type findType(const std::string &, const Units &); + static Type findType(const std::string &, const Slice::ContainerPtr &, const Ice::StringSeq & = Ice::StringSeq()); }; } } diff --git a/icespider/unittests/test-api.ice b/icespider/unittests/test-api.ice index ff43dc2..310761b 100644 --- a/icespider/unittests/test-api.ice +++ b/icespider/unittests/test-api.ice @@ -3,8 +3,18 @@ module TestIceSpider { string value; }; + struct Mash1 { + SomeModel a; + SomeModel b; + }; + + class Mash2 { + SomeModel a; + SomeModel b; + }; + interface TestApi { - SomeModel index(); + SomeModel index(); SomeModel withParams(string s, int i); void returnNothing(string s); void complexParam(optional(0) string s, SomeModel m); diff --git a/icespider/unittests/testApp.cpp b/icespider/unittests/testApp.cpp index 7df75f2..ef98e6a 100644 --- a/icespider/unittests/testApp.cpp +++ b/icespider/unittests/testApp.cpp @@ -27,7 +27,7 @@ void forceEarlyChangeDir() BOOST_AUTO_TEST_CASE( testLoadConfiguration ) { - BOOST_REQUIRE_EQUAL(6, AdHoc::PluginManager::getDefault()->getAll<IRouteHandler>().size()); + BOOST_REQUIRE_EQUAL(8, AdHoc::PluginManager::getDefault()->getAll<IRouteHandler>().size()); } class TestRequest : public IHttpRequest { @@ -89,11 +89,12 @@ BOOST_FIXTURE_TEST_SUITE(c, Core); BOOST_AUTO_TEST_CASE( testCoreSettings ) { BOOST_REQUIRE_EQUAL(6, routes.size()); - BOOST_REQUIRE_EQUAL(4, routes[HttpMethod::GET].size()); + BOOST_REQUIRE_EQUAL(5, routes[HttpMethod::GET].size()); BOOST_REQUIRE_EQUAL(1, routes[HttpMethod::GET][0].size()); BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::GET][1].size()); BOOST_REQUIRE_EQUAL(1, routes[HttpMethod::GET][2].size()); BOOST_REQUIRE_EQUAL(2, routes[HttpMethod::GET][3].size()); + BOOST_REQUIRE_EQUAL(2, routes[HttpMethod::GET][4].size()); BOOST_REQUIRE_EQUAL(1, routes[HttpMethod::HEAD].size()); BOOST_REQUIRE_EQUAL(2, routes[HttpMethod::POST].size()); BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::POST][0].size()); @@ -139,6 +140,12 @@ BOOST_AUTO_TEST_CASE( testFindRoutes ) TestRequest requestDeleteThing(this, HttpMethod::DELETE, "/something"); BOOST_REQUIRE(findRoute(&requestDeleteThing)); + + TestRequest requestMashS(this, HttpMethod::GET, "/mashS/mash/1/3"); + BOOST_REQUIRE(findRoute(&requestMashS)); + + TestRequest requestMashC(this, HttpMethod::GET, "/mashS/mash/1/3"); + BOOST_REQUIRE(findRoute(&requestMashC)); } BOOST_AUTO_TEST_SUITE_END(); @@ -217,6 +224,30 @@ BOOST_AUTO_TEST_CASE( testCallIndex ) BOOST_REQUIRE_EQUAL(v->value, "index"); } +BOOST_AUTO_TEST_CASE( testCallMashS ) +{ + TestRequest requestGetMashS(this, HttpMethod::GET, "/mashS/something/something/1234"); + process(&requestGetMashS); + auto h = parseHeaders(requestGetMashS.output); + BOOST_REQUIRE_EQUAL(h["Status"], "200 OK"); + BOOST_REQUIRE_EQUAL(h["Content-Type"], "application/json"); + auto v = Slicer::DeserializeAny<Slicer::JsonStreamDeserializer, TestIceSpider::Mash1>(requestGetMashS.output); + BOOST_REQUIRE_EQUAL(v.a->value, "withParams"); + BOOST_REQUIRE_EQUAL(v.b->value, "withParams"); +} + +BOOST_AUTO_TEST_CASE( testCallMashC ) +{ + TestRequest requestGetMashC(this, HttpMethod::GET, "/mashC/something/something/1234"); + process(&requestGetMashC); + auto h = parseHeaders(requestGetMashC.output); + BOOST_REQUIRE_EQUAL(h["Status"], "200 OK"); + BOOST_REQUIRE_EQUAL(h["Content-Type"], "application/json"); + auto v = Slicer::DeserializeAny<Slicer::JsonStreamDeserializer, TestIceSpider::Mash2Ptr>(requestGetMashC.output); + BOOST_REQUIRE_EQUAL(v->a->value, "withParams"); + BOOST_REQUIRE_EQUAL(v->b->value, "withParams"); +} + BOOST_AUTO_TEST_CASE( testCallViewSomething1234 ) { TestRequest requestGetItem(this, HttpMethod::GET, "/view/something/1234"); diff --git a/icespider/unittests/testCompile.cpp b/icespider/unittests/testCompile.cpp index 46175ef..6184a7e 100644 --- a/icespider/unittests/testCompile.cpp +++ b/icespider/unittests/testCompile.cpp @@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE( testLoadConfiguration ) rc.applyDefaults(cfg, u); BOOST_REQUIRE_EQUAL("common", cfg->name); - BOOST_REQUIRE_EQUAL(6, cfg->routes.size()); + BOOST_REQUIRE_EQUAL(8, cfg->routes.size()); BOOST_REQUIRE_EQUAL("index", cfg->routes[0]->name); BOOST_REQUIRE_EQUAL("/", cfg->routes[0]->path); @@ -56,6 +56,14 @@ BOOST_AUTO_TEST_CASE( testLoadConfiguration ) BOOST_REQUIRE_EQUAL(HttpMethod::POST, cfg->routes[3]->method); BOOST_REQUIRE_EQUAL(2, cfg->routes[3]->params.size()); + BOOST_REQUIRE_EQUAL("mashStruct", cfg->routes[6]->name); + BOOST_REQUIRE_EQUAL(HttpMethod::GET, cfg->routes[6]->method); + BOOST_REQUIRE_EQUAL(3, cfg->routes[6]->params.size()); + + BOOST_REQUIRE_EQUAL("mashClass", cfg->routes[7]->name); + BOOST_REQUIRE_EQUAL(HttpMethod::GET, cfg->routes[7]->method); + BOOST_REQUIRE_EQUAL(3, cfg->routes[7]->params.size()); + BOOST_REQUIRE_EQUAL(1, cfg->slices.size()); BOOST_REQUIRE_EQUAL("test-api.ice", cfg->slices[0]); } @@ -124,7 +132,7 @@ BOOST_AUTO_TEST_CASE( testLoad ) BOOST_TEST_INFO(dlerror()); BOOST_REQUIRE(lib); - BOOST_REQUIRE_EQUAL(6, AdHoc::PluginManager::getDefault()->getAll<IRouteHandler>().size()); + BOOST_REQUIRE_EQUAL(8, AdHoc::PluginManager::getDefault()->getAll<IRouteHandler>().size()); // smoke test (block ensure dlclose dones't cause segfault) { auto route = AdHoc::PluginManager::getDefault()->get<IRouteHandler>("common::index"); diff --git a/icespider/unittests/testRoutes.json b/icespider/unittests/testRoutes.json index 921d983..3b96a11 100644 --- a/icespider/unittests/testRoutes.json +++ b/icespider/unittests/testRoutes.json @@ -68,6 +68,50 @@ "default": "1234" } ] + }, + { + "name": "mashStruct", + "path": "/mashS/{s}/{t}/{i}", + "method": "GET", + "type": "TestIceSpider.Mash1", + "operations": [{ + "key": "a", + "value": { + "operation": "TestIceSpider.TestApi.withParams" + } + }, + { + "key": "b", + "value": { + "operation": "TestIceSpider.TestApi.withParams", + "paramOverrides": [{ + "key": "s", + "value": "t" + }] + } + }] + }, + { + "name": "mashClass", + "path": "/mashC/{s}/{t}/{i}", + "method": "GET", + "type": "TestIceSpider.Mash2", + "operations": [{ + "key": "a", + "value": { + "operation": "TestIceSpider.TestApi.withParams" + } + }, + { + "key": "b", + "value": { + "operation": "TestIceSpider.TestApi.withParams", + "paramOverrides": [{ + "key": "s", + "value": "t" + }] + } + }] } ], "slices": [ |