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;
46 for (Controller *controllerItem : controllers) {
47 bool instanceUsed = false;
48 const auto actions = controllerItem->actions();
49 for (Action *action : actions) {
50 bool registered = false;
51 if (!d->actions.contains(action->reverse())) {
52 if (!action->attributes().contains(u"Private"_s)) {
53 // Register the action with each dispatcher
54 for (DispatchType *dispatcher : dispatchers) {
55 if (dispatcher->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() + u'/' + 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(controllerItem->objectName(),
91 {controllerItem->objectName(), controllerItem});
92 }
93 }
94
95 if (printActions) {
96 d->printActions();
97 }
98
99 // Cache root actions, BEFORE the controllers set them
100 d->rootActions = d->actionContainer.value(u"").actions;
101
102 for (const Controller *controllerItem : controllers) {
103 controllerItem->d_ptr->setupFinished();
104 }
105
106 // Unregister any dispatcher that is not in use
107 int i = 0;
108 while (i < d->dispatchers.size()) {
109 DispatchType *type = d->dispatchers.at(i);
110 if (!type->inUse()) {
111 d->dispatchers.removeAt(i);
112 continue;
113 }
114 ++i;
115 }
116
117 if (printActions) {
118 // List all public actions
119 for (const DispatchType *dispatcher : dispatchers) {
120 qCDebug(CUTELYST_DISPATCHER) << dispatcher->list().constData();
121 }
122 }
123}
124
126{
127 const Action *action = c->action();
128 if (action) {
129 return action->controller()->_DISPATCH(c);
130 } else {
131 const QString path = c->req()->path();
132 if (path.isEmpty()) {
133 //% "No default action defined."
134 c->appendError(c->qtTrId("cutelyst-dispatcher-no-default-act"));
135 } else {
136 //% "Unknown resource '%1'."
137 c->appendError(c->qtTrId("cutelyst-dispatcher-unknown-resource").arg(path));
138 }
139 }
140 return false;
141}
142
144{
145 Q_ASSERT(component);
146 // If the component was an Action
147 // the dispatch() would call c->execute
148 return c->execute(component);
149}
150
152{
153 Q_D(const Dispatcher);
154
155 Action *action = d->command2Action(c, opname, c->request()->args());
156 if (action) {
157 return action->dispatch(c);
158 }
159
160 qCCritical(CUTELYST_DISPATCHER) << "Action not found" << opname << c->request()->args();
161 return false;
162}
163
165{
166 Q_D(Dispatcher);
167
168 const Request *request = c->request();
169 d->prepareAction(c, request->path());
170
171 static const bool log = CUTELYST_DISPATCHER().isDebugEnabled();
172 if (log) {
173 if (!request->match().isEmpty()) {
174 qCDebug(CUTELYST_DISPATCHER) << "Path is" << request->match();
175 }
176
177 if (!request->args().isEmpty()) {
178 qCDebug(CUTELYST_DISPATCHER) << "Arguments are" << request->args().join(u'/');
179 }
180 }
181}
182
183void DispatcherPrivate::prepareAction(Context *c, QStringView path) const
184{
185 QStringList args;
186
187 // "/foo/bar"
188 // "/foo/" skip
189 // "/foo"
190 // "/"
191 Q_FOREVER
192 {
193 // Check out the dispatch types to see if any
194 // will handle the path at this level
195 bool matched = std::ranges::any_of(dispatchers, [&](const DispatchType *type) {
196 return type->match(c, path, args) == DispatchType::ExactMatch;
197 });
198 if (matched) {
199 return;
200 }
201
202 // leave the loop if we are at the root "/"
203 if (path.length() == 1) {
204 break;
205 }
206
207 int pos = path.lastIndexOf(u'/');
208
209 args.emplaceFront(path.mid(pos + 1).toString());
210
211 if (pos == 0) {
212 path.truncate(pos + 1);
213 } else {
214 path.truncate(pos);
215 }
216 }
217}
218
220{
221 Q_D(const Dispatcher);
222
223 if (name.isEmpty()) {
224 return nullptr;
225 }
226
227 if (nameSpace.isEmpty()) {
228 const QString normName = u'/' + name;
229 return d->actions.value(normName).action;
230 }
231
232 return getActionByPath(QString{nameSpace + u'/' + name});
233}
234
236{
237 Q_D(const Dispatcher);
238
239 int slashes = path.count(u'/');
240 if (slashes == 0) {
241 return d->actions.value(QString{u'/' + path}).action;
242 } else if (path.startsWith(u'/') && slashes != 1) {
243 return d->actions.value(path.mid(1)).action;
244 }
245 return d->actions.value(path).action;
246}
247
249{
250 Q_D(const Dispatcher);
251
252 ActionList ret;
253
254 if (name.isEmpty()) {
255 return ret;
256 }
257
258 const ActionList containers = d->getContainers(nameSpace);
259 auto rIt = containers.rbegin();
260 while (rIt != containers.rend()) {
261 if ((*rIt)->name() == name) {
262 ret.append(*rIt);
263 }
264 ++rIt;
265 }
266 return ret;
267}
268
270{
271 Q_D(const Dispatcher);
272 return d->controllers.value(name).controller;
273}
274
276{
277 Q_D(const Dispatcher);
279 for (const auto &value : d->controllers) {
280 ret.append(value.controller);
281 }
282 return ret;
283}
284
285QString Dispatcher::uriForAction(Action *action, const QStringList &captures) const
286{
287 Q_D(const Dispatcher);
288 QString ret;
289 if (Q_UNLIKELY(action == nullptr)) {
290 qCCritical(CUTELYST_DISPATCHER) << "Dispatcher::uriForAction called with null action";
291 ret = u"/"_s;
292 } else {
293 for (const DispatchType *dispatcher : d->dispatchers) {
294 ret = dispatcher->uriForAction(action, captures);
295 if (!ret.isNull()) {
296 if (ret.isEmpty()) { // cppcheck-suppress knownConditionTrueFalse
297 ret = u"/"_s;
298 }
299 break;
300 }
301 }
302 }
303 return ret;
304}
305
307{
308 Q_D(const Dispatcher);
309 for (const DispatchType *dispatcher : d->dispatchers) {
310 Action *expandedAction = dispatcher->expandAction(c, action);
311 if (expandedAction) {
312 return expandedAction;
313 }
314 }
315 return action;
316}
317
319{
320 Q_D(const Dispatcher);
321 return d->dispatchers;
322}
323
324void DispatcherPrivate::printActions() const
325{
327
328 auto keys = actions.keys();
329 std::ranges::sort(keys);
330 for (const auto &key : std::as_const(keys)) {
331 const Action *action = actions.value(key).action;
332 QString path = key.toString();
333 if (!path.startsWith(u'/')) {
334 path.prepend(u'/');
335 }
336
337 QStringList row;
338 row.append(path);
339 row.append(action->className());
340 row.append(action->name());
341 table.append(row);
342 }
343
344 qCDebug(CUTELYST_DISPATCHER) << Utils::buildTable(table,
345 {
346 u"Private"_s,
347 u"Class"_s,
348 u"Method"_s,
349 },
350 u"Loaded Private actions:"_s)
351 .constData();
352}
353
354ActionList DispatcherPrivate::getContainers(QStringView ns) const
355{
356 ActionList ret;
357
358 if (ns.compare(u"/") != 0) {
359 int pos = ns.size();
360 // qDebug() << pos << ns.mid(0, pos);
361 while (pos > 0) {
362 // qDebug() << pos << ns.mid(0, pos);
363 ret.append(actionContainer.value(ns.mid(0, pos)).actions);
364 pos = ns.lastIndexOf(u'/', pos - 1);
365 }
366 }
367 // qDebug() << actionContainer.size() << rootActions;
368 ret.append(rootActions);
369
370 return ret;
371}
372
373Action *DispatcherPrivate::command2Action(const Context *c,
374 QStringView command,
375 const QStringList &args) const
376{
377 auto it = actions.constFind(command);
378 if (it != actions.constEnd()) {
379 return it.value().action;
380 }
381
382 return invokeAsPath(c, command, args);
383}
384
385Action *DispatcherPrivate::invokeAsPath(const Context *c,
386 QStringView relativePath,
387 const QStringList &args) const
388{
389 Q_UNUSED(args);
390 Q_Q(const Dispatcher);
391
392 Action *ret;
393 const QString path = DispatcherPrivate::actionRel2Abs(c, relativePath);
394 QStringView pathView{path};
395
396 int pos = pathView.lastIndexOf(u'/');
397 int lastPos = pathView.size();
398 do {
399 if (pos == -1) {
400 ret = q->getAction(pathView);
401 if (ret) {
402 return ret;
403 }
404 } else {
405 const auto name = pathView.mid(pos + 1, lastPos);
406 pathView = pathView.mid(0, pos);
407 ret = q->getAction(name, pathView);
408 if (ret) {
409 return ret;
410 }
411 }
412
413 lastPos = pos;
414 pos = pathView.indexOf(u'/', pos - 1);
415 } while (pos != -1);
416
417 return nullptr;
418}
419
420QString DispatcherPrivate::actionRel2Abs(const Context *c, QStringView path)
421{
422 QString ret;
423 if (path.startsWith(u'/')) {
424 ret = path.mid(1).toString();
425 return ret;
426 }
427
428 const QString ns = qobject_cast<Action *>(c->stack().constLast())->ns();
429 if (ns.isEmpty()) {
430 ret = path.toString();
431 } else {
432 ret = ns + u'/' + path;
433 }
434 return ret;
435}
436
437#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:86
Controller * controller() const noexcept
Definition action.cpp:92
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
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
QString & insert(qsizetype position, QChar ch)
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)