5#include "actionchain.h"
8#include "dispatchtypechained_p.h"
14using namespace Qt::Literals::StringLiterals;
18 , d_ptr(new DispatchTypeChainedPrivate)
32 Actions endPoints = d->endPoints;
33 std::sort(endPoints.begin(), endPoints.end(), [](
Action *a,
Action *b) ->
bool {
34 return a->reverse() < b->reverse();
37 QVector<QStringList> paths;
38 QVector<QStringList> unattachedTable;
39 for (
Action *endPoint : endPoints) {
41 if (endPoint->numberOfArgs() == -1) {
42 parts.append(QLatin1String(
"..."));
44 for (
int i = 0; i < endPoint->numberOfArgs(); ++i) {
45 parts.append(QLatin1String(
"*"));
50 QString extra = DispatchTypeChainedPrivate::listExtraHttpMethods(endPoint);
51 QString consumes = DispatchTypeChainedPrivate::listExtraConsumes(endPoint);
53 Action *current = endPoint;
56 parts.prepend(QLatin1String(
"*"));
60 const QStringList pathParts = attributes.values(QLatin1String(
"PathPart"));
61 for (
const QString &part : pathParts) {
62 if (!part.isEmpty()) {
67 parent = attributes.value(QLatin1String(
"Chained"));
68 current = d->actions.value(parent);
70 parents.prepend(current);
74 if (parent.compare(u
"/") != 0) {
76 if (parents.isEmpty()) {
77 row.append(QLatin1Char(
'/') + endPoint->reverse());
79 row.append(QLatin1Char(
'/') + parents.first()->reverse());
82 unattachedTable.append(row);
86 QVector<QStringList> rows;
87 for (
Action *p : parents) {
88 QString name = QLatin1Char(
'/') + p->
reverse();
90 QString extraHttpMethod = DispatchTypeChainedPrivate::listExtraHttpMethods(p);
91 if (!extraHttpMethod.isEmpty()) {
92 name.prepend(extraHttpMethod + QLatin1Char(
' '));
95 const auto attributes = p->attributes();
96 auto it = attributes.constFind(QLatin1String(
"CaptureArgs"));
97 if (it != attributes.constEnd()) {
98 name.append(QLatin1String(
" (") + it.value() + QLatin1Char(
')'));
100 name.append(QLatin1String(
" (0)"));
103 QString ct = DispatchTypeChainedPrivate::listExtraConsumes(p);
105 name.append(QLatin1String(
" :") + ct);
108 if (p != parents[0]) {
109 name = QLatin1String(
"-> ") + name;
112 rows.append({QString(), name});
116 if (!rows.isEmpty()) {
117 line.append(QLatin1String(
"=> "));
119 if (!extra.isEmpty()) {
120 line.append(extra + QLatin1Char(
' '));
122 line.append(QLatin1Char(
'/') + endPoint->reverse());
123 if (endPoint->numberOfArgs() == -1) {
124 line.append(QLatin1String(
" (...)"));
126 line.append(QLatin1String(
" (") + QString::number(endPoint->numberOfArgs()) +
130 if (!consumes.isEmpty()) {
131 line.append(QLatin1String(
" :") + consumes);
133 rows.append({QString(), line});
135 rows[0][0] = QLatin1Char(
'/') + parts.join(QLatin1Char(
'/'));
139 QTextStream out(&buffer, QTextStream::WriteOnly);
141 if (!paths.isEmpty()) {
142 out << Utils::buildTable(paths,
143 {QLatin1String(
"Path Spec"), QLatin1String(
"Private")},
144 QLatin1String(
"Loaded Chained actions:"));
147 if (!unattachedTable.isEmpty()) {
148 out << Utils::buildTable(unattachedTable,
149 {QLatin1String(
"Private"), QLatin1String(
"Missing parent")},
150 QLatin1String(
"Unattached Chained actions:"));
159 if (!args.isEmpty()) {
167 const BestActionMatch ret =
168 d->recurseMatch(args.size(), u
"/"_s, path.mid(1).toString().split(QLatin1Char(
'/')));
170 if (ret.isNull || chain.isEmpty()) {
174 QStringList decodedArgs;
175 const QStringList parts = ret.parts;
176 for (
const QString &arg : parts) {
178 decodedArgs.append(Utils::decodePercentEncoding(&aux));
196 const QStringList chainedList = attributes.values(QLatin1String(
"Chained"));
197 if (chainedList.isEmpty()) {
201 if (chainedList.size() > 1) {
202 qCCritical(CUTELYST_DISPATCHER_CHAINED)
203 <<
"Multiple Chained attributes not supported registering" << action->
reverse();
207 const QString &chainedTo = chainedList.first();
208 if (chainedTo == u
'/' + action->
name()) {
209 qCCritical(CUTELYST_DISPATCHER_CHAINED)
210 <<
"Actions cannot chain to themselves registering /" << action->
name();
214 const QStringList pathPart = attributes.values(QLatin1String(
"PathPart"));
216 QString part = action->
name();
218 if (pathPart.size() == 1 && !pathPart[0].isEmpty()) {
220 }
else if (pathPart.size() > 1) {
221 qCCritical(CUTELYST_DISPATCHER_CHAINED)
222 <<
"Multiple PathPart attributes not supported registering" << action->
reverse();
226 if (part.startsWith(QLatin1Char(
'/'))) {
227 qCCritical(CUTELYST_DISPATCHER_CHAINED)
228 <<
"Absolute parameters to PathPart not allowed registering" << action->
reverse();
232 attributes.replace(QStringLiteral(
"PathPart"), part);
235 auto &childrenOf = d->childrenOf[chainedTo][part];
236 childrenOf.insert(childrenOf.begin(), action);
238 d->actions[QLatin1Char(
'/') + action->
reverse()] = action;
240 if (!d->checkArgsAttr(action, QLatin1String(
"Args")) ||
241 !d->checkArgsAttr(action, QLatin1String(
"CaptureArgs"))) {
245 if (attributes.contains(QLatin1String(
"Args")) &&
246 attributes.contains(QLatin1String(
"CaptureArgs"))) {
247 qCCritical(CUTELYST_DISPATCHER_CHAINED)
248 <<
"Combining Args and CaptureArgs attributes not supported registering"
253 if (!attributes.contains(QLatin1String(
"CaptureArgs"))) {
254 d->endPoints.push_back(action);
266 if (!(attributes.contains(QStringLiteral(
"Chained")) &&
267 !attributes.contains(QStringLiteral(
"CaptureArgs")))) {
268 qCWarning(CUTELYST_DISPATCHER_CHAINED)
269 <<
"uriForAction: action is not an end point" << action;
274 QStringList localCaptures = captures;
279 if (curr_attributes.contains(QStringLiteral(
"CaptureArgs"))) {
282 qCWarning(CUTELYST_DISPATCHER_CHAINED)
288 parts = localCaptures.mid(localCaptures.size() - curr->
numberOfCaptures()) + parts;
289 localCaptures = localCaptures.mid(0, localCaptures.size() - curr->
numberOfCaptures());
292 const QString pp = curr_attributes.value(QStringLiteral(
"PathPart"));
297 parent = curr_attributes.value(QStringLiteral(
"Chained"));
298 curr = d->actions.value(parent);
301 if (parent.compare(u
"/") != 0) {
303 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: dangling action" << parent;
307 if (!localCaptures.isEmpty()) {
309 qCWarning(CUTELYST_DISPATCHER_CHAINED)
310 <<
"uriForAction: too many captures" << localCaptures;
314 ret = QLatin1Char(
'/') + parts.join(QLatin1Char(
'/'));
323 if (qobject_cast<ActionChain *>(action)) {
328 if (!action->
attributes().contains(QStringLiteral(
"Chained"))) {
337 const QString parent = curr->
attribute(QStringLiteral(
"Chained"));
338 curr = d->actions.value(parent);
348 if (d->actions.isEmpty()) {
357BestActionMatch DispatchTypeChainedPrivate::recurseMatch(
int reqArgsSize,
358 const QString &parent,
359 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) {
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 =
409 recurseMatch(reqArgsSize, QLatin1Char(
'/') + action->reverse(), localParts);
415 const QStringList actionCaptures = ret.captures;
416 const QStringList actionParts = ret.parts;
417 int bestActionParts = bestAction.parts.size();
419 if (!actions.isEmpty() &&
420 (bestAction.isNull || actionParts.size() < bestActionParts ||
421 (actionParts.size() == bestActionParts &&
422 actionCaptures.size() < bestAction.captures.size() &&
423 ret.n_pathParts > bestAction.n_pathParts))) {
424 actions.prepend(action);
426 attributes.value(QStringLiteral(
"PathPart")).count(QLatin1Char(
'/')) + 1;
427 bestAction.actions = actions;
428 bestAction.captures = captures + actionCaptures;
429 bestAction.parts = actionParts;
430 bestAction.n_pathParts = pathparts + ret.n_pathParts;
431 bestAction.isNull =
false;
434 if (!action->match(reqArgsSize + parts.size())) {
438 const QString argsAttr = attributes.value(QStringLiteral(
"Args"));
439 const int pathparts =
440 attributes.value(QStringLiteral(
"PathPart")).count(QLatin1Char(
'/')) + 1;
448 if (bestAction.isNull || parts.size() < bestAction.parts.size() ||
449 (parts.isEmpty() && !argsAttr.isEmpty() && action->numberOfArgs() == 0)) {
450 bestAction.actions = {action};
451 bestAction.captures = QStringList();
452 bestAction.parts = parts;
453 bestAction.n_pathParts = pathparts;
454 bestAction.isNull =
false;
463bool DispatchTypeChainedPrivate::checkArgsAttr(
Action *action,
const QString &name)
const
466 if (!attributes.contains(name)) {
470 const QStringList values = attributes.values(name);
471 if (values.size() > 1) {
472 qCCritical(CUTELYST_DISPATCHER_CHAINED)
473 <<
"Multiple" << name <<
"attributes not supported registering" << action->
reverse();
477 QString args = values[0];
479 if (!args.isEmpty() && args.toInt(&ok) < 0 && !ok) {
480 qCCritical(CUTELYST_DISPATCHER_CHAINED)
481 <<
"Invalid" << name <<
"(" << args <<
") for action" << action->
reverse() <<
"(use '"
482 << name <<
"' or '" << name <<
"(<number>)')";
489QString DispatchTypeChainedPrivate::listExtraHttpMethods(
Action *action)
493 if (attributes.contains(QLatin1String(
"HTTP_METHODS"))) {
494 const QStringList extra = attributes.values(QLatin1String(
"HTTP_METHODS"));
495 ret = extra.join(QLatin1String(
", "));
500QString DispatchTypeChainedPrivate::listExtraConsumes(
Action *action)
504 if (attributes.contains(QLatin1String(
"CONSUMES"))) {
505 const QStringList extra = attributes.values(QLatin1String(
"CONSUMES"));
506 ret = extra.join(QLatin1String(
", "));
511#include "moc_dispatchtypechained.cpp"
Holds a chain of Cutelyst actions.
This class represents a Cutelyst Action.
void setAttributes(const ParamsMultiMap &attributes)
virtual qint8 numberOfCaptures() const
ParamsMultiMap attributes() const noexcept
QString attribute(const QString &name, const QString &defaultValue={}) const
QString reverse() const noexcept
QString name() const noexcept
Describes a chained dispatch type.
MatchType match(Context *c, QStringView path, const QStringList &args) const override
QString uriForAction(Action *action, const QStringList &captures) const override
QByteArray list() const override
bool registerAction(Action *action) override
Action * expandAction(const Context *c, Action *action) const final
~DispatchTypeChained() override
DispatchTypeChained(QObject *parent=nullptr)
Abstract class to described a dispatch type.
void setupMatchedAction(Context *c, Action *action) const
void setCaptures(const QStringList &captures)
void setArguments(const QStringList &arguments)
void setMatch(const QString &match)
QMultiMap< QString, QString > ParamsMultiMap
The Cutelyst namespace holds all public Cutelyst API.
QVector< Action * > ActionList