5 #include "dispatchtypechained_p.h"
7 #include "actionchain.h"
11 #include <QtCore/QUrl>
16 , d_ptr(new DispatchTypeChainedPrivate)
21 DispatchTypeChained::~DispatchTypeChained()
31 Actions endPoints = d->endPoints;
32 std::sort(endPoints.begin(), endPoints.end(), [](
Action *a,
Action *b) ->
bool {
33 return a->reverse() < b->reverse();
36 QVector<QStringList> paths;
37 QVector<QStringList> unattachedTable;
38 for (
Action *endPoint : endPoints) {
40 if (endPoint->numberOfArgs() == -1) {
41 parts.append(QLatin1String(
"..."));
43 for (
int i = 0; i < endPoint->numberOfArgs(); ++i) {
44 parts.append(QLatin1String(
"*"));
49 QString extra = DispatchTypeChainedPrivate::listExtraHttpMethods(endPoint);
50 QString consumes = DispatchTypeChainedPrivate::listExtraConsumes(endPoint);
52 Action *current = endPoint;
55 parts.prepend(QLatin1String(
"*"));
59 const QStringList pathParts = attributes.values(QLatin1String(
"PathPart"));
60 for (
const QString &part : pathParts) {
61 if (!part.isEmpty()) {
66 parent = attributes.value(QLatin1String(
"Chained"));
67 current = d->actions.value(parent);
69 parents.prepend(current);
73 if (parent.compare(u
"/") != 0) {
75 if (parents.isEmpty()) {
76 row.append(QLatin1Char(
'/') + endPoint->reverse());
78 row.append(QLatin1Char(
'/') + parents.first()->reverse());
81 unattachedTable.append(row);
85 QVector<QStringList> rows;
86 for (
Action *p : parents) {
87 QString name = QLatin1Char(
'/') + p->
reverse();
89 QString extraHttpMethod = DispatchTypeChainedPrivate::listExtraHttpMethods(p);
90 if (!extraHttpMethod.isEmpty()) {
91 name.prepend(extraHttpMethod + QLatin1Char(
' '));
94 const auto attributes = p->attributes();
95 auto it = attributes.constFind(QLatin1String(
"CaptureArgs"));
96 if (it != attributes.constEnd()) {
97 name.append(QLatin1String(
" (") + it.value() + QLatin1Char(
')'));
99 name.append(QLatin1String(
" (0)"));
102 QString ct = DispatchTypeChainedPrivate::listExtraConsumes(p);
104 name.append(QLatin1String(
" :") + ct);
107 if (p != parents[0]) {
108 name = QLatin1String(
"-> ") + name;
111 rows.append({QString(), name});
115 if (!rows.isEmpty()) {
116 line.append(QLatin1String(
"=> "));
118 if (!extra.isEmpty()) {
119 line.append(extra + QLatin1Char(
' '));
121 line.append(QLatin1Char(
'/') + endPoint->reverse());
122 if (endPoint->numberOfArgs() == -1) {
123 line.append(QLatin1String(
" (...)"));
125 line.append(QLatin1String(
" (") + QString::number(endPoint->numberOfArgs()) + QLatin1Char(
')'));
128 if (!consumes.isEmpty()) {
129 line.append(QLatin1String(
" :") + consumes);
131 rows.append({QString(), line});
133 rows[0][0] = QLatin1Char(
'/') + parts.join(QLatin1Char(
'/'));
137 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
138 QTextStream out(&buffer, QTextStream::WriteOnly);
140 QTextStream out(&buffer, QIODevice::WriteOnly);
143 if (!paths.isEmpty()) {
144 out << Utils::buildTable(paths, { QLatin1String(
"Path Spec"), QLatin1String(
"Private") },
145 QLatin1String(
"Loaded Chained actions:"));
148 if (!unattachedTable.isEmpty()) {
149 out << Utils::buildTable(unattachedTable, { QLatin1String(
"Private"), QLatin1String(
"Missing parent") },
150 QLatin1String(
"Unattached Chained actions:"));
158 if (!args.isEmpty()) {
164 const BestActionMatch ret = d->recurseMatch(args.size(), QStringLiteral(
"/"), path.split(QLatin1Char(
'/')));
166 if (ret.isNull || chain.isEmpty()) {
170 QStringList decodedArgs;
171 const QStringList parts = ret.parts;
172 for (
const QString &arg : parts) {
174 decodedArgs.append(Utils::decodePercentEncoding(&aux));
178 Request *request = c->request();
192 const QStringList chainedList = attributes.values(QLatin1String(
"Chained"));
193 if (chainedList.isEmpty()) {
197 if (chainedList.size() > 1) {
198 qCCritical(CUTELYST_DISPATCHER_CHAINED)
199 <<
"Multiple Chained attributes not supported registering" << action->
reverse();
203 const QString chainedTo = chainedList.first();
204 if (chainedTo == u
'/' + action->
name()) {
205 qCCritical(CUTELYST_DISPATCHER_CHAINED)
206 <<
"Actions cannot chain to themselves registering /" << action->
name();
210 const QStringList pathPart = attributes.values(QLatin1String(
"PathPart"));
212 QString part = action->
name();
214 if (pathPart.size() == 1 && !pathPart[0].isEmpty()) {
216 }
else if (pathPart.size() > 1) {
217 qCCritical(CUTELYST_DISPATCHER_CHAINED)
218 <<
"Multiple PathPart attributes not supported registering"
223 if (part.startsWith(QLatin1Char(
'/'))) {
224 qCCritical(CUTELYST_DISPATCHER_CHAINED)
225 <<
"Absolute parameters to PathPart not allowed registering"
230 attributes.replace(QStringLiteral(
"PathPart"), part);
233 auto &childrenOf = d->childrenOf[chainedTo][part];
234 childrenOf.insert(childrenOf.begin(), action);
236 d->actions[QLatin1Char(
'/') + action->
reverse()] = action;
238 if (!d->checkArgsAttr(action, QLatin1String(
"Args")) ||
239 !d->checkArgsAttr(action, QLatin1String(
"CaptureArgs"))) {
243 if (attributes.contains(QLatin1String(
"Args")) && attributes.contains(QLatin1String(
"CaptureArgs"))) {
244 qCCritical(CUTELYST_DISPATCHER_CHAINED)
245 <<
"Combining Args and CaptureArgs attributes not supported registering"
250 if (!attributes.contains(QLatin1String(
"CaptureArgs"))) {
251 d->endPoints.push_back(action);
263 if (!(attributes.contains(QStringLiteral(
"Chained")) &&
264 !attributes.contains(QStringLiteral(
"CaptureArgs")))) {
265 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: action is not an end point" << action;
270 QStringList localCaptures = captures;
275 if (curr_attributes.contains(QStringLiteral(
"CaptureArgs"))) {
278 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: not enough captures" << curr->
numberOfCaptures() << captures.size();
282 parts = localCaptures.mid(localCaptures.size() - curr->
numberOfCaptures()) + parts;
283 localCaptures = localCaptures.mid(0, localCaptures.size() - curr->
numberOfCaptures());
286 const QString pp = curr_attributes.value(QStringLiteral(
"PathPart"));
291 parent = curr_attributes.value(QStringLiteral(
"Chained"));
292 curr = d->actions.value(parent);
295 if (parent.compare(u
"/") != 0) {
297 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: dangling action" << parent;
301 if (!localCaptures.isEmpty()) {
303 qCWarning(CUTELYST_DISPATCHER_CHAINED) <<
"uriForAction: too many captures" << localCaptures;
307 ret = QLatin1Char(
'/') + parts.join(QLatin1Char(
'/'));
316 if (qobject_cast<ActionChain*>(action)) {
321 if (!action->
attributes().contains(QStringLiteral(
"Chained"))) {
330 const QString parent = curr->
attribute(QStringLiteral(
"Chained"));
331 curr = d->actions.value(parent);
341 if (d->actions.isEmpty()) {
350 BestActionMatch DispatchTypeChainedPrivate::recurseMatch(
int reqArgsSize,
const QString &parent,
const QStringList &pathParts)
const
352 BestActionMatch bestAction;
353 auto it = childrenOf.constFind(parent);
354 if (it == childrenOf.constEnd()) {
358 const StringActionsMap &children = it.value();
359 QStringList keys = children.keys();
360 std::sort(keys.begin(), keys.end(), [](
const QString &a,
const QString &b) ->
bool {
362 return b.size() < a.size();
365 for (
const QString &tryPart : keys) {
366 QStringList parts = pathParts;
367 if (!tryPart.isEmpty()) {
370 int tryPartCount = tryPart.count(QLatin1Char(
'/')) + 1;
371 const QStringList possiblePart = parts.mid(0, tryPartCount);
372 if (tryPart != possiblePart.join(QLatin1Char(
'/'))) {
375 parts = parts.mid(tryPartCount);
378 const Actions tryActions = children.value(tryPart);
379 for (
Action *action : tryActions) {
381 if (attributes.contains(QStringLiteral(
"CaptureArgs"))) {
382 const int captureCount = action->numberOfCaptures();
384 if (parts.size() < captureCount) {
389 const QStringList captures = parts.mid(0, captureCount);
392 if (!action->matchCaptures(captures.size())) {
396 const QStringList localParts = parts.mid(captureCount);
399 const BestActionMatch ret = recurseMatch(reqArgsSize, QLatin1Char(
'/') + action->reverse(), localParts);
405 const QStringList actionCaptures = ret.captures;
406 const QStringList actionParts = ret.parts;
407 int bestActionParts = bestAction.parts.size();
409 if (!actions.isEmpty() &&
410 (bestAction.isNull ||
411 actionParts.size() < bestActionParts ||
412 (actionParts.size() == bestActionParts &&
413 actionCaptures.size() < bestAction.captures.size() &&
414 ret.n_pathParts > bestAction.n_pathParts))) {
415 actions.prepend(action);
416 int pathparts = attributes.value(QStringLiteral(
"PathPart")).count(QLatin1Char(
'/')) + 1;
417 bestAction.actions = actions;
418 bestAction.captures = captures + actionCaptures;
419 bestAction.parts = actionParts;
420 bestAction.n_pathParts = pathparts + ret.n_pathParts;
421 bestAction.isNull =
false;
424 if (!action->match(reqArgsSize + parts.size())) {
428 const QString argsAttr = attributes.value(QStringLiteral(
"Args"));
429 const int pathparts = attributes.value(QStringLiteral(
"PathPart")).count(QLatin1Char(
'/')) + 1;
437 if (bestAction.isNull ||
438 parts.size() < bestAction.parts.size() ||
439 (parts.isEmpty() && !argsAttr.isEmpty() && action->numberOfArgs() == 0)) {
440 bestAction.actions = { action };
441 bestAction.captures = QStringList();
442 bestAction.parts = parts;
443 bestAction.n_pathParts = pathparts;
444 bestAction.isNull =
false;
453 bool DispatchTypeChainedPrivate::checkArgsAttr(
Action *action,
const QString &name)
const
456 if (!attributes.contains(name)) {
460 const QStringList values = attributes.values(name);
461 if (values.size() > 1) {
462 qCCritical(CUTELYST_DISPATCHER_CHAINED)
465 <<
"attributes not supported registering"
470 QString args = values[0];
472 if (!args.isEmpty() && args.toInt(&ok) < 0 && !ok) {
473 qCCritical(CUTELYST_DISPATCHER_CHAINED)
475 << name <<
"(" << args <<
") for action"
477 <<
"(use '" << name <<
"' or '" << name <<
"(<number>)')";
484 QString DispatchTypeChainedPrivate::listExtraHttpMethods(
Action *action)
488 if (attributes.contains(QLatin1String(
"HTTP_METHODS"))) {
489 const QStringList extra = attributes.values(QLatin1String(
"HTTP_METHODS"));
490 ret = extra.join(QLatin1String(
", "));
495 QString DispatchTypeChainedPrivate::listExtraConsumes(
Action *action)
499 if (attributes.contains(QLatin1String(
"CONSUMES"))) {
500 const QStringList extra = attributes.values(QLatin1String(
"CONSUMES"));
501 ret = extra.join(QLatin1String(
", "));
506 #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 noexcept
ParamsMultiMap attributes() const noexcept
QString attribute(const QString &name, const QString &defaultValue={}) const
virtual bool inUse() override
virtual QString uriForAction(Action *action, const QStringList &captures) const override
virtual QByteArray list() const override
list the registered actions To be implemented by subclasses
virtual MatchType match(Context *c, const QString &path, const QStringList &args) const override
virtual bool registerAction(Action *action) override
registerAction
Action * expandAction(const Context *c, Action *action) const final
DispatchTypeChained(QObject *parent=nullptr)
void setupMatchedAction(Context *c, Action *action) const
void setCaptures(const QStringList &captures)
void setArguments(const QStringList &arguments)
void setMatch(const QString &match)
The Cutelyst namespace holds all public Cutelyst API.
QVector< Action * > ActionList
QMultiMap< QString, QString > ParamsMultiMap