cutelyst 5.0.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
dispatcher.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "action.h"
6#include "application.h"
7#include "common.h"
8#include "context.h"
9#include "controller.h"
10#include "controller_p.h"
11#include "dispatcher_p.h"
12#include "dispatchtypechained.h"
13#include "dispatchtypepath.h"
14#include "engine.h"
15#include "request_p.h"
16#include "utils.h"
17
18#include <QMetaMethod>
19#include <QUrl>
20
21using namespace Cutelyst;
22using namespace Qt::Literals::StringLiterals;
23
25 : QObject(parent)
26 , d_ptr(new DispatcherPrivate(this))
27{
30}
31
33{
34 delete d_ptr;
35}
36
38 const QVector<Cutelyst::DispatchType *> &dispatchers,
39 bool printActions)
40{
41 Q_D(Dispatcher);
42
43 d->dispatchers = dispatchers;
44
45 ActionList registeredActions;
47 bool instanceUsed = false;
48 const auto actions = controller->actions();
49 for (Action *action : actions) {
50 bool registered = false;
51 if (!d->actions.contains(action->reverse())) {
52 if (!action->attributes().contains(QStringLiteral("Private"))) {
53 // Register the action with each dispatcher
55 if (dispatch->registerAction(action)) {
56 registered = true;
57 }
58 }
59 } else {
60 // We register private actions
61 registered = true;
62 }
63 }
64
65 // The Begin, Auto, End actions are not
66 // registered by Dispatchers but we need them
67 // as private actions anyway
68 if (registered) {
69 const QString name = action->ns() + QLatin1Char('/') + action->name();
70 d->actions.insert(name, {name, action});
71 auto it = d->actionContainer.find(action->ns());
72 if (it != d->actionContainer.end()) {
73 it->actions << action;
74 } else {
75 d->actionContainer.insert(action->ns(), {action->ns(), {action}});
76 }
77
78 registeredActions.append(action);
79 instanceUsed = true;
80 } else {
81 qCDebug(CUTELYST_DISPATCHER)
82 << "The action" << action->name() << "of" << action->controller()->objectName()
83 << "controller was not registered in any dispatcher."
84 " If you still want to access it internally (via actionFor())"
85 " you may make it's method private.";
86 }
87 }
88
89 if (instanceUsed) {
90 d->controllers.insert(controller->objectName(), {controller->objectName(), controller});
91 }
92 }
93
94 if (printActions) {
95 d->printActions();
96 }
97
98 // Cache root actions, BEFORE the controllers set them
99 d->rootActions = d->actionContainer.value(u"").actions;
100
101 for (Controller *controller : controllers) {
102 controller->d_ptr->setupFinished();
103 }
104
105 // Unregister any dispatcher that is not in use
106 int i = 0;
107 while (i < d->dispatchers.size()) {
108 DispatchType *type = d->dispatchers.at(i);
109 if (!type->inUse()) {
110 d->dispatchers.removeAt(i);
111 continue;
112 }
113 ++i;
114 }
115
116 if (printActions) {
117 // List all public actions
118 for (DispatchType *dispatch : dispatchers) {
119 qCDebug(CUTELYST_DISPATCHER) << dispatch->list().constData();
120 }
121 }
122}
123
125{
126 Action *action = c->action();
127 if (action) {
128 return action->controller()->_DISPATCH(c);
129 } else {
130 const QString path = c->req()->path();
131 if (path.isEmpty()) {
132 //% "No default action defined."
133 c->appendError(c->qtTrId("cutelyst-dispatcher-no-default-act"));
134 } else {
135 //% "Unknown resource '%1'."
136 c->appendError(c->qtTrId("cutelyst-dispatcher-unknown-resource").arg(path));
137 }
138 }
139 return false;
140}
141
143{
144 Q_ASSERT(component);
145 // If the component was an Action
146 // the dispatch() would call c->execute
147 return c->execute(component);
148}
149
151{
152 Q_D(const Dispatcher);
153
154 Action *action = d->command2Action(c, opname, c->request()->args());
155 if (action) {
156 return action->dispatch(c);
157 }
158
159 qCCritical(CUTELYST_DISPATCHER) << "Action not found" << opname << c->request()->args();
160 return false;
161}
162
164{
165 Q_D(Dispatcher);
166
167 Request *request = c->request();
168 d->prepareAction(c, request->path());
169
170 static const bool log = CUTELYST_DISPATCHER().isDebugEnabled();
171 if (log) {
172 if (!request->match().isEmpty()) {
173 qCDebug(CUTELYST_DISPATCHER) << "Path is" << request->match();
174 }
175
176 if (!request->args().isEmpty()) {
177 qCDebug(CUTELYST_DISPATCHER) << "Arguments are" << request->args().join(u'/');
178 }
179 }
180}
181
182void DispatcherPrivate::prepareAction(Context *c, QStringView path) const
183{
184 QStringList args;
185
186 // "/foo/bar"
187 // "/foo/" skip
188 // "/foo"
189 // "/"
190 Q_FOREVER
191 {
192 // Check out the dispatch types to see if any
193 // will handle the path at this level
194 for (DispatchType *type : dispatchers) {
195 if (type->match(c, path, args) == DispatchType::ExactMatch) {
196 return;
197 }
198 }
199
200 // leave the loop if we are at the root "/"
201 if (path.length() == 1) {
202 break;
203 }
204
205 int pos = path.lastIndexOf(u'/');
206
207 args.emplaceFront(path.mid(pos + 1).toString());
208
209 if (pos == 0) {
210 path.truncate(pos + 1);
211 } else {
212 path.truncate(pos);
213 }
214 }
215}
216
218{
219 Q_D(const Dispatcher);
220
221 if (name.isEmpty()) {
222 return nullptr;
223 }
224
225 if (nameSpace.isEmpty()) {
226 const QString normName = u'/' + name;
227 return d->actions.value(normName).action;
228 }
229
230 return getActionByPath(QString{nameSpace + u'/' + name});
231}
232
234{
235 Q_D(const Dispatcher);
236
237 int slashes = path.count(u'/');
238 if (slashes == 0) {
239 return d->actions.value(QString{u'/' + path}).action;
240 } else if (path.startsWith(u'/') && slashes != 1) {
241 return d->actions.value(path.mid(1)).action;
242 }
243 return d->actions.value(path).action;
244}
245
247{
248 Q_D(const Dispatcher);
249
250 ActionList ret;
251
252 if (name.isEmpty()) {
253 return ret;
254 }
255
256 const ActionList containers = d->getContainers(nameSpace);
257 auto rIt = containers.rbegin();
258 while (rIt != containers.rend()) {
259 if ((*rIt)->name() == name) {
260 ret.append(*rIt);
261 }
262 ++rIt;
263 }
264 return ret;
265}
266
268{
269 Q_D(const Dispatcher);
270 return d->controllers.value(name).controller;
271}
272
274{
275 Q_D(const Dispatcher);
277 for (const auto &value : d->controllers) {
278 ret.append(value.controller);
279 }
280 return ret;
281}
282
283QString Dispatcher::uriForAction(Action *action, const QStringList &captures) const
284{
285 Q_D(const Dispatcher);
286 QString ret;
287 if (Q_UNLIKELY(action == nullptr)) {
288 qCCritical(CUTELYST_DISPATCHER) << "Dispatcher::uriForAction called with null action";
289 ret = u"/"_s;
290 } else {
291 for (DispatchType *dispatch : d->dispatchers) {
292 ret = dispatch->uriForAction(action, captures);
293 if (!ret.isNull()) {
294 if (ret.isEmpty()) {
295 ret = u"/"_s;
296 }
297 break;
298 }
299 }
300 }
301 return ret;
302}
303
305{
306 Q_D(const Dispatcher);
307 for (DispatchType *dispatch : d->dispatchers) {
308 Action *expandedAction = dispatch->expandAction(c, action);
309 if (expandedAction) {
310 return expandedAction;
311 }
312 }
313 return action;
314}
315
317{
318 Q_D(const Dispatcher);
319 return d->dispatchers;
320}
321
322void DispatcherPrivate::printActions() const
323{
325
326 auto keys = actions.keys();
327 std::ranges::sort(keys);
328 for (const auto &key : std::as_const(keys)) {
329 Action *action = actions.value(key).action;
330 QString path = key.toString();
331 if (!path.startsWith(u'/')) {
332 path.prepend(u'/');
333 }
334
335 QStringList row;
336 row.append(path);
337 row.append(action->className());
338 row.append(action->name());
339 table.append(row);
340 }
341
342 qCDebug(CUTELYST_DISPATCHER) << Utils::buildTable(table,
343 {QLatin1String("Private"),
344 QLatin1String("Class"),
345 QLatin1String("Method")},
346 QLatin1String("Loaded Private actions:"))
347 .constData();
348}
349
350ActionList DispatcherPrivate::getContainers(QStringView ns) const
351{
352 ActionList ret;
353
354 if (ns.compare(u"/") != 0) {
355 int pos = ns.size();
356 // qDebug() << pos << ns.mid(0, pos);
357 while (pos > 0) {
358 // qDebug() << pos << ns.mid(0, pos);
359 ret.append(actionContainer.value(ns.mid(0, pos)).actions);
360 pos = ns.lastIndexOf(u'/', pos - 1);
361 }
362 }
363 // qDebug() << actionContainer.size() << rootActions;
364 ret.append(rootActions);
365
366 return ret;
367}
368
369Action *DispatcherPrivate::command2Action(Context *c,
370 QStringView command,
371 const QStringList &args) const
372{
373 auto it = actions.constFind(command);
374 if (it != actions.constEnd()) {
375 return it.value().action;
376 }
377
378 return invokeAsPath(c, command, args);
379}
380
381Action *DispatcherPrivate::invokeAsPath(Context *c,
382 QStringView relativePath,
383 const QStringList &args) const
384{
385 Q_UNUSED(args);
386 Q_Q(const Dispatcher);
387
388 Action *ret;
389 const QString path = DispatcherPrivate::actionRel2Abs(c, relativePath);
390 QStringView pathView{path};
391
392 int pos = pathView.lastIndexOf(u'/');
393 int lastPos = pathView.size();
394 do {
395 if (pos == -1) {
396 ret = q->getAction(pathView);
397 if (ret) {
398 return ret;
399 }
400 } else {
401 const auto name = pathView.mid(pos + 1, lastPos);
402 pathView = pathView.mid(0, pos);
403 ret = q->getAction(name, pathView);
404 if (ret) {
405 return ret;
406 }
407 }
408
409 lastPos = pos;
410 pos = pathView.indexOf(u'/', pos - 1);
411 } while (pos != -1);
412
413 return nullptr;
414}
415
416QString DispatcherPrivate::actionRel2Abs(Context *c, QStringView path)
417{
418 QString ret;
419 if (path.startsWith(u'/')) {
420 ret = path.mid(1).toString();
421 return ret;
422 }
423
424 const QString ns = qobject_cast<Action *>(c->stack().constLast())->ns();
425 if (ns.isEmpty()) {
426 ret = path.toString();
427 } else {
428 ret = ns + QLatin1Char('/') + path;
429 }
430 return ret;
431}
432
433#include "moc_dispatcher.cpp"
This class represents a Cutelyst Action.
Definition action.h:35
bool dispatch(Context *c)
Definition action.h:88
QString className() const noexcept
Definition action.cpp:87
Controller * controller() const noexcept
Definition action.cpp:93
The Cutelyst Component base class.
Definition component.h:30
QString name() const noexcept
Definition component.cpp:33
The Cutelyst Context.
Definition context.h:42
QStack< Component * > stack() const noexcept
Definition context.cpp:225
Request * request
Definition context.h:71
Request * req
Definition context.h:66
QString qtTrId(const char *id, int n=-1) const
Definition context.h:657
Action * action
Definition context.h:47
bool execute(Component *code)
Definition context.cpp:424
void appendError(const QString &error)
Definition context.cpp:57
Cutelyst Controller base class.
Definition controller.h:56
ActionList actions() const noexcept
bool _DISPATCH(Context *c)
Describes a chained dispatch type.
Describes a path dispatch type.
Abstract class to described a dispatch type.
virtual bool inUse()=0
virtual QByteArray list() const =0
virtual MatchType match(Context *c, QStringView path, const QStringList &args) const =0
The Cutelyst Dispatcher.
Definition dispatcher.h:29
Action * getAction(QStringView name, QStringView nameSpace={}) const
void setupActions(const QVector< Controller * > &controllers, const QVector< DispatchType * > &dispatchers, bool printActions)
QVector< DispatchType * > dispatchers() const
QString uriForAction(Action *action, const QStringList &captures) const
ActionList getActions(QStringView name, QStringView nameSpace) const
bool forward(Context *c, Component *component)
Controller * controller(QStringView name) const
Dispatcher(QObject *parent=nullptr)
Action * getActionByPath(QStringView path) const
QList< Controller * > controllers() const
void prepareAction(Context *c)
bool dispatch(Context *c)
Action * expandAction(const Context *c, Action *action) const
A request.
Definition request.h:42
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
void append(QList::parameter_type value)
bool isEmpty() const const
QList::reverse_iterator rbegin()
QList::reverse_iterator rend()
QObject * parent() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
bool isNull() const const
qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs) const const
QString & prepend(QChar ch)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
QStringView mid(qsizetype start, qsizetype length) const const
int compare(QChar ch) const const
qsizetype count(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs) const const
qsizetype length() const const
qsizetype size() const const
bool startsWith(QChar ch) const const
QString toString() const const
void truncate(qsizetype length)