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": [  | 
