18 #include "dispatchtypechained_p.h"
20 #include "actionchain.h"
24 #include <QtCore/QUrl>
29 , d_ptr(new DispatchTypeChainedPrivate)
34 DispatchTypeChained::~DispatchTypeChained()
44 Actions endPoints = d->endPoints;
45 std::sort(endPoints.begin(), endPoints.end(), [](
Action *a,
Action *b) ->
bool {
46 return a->reverse() < b->reverse();
49 QVector<QStringList> paths;
50 QVector<QStringList> unattachedTable;
51 for (
Action *endPoint : endPoints) {
53 if (endPoint->numberOfArgs() == -1) {
54 parts.append(QLatin1String(
"..."));
56 for (
int i = 0; i < endPoint->numberOfArgs(); ++i) {
57 parts.append(QLatin1String(
"*"));
62 QString extra = DispatchTypeChainedPrivate::listExtraHttpMethods(endPoint);
63 QString consumes = DispatchTypeChainedPrivate::listExtraConsumes(endPoint);
65 Action *current = endPoint;
68 parts.prepend(QLatin1String(
"*"));
72 const QStringList pathParts = attributes.values(QLatin1String(
"PathPart"));
73 for (
const QString &part : pathParts) {
74 if (!part.isEmpty()) {
79 parent = attributes.value(QLatin1String(
"Chained"));
80 current = d->actions.value(parent);
82 parents.prepend(current);
86 if (parent != QLatin1String(
"/")) {
88 if (parents.isEmpty()) {
89 row.append(QLatin1Char(
'/') + endPoint->reverse());
91 row.append(QLatin1Char(
'/') + parents.first()->reverse());
94 unattachedTable.append(row);
98 QVector<QStringList> rows;
99 for (
Action *p : parents) {
100 QString name = QLatin1Char(
'/') + p->
reverse();
102 QString extraHttpMethod = DispatchTypeChainedPrivate::listExtraHttpMethods(p);
103 if (!extraHttpMethod.isEmpty()) {
104 name.prepend(extraHttpMethod + QLatin1Char(
' '));
107 const auto attributes = p->attributes();
108 auto it = attributes.constFind(QLatin1String(
"CaptureArgs"));
109 if (it != attributes.constEnd()) {
110 name.append(QLatin1String(
" (") + it.value() + QLatin1Char(
')'));
112 name.append(QLatin1String(
" (0)"));
115 QString ct = DispatchTypeChainedPrivate::listExtraConsumes(p);
117 name.append(QLatin1String(
" :") + ct);
120 if (p != parents[0]) {
121 name = QLatin1String(
"-> ") + name;
124 rows.append({QString(), name});
128 if (!rows.isEmpty()) {
129 line.append(QLatin1String(
"=> "));
131 if (!extra.isEmpty()) {
132 line.append(extra + QLatin1Char(
' '));
134 line.append(QLatin1Char(
'/') + endPoint->reverse());
135 if (endPoint->numberOfArgs() == -1) {
136 line.append(QLatin1String(
" (...)"));
138 line.append(QLatin1String(
" (") + QString::number(endPoint->numberOfArgs()) + QLatin1Char(
')'));
141 if (!consumes.isEmpty()) {
142 line.append(QLatin1String(
" :") + consumes);
144 rows.append({QString(), line});
146 rows[0][0] = QLatin1Char(
'/') + parts.join(QLatin1Char(
'/'));
150 QTextStream out(&buffer, QIODevice::WriteOnly);
152 if (!paths.isEmpty()) {
153 out << Utils::buildTable(paths, { QLatin1String(
"Path Spec"), QLatin1String(
"Private") },
154 QLatin1String(
"Loaded Chained actions:"));
157 if (!unattachedTable.isEmpty()) {
158 out << Utils::buildTable(unattachedTable, { QLatin1String(
"Private"), QLatin1String(
"Missing parent") },
159 QLatin1String(
"Unattached Chained actions:"));
167 if (!args.isEmpty()) {
173 const BestActionMatch ret = d->recurseMatch(args.size(), QStringLiteral(
"/"), path.split(QLatin1Char(
'/')));
175 if (ret.isNull || chain.isEmpty()) {
179 QStringList decodedArgs;
180 const QStringList parts = ret.parts;
181 for (
const QString &arg : parts) {
183 decodedArgs.append(Utils::decodePercentEncoding(&aux));
187 Request *request = c->request();
201 const QStringList chainedList = attributes.values(QLatin1String(
"Chained"));
202 if (chainedList.isEmpty()) {
206 if (chainedList.size() > 1) {
207 qCCritical(CUTELYST_DISPATCHER_CHAINED)
208 <<
"Multiple Chained attributes not supported registering" << action->
reverse();
212 const QString chainedTo = chainedList.first();
213 if (chainedTo == QLatin1Char(
'/') + action->
name()) {
214 qCCritical(CUTELYST_DISPATCHER_CHAINED)
215 <<
"Actions cannot chain to themselves registering /" << action->
name();
219 const QStringList pathPart = attributes.values(QLatin1String(
"PathPart"));
221 QString part = action->
name();
223 if (pathPart.size() == 1 && !pathPart[0].isEmpty()) {
225 }
else if (pathPart.size() > 1) {
226 qCCritical(CUTELYST_DISPATCHER_CHAINED)
227 <<
"Multiple PathPart attributes not supported registering"
232 if (part.startsWith(QLatin1Char(
'/'))) {
233 qCCritical(CUTELYST_DISPATCHER_CHAINED)
234 <<
"Absolute parameters to PathPart not allowed registering"
239 attributes.insert(QStringLiteral(
"PathPart"), part);
242 auto &childrenOf = d->childrenOf[chainedTo][part];
243 childrenOf.insert(childrenOf.begin(), action);
245 d->actions[QLatin1Char(
'/') + action->
reverse()] = action;
247 if (!d->checkArgsAttr(action, QLatin1String(
"Args")) ||
248 !d->checkArgsAttr(action, QLatin1String(
"CaptureArgs"))) {
252 if (attributes.contains(QLatin1String(
"Args")) && attributes.contains(QLatin1String(
"CaptureArgs"))) {
253 qCCritical(CUTELYST_DISPATCHER_CHAINED)
254 <<
"Combining Args and CaptureArgs attributes not supported registering"
259 if (!attributes.contains(QLatin1String(
"CaptureArgs"))) {
260 d->endPoints.push_back(action);
271 const QMap<QString, QString> attributes = action->
attributes();
272 if (!(attributes.contains(QStringLiteral(
"Chained")) &&
273 !attributes.contains(QStringLiteral(
"CaptureArgs")))) {
274 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: action is not an end point" << action;
279 QStringList localCaptures = captures;
283 const QMap<QString, QString> curr_attributes = curr->
attributes();
284 if (curr_attributes.contains(QStringLiteral(
"CaptureArgs"))) {
287 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: not enough captures" << curr->
numberOfCaptures() << captures.size();
291 parts = localCaptures.mid(localCaptures.size() - curr->
numberOfCaptures()) + parts;
292 localCaptures = localCaptures.mid(0, localCaptures.size() - curr->
numberOfCaptures());
295 const QString pp = curr_attributes.value(QStringLiteral(
"PathPart"));
300 parent = curr_attributes.value(QStringLiteral(
"Chained"));
301 curr = d->actions.value(parent);
304 if (parent != QLatin1String(
"/")) {
306 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: dangling action" << parent;
310 if (!localCaptures.isEmpty()) {
312 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: too many captures" << localCaptures;
316 ret = QLatin1Char(
'/') + parts.join(QLatin1Char(
'/'));
325 if (qobject_cast<ActionChain*>(action)) {
330 if (!action->
attributes().contains(QStringLiteral(
"Chained"))) {
339 const QString parent = curr->
attribute(QStringLiteral(
"Chained"));
340 curr = d->actions.value(parent);
350 if (d->actions.isEmpty()) {
359 BestActionMatch DispatchTypeChainedPrivate::recurseMatch(
int reqArgsSize,
const QString &parent,
const QStringList &pathParts)
const
361 BestActionMatch bestAction;
362 auto it = childrenOf.constFind(parent);
363 if (it == childrenOf.constEnd()) {
367 const StringActionsMap &children = it.value();
368 QStringList keys = children.keys();
369 std::sort(keys.begin(), keys.end(), [](
const QString &a,
const QString &b) ->
bool {
371 return b.size() < a.size();
374 for (
const QString &tryPart : keys) {
375 QStringList parts = pathParts;
376 if (!tryPart.isEmpty()) {
379 int tryPartCount = tryPart.count(QLatin1Char(
'/')) + 1;
380 const QStringList possiblePart = parts.mid(0, tryPartCount);
381 if (tryPart != possiblePart.join(QLatin1Char(
'/'))) {
384 parts = parts.mid(tryPartCount);
387 const Actions tryActions = children.value(tryPart);
388 for (
Action *action : tryActions) {
389 const QMap<QString, QString> attributes = action->attributes();
390 if (attributes.contains(QStringLiteral(
"CaptureArgs"))) {
391 const int captureCount = action->numberOfCaptures();
393 if (parts.size() < captureCount) {
398 const QStringList captures = parts.mid(0, captureCount);
401 if (!action->matchCaptures(captures.size())) {
405 const QStringList localParts = parts.mid(captureCount);
408 const BestActionMatch ret = recurseMatch(reqArgsSize, QLatin1Char(
'/') + action->reverse(), localParts);
414 const QStringList actionCaptures = ret.captures;
415 const QStringList actionParts = ret.parts;
416 int bestActionParts = bestAction.parts.size();
418 if (!actions.isEmpty() &&
419 (bestAction.isNull ||
420 actionParts.size() < bestActionParts ||
421 (actionParts.size() == bestActionParts &&
422 actionCaptures.size() < bestAction.captures.size() &&
423 ret.n_pathParts > bestAction.n_pathParts))) {
424 actions.prepend(action);
425 int pathparts = attributes.value(QStringLiteral(
"PathPart")).count(QLatin1Char(
'/')) + 1;
426 bestAction.actions = actions;
427 bestAction.captures = captures + actionCaptures;
428 bestAction.parts = actionParts;
429 bestAction.n_pathParts = pathparts + ret.n_pathParts;
430 bestAction.isNull =
false;
433 if (!action->match(reqArgsSize + parts.size())) {
437 const QString argsAttr = attributes.value(QStringLiteral(
"Args"));
438 const int pathparts = attributes.value(QStringLiteral(
"PathPart")).count(QLatin1Char(
'/')) + 1;
446 if (bestAction.isNull ||
447 parts.size() < bestAction.parts.size() ||
448 (parts.isEmpty() && !argsAttr.isEmpty() && action->numberOfArgs() == 0)) {
449 bestAction.actions = { action };
450 bestAction.captures = QStringList();
451 bestAction.parts = parts;
452 bestAction.n_pathParts = pathparts;
453 bestAction.isNull =
false;
462 bool DispatchTypeChainedPrivate::checkArgsAttr(
Action *action,
const QString &name)
const
464 const QMap<QString, QString> attributes = action->
attributes();
465 if (!attributes.contains(name)) {
469 const QStringList values = attributes.values(name);
470 if (values.size() > 1) {
471 qCCritical(CUTELYST_DISPATCHER_CHAINED)
474 <<
"attributes not supported registering"
479 QString args = values[0];
481 if (!args.isEmpty() && args.toInt(&ok) < 0 && !ok) {
482 qCCritical(CUTELYST_DISPATCHER_CHAINED)
484 << name <<
"(" << args <<
") for action"
486 <<
"(use '" << name <<
"' or '" << name <<
"(<number>)')";
493 QString DispatchTypeChainedPrivate::listExtraHttpMethods(
Action *action)
497 if (attributes.contains(QLatin1String(
"HTTP_METHODS"))) {
498 const QStringList extra = attributes.values(QLatin1String(
"HTTP_METHODS"));
499 ret = extra.join(QLatin1String(
", "));
504 QString DispatchTypeChainedPrivate::listExtraConsumes(
Action *action)
508 if (attributes.contains(QLatin1String(
"CONSUMES"))) {
509 const QStringList extra = attributes.values(QLatin1String(
"CONSUMES"));
510 ret = extra.join(QLatin1String(
", "));
515 #include "moc_dispatchtypechained.cpp"