QGIS API Documentation 3.27.0-Master (c6eca784ad)
qgsserverogcapihandler.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsserverogcapihandler.cpp - QgsServerOgcApiHandler
3
4 ---------------------
5 begin : 10.7.2019
6 copyright : (C) 2019 by Alessandro Pasotti
7 email : elpaso at itopen dot it
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17#include <QDateTime>
18#include <QFileInfo>
19#include <QDir>
20#include <QDebug>
21
22#include "qgsmessagelog.h"
23#include "qgsproject.h"
24#include "qgsjsonutils.h"
25#include "qgsvectorlayer.h"
26
28#include "qgsserverapiutils.h"
29#include "qgsserverresponse.h"
30#include "qgsserverinterface.h"
31
32#include "nlohmann/json.hpp"
33#include "inja/inja.hpp"
34
35using namespace nlohmann;
36using namespace inja;
37
38
39
40QVariantMap QgsServerOgcApiHandler::values( const QgsServerApiContext &context ) const
41{
42 QVariantMap result ;
43 const auto constParameters { parameters( context ) };
44 for ( const auto &p : constParameters )
45 {
46 // value() calls the validators and throws an exception if validation fails
47 result[p.name()] = p.value( context );
48 }
49 const auto sanitizedPath { QgsServerOgcApi::sanitizeUrl( context.handlerPath( ) ).path() };
50 const auto match { path().match( sanitizedPath ) };
51 if ( match.hasMatch() )
52 {
53 const auto constNamed { path().namedCaptureGroups() };
54 // Get named path parameters
55 for ( const auto &name : constNamed )
56 {
57 if ( ! name.isEmpty() )
58 result[name] = QUrlQuery( match.captured( name ) ).toString() ;
59 }
60 }
61 return result;
62}
63
65{
66 //qDebug() << "handler destroyed";
67}
68
70{
71 const auto constContentTypes( contentTypes() );
72 return constContentTypes.size() > 0 ? constContentTypes.first() : QgsServerOgcApi::ContentType::JSON;
73}
74
75QList<QgsServerOgcApi::ContentType> QgsServerOgcApiHandler::contentTypes() const
76{
77 return mContentTypes;
78}
79
81{
82 Q_UNUSED( context )
83 throw QgsServerApiNotImplementedException( QStringLiteral( "Subclasses must implement handleRequest" ) );
84}
85
86QString QgsServerOgcApiHandler::contentTypeForAccept( const QString &accept ) const
87{
88 const auto constContentTypes( QgsServerOgcApi::contentTypeMimes() );
89 for ( auto it = constContentTypes.constBegin();
90 it != constContentTypes.constEnd(); ++it )
91 {
92 const auto constValues = it.value();
93 for ( const auto &value : constValues )
94 {
95 if ( accept.contains( value, Qt::CaseSensitivity::CaseInsensitive ) )
96 {
97 return value;
98 }
99 }
100 }
101 // Log level info because this is not completely unexpected
102 QgsMessageLog::logMessage( QStringLiteral( "Content type for accept %1 not found!" ).arg( accept ),
103 QStringLiteral( "Server" ),
104 Qgis::MessageLevel::Info );
105
106 return QString();
107}
108
109void QgsServerOgcApiHandler::write( json &data, const QgsServerApiContext &context, const json &htmlMetadata ) const
110{
111 const QgsServerOgcApi::ContentType contentType { contentTypeFromRequest( context.request() ) };
112 switch ( contentType )
113 {
114 case QgsServerOgcApi::ContentType::HTML:
115 data["handler"] = schema( context );
116 if ( ! htmlMetadata.is_null() )
117 {
118 data["metadata"] = htmlMetadata;
119 }
120 htmlDump( data, context );
121 break;
122 case QgsServerOgcApi::ContentType::GEOJSON:
123 case QgsServerOgcApi::ContentType::JSON:
124 case QgsServerOgcApi::ContentType::OPENAPI3:
125 jsonDump( data, context, QgsServerOgcApi::contentTypeMimes().value( contentType ).first() );
126 break;
127 case QgsServerOgcApi::ContentType::XML:
128 // Not handled yet
129 break;
130 }
131}
132
133void QgsServerOgcApiHandler::write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata ) const
134{
135 json j = QgsJsonUtils::jsonFromVariant( data );
136 json jm = QgsJsonUtils::jsonFromVariant( htmlMetadata );
137 QgsServerOgcApiHandler::write( j, context, jm );
138}
139
140std::string QgsServerOgcApiHandler::href( const QgsServerApiContext &context, const QString &extraPath, const QString &extension ) const
141{
142 QUrl url { context.request()->url() };
143 QString urlBasePath { context.matchedPath() };
144 const auto match { path().match( QgsServerOgcApi::sanitizeUrl( context.handlerPath( ) ).path( ) ) };
145 if ( match.captured().count() > 0 )
146 {
147 url.setPath( urlBasePath + match.captured( 0 ) );
148 }
149 else
150 {
151 url.setPath( urlBasePath );
152 }
153
154 // Remove any existing extension
155 const auto suffixLength { QFileInfo( url.path() ).suffix().length() };
156 if ( suffixLength > 0 )
157 {
158 auto path {url.path()};
159 path.truncate( path.length() - ( suffixLength + 1 ) );
160 url.setPath( path );
161 }
162
163 // Add extra path
164 url.setPath( url.path() + extraPath );
165
166 // (re-)add extension
167 // JSON is the default anyway so we don't need to add it
168 if ( ! extension.isEmpty() )
169 {
170 // Remove trailing slashes if any.
171 QString path { url.path() };
172 while ( path.endsWith( '/' ) )
173 {
174 path.chop( 1 );
175 }
176 url.setPath( path + '.' + extension );
177 }
178 return QgsServerOgcApi::sanitizeUrl( url ).toString( QUrl::FullyEncoded ).toStdString();
179
180}
181
182void QgsServerOgcApiHandler::jsonDump( json &data, const QgsServerApiContext &context, const QString &contentType ) const
183{
184 // Do not append timestamp to openapi
185 if ( ! QgsServerOgcApi::contentTypeMimes().value( QgsServerOgcApi::ContentType::OPENAPI3 ).contains( contentType, Qt::CaseSensitivity::CaseInsensitive ) )
186 {
187 QDateTime time { QDateTime::currentDateTime() };
188 time.setTimeSpec( Qt::TimeSpec::UTC );
189 data["timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString() ;
190 }
191 context.response()->setStatusCode( 200 );
192 context.response()->setHeader( QStringLiteral( "Content-Type" ), contentType );
193#ifdef QGISDEBUG
194 context.response()->write( data.dump( 2 ) );
195#else
196 context.response()->write( data.dump( ) );
197#endif
198}
199
201{
202 Q_UNUSED( context )
203 return nullptr;
204}
205
206json QgsServerOgcApiHandler::link( const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType, const QgsServerOgcApi::ContentType contentType, const std::string &title ) const
207{
208 json l
209 {
210 {
211 "href", href( context, "/",
213 },
215 { "type", QgsServerOgcApi::mimeType( contentType ) },
216 { "title", title != "" ? title : linkTitle() },
217 };
218 return l;
219}
220
222{
223 const QgsServerOgcApi::ContentType currentCt { contentTypeFromRequest( context.request() ) };
224 json links = json::array();
225 const QList<QgsServerOgcApi::ContentType> constCts { contentTypes() };
226 for ( const auto &ct : constCts )
227 {
228 links.push_back( link( context, ( ct == currentCt ? QgsServerOgcApi::Rel::self :
229 QgsServerOgcApi::Rel::alternate ), ct,
231 }
232 return links;
233}
234
236{
237 if ( ! context.project() )
238 {
239 throw QgsServerApiImproperlyConfiguredException( QStringLiteral( "Project is invalid or undefined" ) );
240 }
241 // Check collectionId
242 const QRegularExpressionMatch match { path().match( context.request()->url().path( ) ) };
243 if ( ! match.hasMatch() )
244 {
245 throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) );
246 }
247 const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) };
248 // May throw if not found
249 return layerFromCollectionId( context, collectionId );
250
251}
252
253const QString QgsServerOgcApiHandler::staticPath( const QgsServerApiContext &context ) const
254{
255 // resources/server/api + /static
256 return context.serverInterface()->serverSettings()->apiResourcesDirectory() + QStringLiteral( "/ogc/static" );
257}
258
260{
261 // resources/server/api + /ogc/templates/ + operationId + .html
262 QString path { context.serverInterface()->serverSettings()->apiResourcesDirectory() };
263 path += QLatin1String( "/ogc/templates" );
264 path += context.apiRootPath();
265 path += '/';
266 path += QString::fromStdString( operationId() );
267 path += QLatin1String( ".html" );
268 return path;
269}
270
271
272void QgsServerOgcApiHandler::htmlDump( const json &data, const QgsServerApiContext &context ) const
273{
274 context.response()->setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/html" ) );
275 auto path { templatePath( context ) };
276 if ( ! QFile::exists( path ) )
277 {
278 QgsMessageLog::logMessage( QStringLiteral( "Template not found error: %1" ).arg( path ), QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
279 throw QgsServerApiBadRequestException( QStringLiteral( "Template not found: %1" ).arg( QFileInfo( path ).fileName() ) );
280 }
281
282 QFile f( path );
283 if ( ! f.open( QFile::ReadOnly | QFile::Text ) )
284 {
285 QgsMessageLog::logMessage( QStringLiteral( "Could not open template file: %1" ).arg( path ), QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
286 throw QgsServerApiInternalServerError( QStringLiteral( "Could not open template file: %1" ).arg( QFileInfo( path ).fileName() ) );
287 }
288
289 try
290 {
291 // Get the template directory and the file name
292 QFileInfo pathInfo { path };
293 Environment env { QString( pathInfo.dir().path() + QDir::separator() ).toStdString() };
294
295 // For template debugging:
296 env.add_callback( "json_dump", 0, [ = ]( Arguments & )
297 {
298 return data.dump();
299 } );
300
301 // Path manipulation: appends a directory path to the current url
302 env.add_callback( "path_append", 1, [ = ]( Arguments & args )
303 {
304 auto url { context.request()->url() };
305 QFileInfo fi{ url.path() };
306 auto suffix { fi.suffix() };
307 auto fName { fi.filePath()};
308 if ( !suffix.isEmpty() )
309 {
310 fName.chop( suffix.length() + 1 );
311 }
312 // Chop any ending slashes
313 while ( fName.endsWith( '/' ) )
314 {
315 fName.chop( 1 );
316 }
317 fName += '/' + QString::fromStdString( args.at( 0 )->get<std::string>( ) );
318 if ( !suffix.isEmpty() )
319 {
320 fName += '.' + suffix;
321 }
322 fi.setFile( fName );
323 url.setPath( fi.filePath() );
324 return url.toString().toStdString();
325 } );
326
327 // Path manipulation: removes the specified number of directory components from the current url path
328 env.add_callback( "path_chomp", 1, [ = ]( Arguments & args )
329 {
330 QUrl url { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
331 QFileInfo fi{ url.path() };
332 auto suffix { fi.suffix() };
333 auto fName { fi.filePath()};
334 fName.chop( suffix.length() + 1 );
335 // Chomp last segment
336 fName = fName.replace( QRegularExpression( R"raw(\/[^/]+$)raw" ), QString() );
337 if ( !suffix.isEmpty() )
338 {
339 fName += '.' + suffix;
340 }
341 fi.setFile( fName );
342 url.setPath( fi.filePath() );
343 return url.toString().toStdString();
344 } );
345
346 // Returns filtered links from a link list
347 // links_filter( <links>, <key>, <value> )
348 env.add_callback( "links_filter", 3, [ = ]( Arguments & args )
349 {
350 json links = args.at( 0 )->get<json>( );
351 if ( ! links.is_array() )
352 {
353 links = json::array();
354 }
355 std::string key { args.at( 1 )->get<std::string>( ) };
356 std::string value { args.at( 2 )->get<std::string>( ) };
357 json result = json::array();
358 for ( const auto &l : links )
359 {
360 if ( l[key] == value )
361 {
362 result.push_back( l );
363 }
364 }
365 return result;
366 } );
367
368 // Returns a short name from content types
369 env.add_callback( "content_type_name", 1, [ = ]( Arguments & args )
370 {
371 const QgsServerOgcApi::ContentType ct { QgsServerOgcApi::contenTypeFromExtension( args.at( 0 )->get<std::string>( ) ) };
373 } );
374
375 // Replace newlines with <br>
376 env.add_callback( "nl2br", 1, [ = ]( Arguments & args )
377 {
378 QString text { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
379 return text.replace( '\n', QLatin1String( "<br>" ) ).toStdString();
380 } );
381
382
383 // Returns a list of parameter component data from components -> parameters by ref name
384 // parameter( <ref object> )
385 env.add_callback( "component_parameter", 1, [ = ]( Arguments & args )
386 {
387 json ret = json::array();
388 json ref = args.at( 0 )->get<json>( );
389 if ( ! ref.is_object() )
390 {
391 return ret;
392 }
393 try
394 {
395 QString name = QString::fromStdString( ref["$ref"] );
396 name = name.split( '/' ).last();
397 ret.push_back( data["components"]["parameters"][name.toStdString()] );
398 }
399 catch ( std::exception & )
400 {
401 // Do nothing
402 }
403 return ret;
404 } );
405
406
407 // Static: returns the full URL to the specified static <path>
408 env.add_callback( "static", 1, [ = ]( Arguments & args )
409 {
410 auto asset( args.at( 0 )->get<std::string>( ) );
411 QString matchedPath { context.matchedPath() };
412 // If its the root path '/' strip it!
413 if ( matchedPath == '/' )
414 {
415 matchedPath.clear();
416 }
417 return matchedPath.toStdString() + "/static/" + asset;
418 } );
419
420
421 // Returns true if a string begins with the provided string prefix, false otherwise
422 env.add_callback( "starts_with", 2, [ ]( Arguments & args )
423 {
424 return string_view::starts_with( args.at( 0 )->get<std::string_view>( ), args.at( 1 )->get<std::string_view>( ) );
425 } );
426
427 context.response()->write( env.render_file( pathInfo.fileName().toStdString(), data ) );
428 }
429 catch ( std::exception &e )
430 {
431 QgsMessageLog::logMessage( QStringLiteral( "Error parsing template file: %1 - %2" ).arg( path, e.what() ), QStringLiteral( "Server" ), Qgis::MessageLevel::Critical );
432 throw QgsServerApiInternalServerError( QStringLiteral( "Error parsing template file: %1" ).arg( e.what() ) );
433 }
434}
435
437{
438 // Fallback to default
440 bool found { false };
441 // First file extension ...
442 const QString extension { QFileInfo( request->url().path() ).suffix().toUpper() };
443 if ( ! extension.isEmpty() )
444 {
445 static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
446 bool ok { false };
447 const int ct { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
448 if ( ok )
449 {
450 result = static_cast<QgsServerOgcApi::ContentType>( ct );
451 found = true;
452 }
453 else
454 {
455 QgsMessageLog::logMessage( QStringLiteral( "The client requested an unsupported extension: %1" ).arg( extension ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
456 }
457 }
458 // ... then "Accept"
459 const QString accept { request->header( QStringLiteral( "Accept" ) ) };
460 if ( ! found && ! accept.isEmpty() )
461 {
462 const QString ctFromAccept { contentTypeForAccept( accept ) };
463 if ( ! ctFromAccept.isEmpty() )
464 {
465 const auto constContentTypes( QgsServerOgcApi::contentTypeMimes() );
466 auto it = constContentTypes.constBegin();
467 while ( ! found && it != constContentTypes.constEnd() )
468 {
469 int idx = it.value().indexOf( ctFromAccept );
470 if ( idx >= 0 )
471 {
472 found = true;
473 result = it.key();
474 }
475 it++;
476 }
477 }
478 else
479 {
480 QgsMessageLog::logMessage( QStringLiteral( "The client requested an unsupported content type in Accept header: %1" ).arg( accept ), QStringLiteral( "Server" ), Qgis::MessageLevel::Warning );
481 }
482 }
483 // Validation: check if the requested content type (or an alias) is supported by the handler
484 if ( ! contentTypes().contains( result ) )
485 {
486 // Check aliases
487 bool found { false };
488 if ( QgsServerOgcApi::contentTypeAliases().contains( result ) )
489 {
490 const QList<QgsServerOgcApi::ContentType> constCt { contentTypes() };
491 for ( const auto &ct : constCt )
492 {
493 if ( QgsServerOgcApi::contentTypeAliases()[result].contains( ct ) )
494 {
495 result = ct;
496 found = true;
497 break;
498 }
499 }
500 }
501
502 if ( ! found )
503 {
504 QgsMessageLog::logMessage( QStringLiteral( "Unsupported Content-Type: %1" ).arg( QgsServerOgcApi::contentTypeToString( result ) ), QStringLiteral( "Server" ), Qgis::MessageLevel::Info );
505 throw QgsServerApiBadRequestException( QStringLiteral( "Unsupported Content-Type: %1" ).arg( QgsServerOgcApi::contentTypeToString( result ) ) );
506 }
507 }
508 return result;
509}
510
511QString QgsServerOgcApiHandler::parentLink( const QUrl &url, int levels )
512{
513 QString path { url.path() };
514 const QFileInfo fi { path };
515 const QString suffix { fi.suffix() };
516 if ( ! suffix.isEmpty() )
517 {
518 path.chop( suffix.length() + 1 );
519 }
520 while ( path.endsWith( '/' ) )
521 {
522 path.chop( 1 );
523 }
524 QRegularExpression re( R"raw(\/[^/]+$)raw" );
525 for ( int i = 0; i < levels ; i++ )
526 {
527 path = path.replace( re, QString() );
528 }
529 QUrl result( url );
530 QUrlQuery query( result );
531 QList<QPair<QString, QString> > qi;
532 const auto constItems { query.queryItems( ) };
533 for ( const auto &i : constItems )
534 {
535 if ( i.first.compare( QStringLiteral( "MAP" ), Qt::CaseSensitivity::CaseInsensitive ) == 0 )
536 {
537 qi.push_back( i );
538 }
539 }
540 // Make sure the parent link ends with a slash
541 if ( ! path.endsWith( '/' ) )
542 {
543 path.append( '/' );
544 }
545 QUrlQuery resultQuery;
546 resultQuery.setQueryItems( qi );
547 result.setQuery( resultQuery );
548 result.setPath( path );
549 return result.toString();
550}
551
553{
554 const auto mapLayers { context.project()->mapLayersByShortName<QgsVectorLayer *>( collectionId ) };
555 if ( mapLayers.count() != 1 )
556 {
557 throw QgsServerApiNotFoundError( QStringLiteral( "Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) );
558 }
559 return mapLayers.first();
560}
561
563{
564 static json defRes =
565 {
566 { "description", "An error occurred." },
567 {
568 "content", {
569 {
570 "application/json", {
571 {
572 "schema", {
573 { "$ref", "#/components/schemas/exception" }
574 }
575 }
576 }
577 },
578 {
579 "text/html", {
580 {
581 "schema", {
582 { "type", "string" }
583 }
584 }
585 }
586 }
587 }
588 }
589 };
590 return defRes;
591}
592
594{
596}
597
598void QgsServerOgcApiHandler::setContentTypesInt( const QList<int> &contentTypes )
599{
600 mContentTypes.clear();
601 for ( const int &i : std::as_const( contentTypes ) )
602 {
603 mContentTypes.push_back( static_cast<QgsServerOgcApi::ContentType>( i ) );
604 }
605}
606
607void QgsServerOgcApiHandler::setContentTypes( const QList<QgsServerOgcApi::ContentType> &contentTypes )
608{
609 mContentTypes = contentTypes;
610}
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QList< QgsMapLayer * > mapLayersByShortName(const QString &shortName) const
Retrieves a list of matching registered layers by layer shortName.
Bad request error API exception.
The QgsServerApiContext class encapsulates the resources for a particular client request: the request...
const QgsProject * project() const
Returns the (possibly NULL) project.
QgsServerResponse * response() const
Returns the server response object.
QString handlerPath() const
Returns the handler component of the URL path, i.e.
const QgsServerRequest * request() const
Returns the server request object.
QgsServerInterface * serverInterface() const
Returns the server interface.
QString apiRootPath() const
Returns the API root path.
const QString matchedPath() const
Returns the initial part of the incoming request URL path that matches the API root path.
configuration error on the server prevents to serve the request, which would be valid otherwise.
Internal server error API exception.
Not found error API exception.
this method is not yet implemented
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
std::string href(const QgsServerApiContext &context, const QString &extraPath=QString(), const QString &extension=QString()) const
Returns an URL to self, to be used for links to the current resources and as a base for constructing ...
virtual const QString templatePath(const QgsServerApiContext &context) const
Returns the HTML template path for the handler in the given context.
virtual const QString staticPath(const QgsServerApiContext &context) const
Returns the absolute path to the base directory where static resources for this handler are stored in...
virtual void handleRequest(const QgsServerApiContext &context) const SIP_THROW(QgsServerApiBadRequestException)
Handles the request within its context.
json jsonTags() const
Returns tags as JSON.
void htmlDump(const json &data, const QgsServerApiContext &context) const
Writes data as HTML to the response stream in context using a template.
virtual QgsServerOgcApi::ContentType defaultContentType() const
Returns the default response content type in case the client did not specifically ask for any particu...
QgsServerOgcApi::ContentType contentTypeFromRequest(const QgsServerRequest *request) const
Returns the content type from the request.
virtual QgsServerOgcApi::Rel linkType() const =0
Main role for the resource link.
virtual json schema(const QgsServerApiContext &context) const
Returns handler information from the context for the OPENAPI description (id, description and other m...
virtual QStringList tags() const
Tags.
void setContentTypes(const QList< QgsServerOgcApi::ContentType > &contentTypes)
Set the content types to contentTypes.
void write(json &data, const QgsServerApiContext &context, const json &htmlMetadata=nullptr) const
Writes data to the context response stream, content-type is calculated from the context request,...
QList< QgsServerOgcApi::ContentType > contentTypes() const
Returns the list of content types this handler can serve, default to JSON and HTML.
virtual std::string linkTitle() const =0
Title for the handler link.
virtual QList< QgsServerQueryStringParameter > parameters(const QgsServerApiContext &context) const
Returns a list of query string parameters.
virtual std::string operationId() const =0
Returns the operation id for template file names and other internal references.
virtual QVariantMap values(const QgsServerApiContext &context) const SIP_THROW(QgsServerApiBadRequestException)
Analyzes the incoming request context and returns the validated parameter map, throws QgsServerApiBad...
static json defaultResponse()
Returns the defaultResponse as JSON.
void jsonDump(json &data, const QgsServerApiContext &context, const QString &contentType=QStringLiteral("application/json")) const
Writes data to the context response stream as JSON (indented if debug is active), an optional content...
QgsVectorLayer * layerFromContext(const QgsServerApiContext &context) const
Returns a vector layer instance from the "collectionId" parameter of the path in the given context,...
QString contentTypeForAccept(const QString &accept) const
Looks for the first ContentType match in the accept header and returns its mime type,...
json link(const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType=QgsServerOgcApi::Rel::self, const QgsServerOgcApi::ContentType contentType=QgsServerOgcApi::ContentType::JSON, const std::string &title="") const
Builds and returns a link to the resource.
json links(const QgsServerApiContext &context) const
Returns all the links for the given request context.
static QString parentLink(const QUrl &url, int levels=1)
Returns a link to the parent page up to levels in the HTML hierarchy from the given url,...
virtual QRegularExpression path() const =0
URL pattern for this handler, named capture group are automatically extracted and returned by values(...
void setContentTypesInt(const QList< int > &contentTypes)
Set the content types to contentTypes.
static QgsVectorLayer * layerFromCollectionId(const QgsServerApiContext &context, const QString &collectionId)
Returns a vector layer from the collectionId in the given context.
static QUrl sanitizeUrl(const QUrl &url)
Returns a sanitized url with extra slashes removed and the path URL component that always starts with...
static QString contentTypeToExtension(const QgsServerOgcApi::ContentType &ct)
Returns the file extension for a ct (Content-Type).
static const QMap< QgsServerOgcApi::ContentType, QStringList > contentTypeMimes()
Returns a map of contentType => list of mime types.
static QgsServerOgcApi::ContentType contenTypeFromExtension(const std::string &extension)
Returns the Content-Type value corresponding to extension.
ContentType
Media types used for content negotiation, insert more specific first.
static QString contentTypeToString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
Rel
Rel link types.
static std::string contentTypeToStdString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
static std::string mimeType(const QgsServerOgcApi::ContentType &contentType)
Returns the mime-type for the contentType or an empty string if not found.
static std::string relToString(const QgsServerOgcApi::Rel &rel)
Returns the string representation of rel attribute.
static const QHash< QgsServerOgcApi::ContentType, QList< QgsServerOgcApi::ContentType > > contentTypeAliases()
Returns contentType specializations (e.g.
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
virtual QString header(const QString &name) const
Returns the header value.
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device.
virtual void setHeader(const QString &key, const QString &value)=0
Set Header entry Add Header entry to the response Note that it is usually an error to set Header afte...
virtual void setStatusCode(int code)=0
Set the http status code.
QString apiResourcesDirectory() const
Returns the server-wide base directory where HTML templates and static assets (e.g.
Represents a vector layer which manages a vector based data sets.