summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2016-08-29 15:16:16 +0100
committerDan Goodliffe <dan@randomdan.homeip.net>2016-08-29 15:16:16 +0100
commit66250b8049b720e02bd1008b32d2707b5d13a19e (patch)
treec116031933ec32a60fb3e44257cb551c686549d3
parentFind operation by name, not route (diff)
downloadicespider-66250b8049b720e02bd1008b32d2707b5d13a19e.tar.bz2
icespider-66250b8049b720e02bd1008b32d2707b5d13a19e.tar.xz
icespider-66250b8049b720e02bd1008b32d2707b5d13a19e.zip
Add support for mash up routes that call multiple services
-rw-r--r--icespider/common/routes.ice12
-rw-r--r--icespider/compile/routeCompiler.cpp193
-rw-r--r--icespider/compile/routeCompiler.h7
-rw-r--r--icespider/unittests/test-api.ice12
-rw-r--r--icespider/unittests/testApp.cpp35
-rw-r--r--icespider/unittests/testCompile.cpp12
-rw-r--r--icespider/unittests/testRoutes.json44
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": [