QGIS API Documentation 4.3.0-Master (bed6b413c04)
Loading...
Searching...
No Matches
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
18
19#include <nlohmann/json.hpp>
20
21#include "inja/inja.hpp"
22#include "qgsjsonutils.h"
23#include "qgsmessagelog.h"
24#include "qgsproject.h"
25#include "qgsserverapiutils.h"
26#include "qgsserverinterface.h"
27#include "qgsserverresponse.h"
28#include "qgsvectorlayer.h"
29
30#include <QDateTime>
31#include <QDebug>
32#include <QDir>
33#include <QFileInfo>
34#include <QString>
35
36using namespace Qt::StringLiterals;
37
38using namespace nlohmann;
39using namespace inja;
40
41
42QVariantMap QgsServerOgcApiHandler::values( const QgsServerApiContext &context ) const
43{
44 QVariantMap result;
45 const auto constParameters { parameters( context ) };
46 for ( const auto &p : constParameters )
47 {
48 // value() calls the validators and throws an exception if validation fails
49 result[p.name()] = p.value( context );
50 }
51 const auto sanitizedPath { QgsServerOgcApi::sanitizeUrl( context.handlerPath() ).path() };
52 const auto match { path().match( sanitizedPath ) };
53 if ( match.hasMatch() )
54 {
55 const auto constNamed { path().namedCaptureGroups() };
56 // Get named path parameters
57 for ( const auto &name : constNamed )
58 {
59 if ( !name.isEmpty() )
60 result[name] = QUrlQuery( match.captured( name ) ).toString();
61 }
62 }
63 return result;
64}
65
67{
68 //qDebug() << "handler destroyed";
69}
70
72{
73 const auto constContentTypes( contentTypes() );
74 return constContentTypes.size() > 0 ? constContentTypes.first() : QgsServerOgcApi::ContentType::JSON;
75}
76
77QList<QgsServerOgcApi::ContentType> QgsServerOgcApiHandler::contentTypes() const
78{
79 return mContentTypes;
80}
81
83{
84 Q_UNUSED( context )
85 throw QgsServerApiNotImplementedException( u"Subclasses must implement handleRequest"_s );
86}
87
88QString QgsServerOgcApiHandler::contentTypeForAccept( const QString &accept ) const
89{
90 const auto constContentTypes( QgsServerOgcApi::contentTypeMimes() );
91 for ( auto it = constContentTypes.constBegin(); it != constContentTypes.constEnd(); ++it )
92 {
93 const auto constValues = it.value();
94 for ( const auto &value : constValues )
95 {
96 if ( accept.contains( value, Qt::CaseSensitivity::CaseInsensitive ) )
97 {
98 return value;
99 }
100 }
101 }
102 // Log level info because this is not completely unexpected
103 QgsMessageLog::logMessage( u"Content type for accept %1 not found!"_s.arg( accept ), u"Server"_s, Qgis::MessageLevel::Info );
104
105 return QString();
106}
107
108void QgsServerOgcApiHandler::write( json &data, const QgsServerApiContext &context, const json &htmlMetadata ) const
109{
110 const QgsServerOgcApi::ContentType contentType { contentTypeFromRequest( context.request() ) };
111 switch ( contentType )
112 {
114 data["handler"] = schema( context );
115 if ( !htmlMetadata.is_null() )
116 {
117 data["metadata"] = htmlMetadata;
118 }
119 htmlDump( data, context );
120 break;
125 jsonDump( data, context, QgsServerOgcApi::contentTypeMimes().value( contentType ).first() );
126 break;
128 // Not handled yet
129 break;
131 // Handled separately in the handler if supported, so do nothing here
132 break;
133 }
134}
135
136void QgsServerOgcApiHandler::write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata ) const
137{
138 json j = QgsJsonUtils::jsonFromVariant( data );
139 json jm = QgsJsonUtils::jsonFromVariant( htmlMetadata );
140 QgsServerOgcApiHandler::write( j, context, jm );
141}
142
143std::string QgsServerOgcApiHandler::href( const QgsServerApiContext &context, const QString &extraPath, const QString &extension ) const
144{
145 QUrl url { context.request()->url() };
146 QString urlBasePath { context.matchedPath() };
147 const auto match { path().match( QgsServerOgcApi::sanitizeUrl( context.handlerPath() ).path() ) };
148 if ( match.captured().count() > 0 )
149 {
150 url.setPath( urlBasePath + match.captured( 0 ) );
151 }
152 else
153 {
154 url.setPath( urlBasePath );
155 }
156
157 // Remove any existing extension
158 const auto suffixLength { QFileInfo( url.path() ).suffix().length() };
159 if ( suffixLength > 0 )
160 {
161 auto path { url.path() };
162 path.truncate( path.length() - ( suffixLength + 1 ) );
163 url.setPath( path );
164 }
165
166 // Add extra path
167 url.setPath( url.path() + extraPath );
168
169 // (re-)add extension
170 // JSON is the default anyway so we don't need to add it
171 if ( !extension.isEmpty() )
172 {
173 // Remove trailing slashes if any.
174 QString path { url.path() };
175 while ( path.endsWith( '/' ) )
176 {
177 path.chop( 1 );
178 }
179 url.setPath( path + '.' + extension );
180 }
181 return QgsServerOgcApi::sanitizeUrl( url ).toString( QUrl::FullyEncoded ).toStdString();
182}
183
184void QgsServerOgcApiHandler::jsonDump( json &data, const QgsServerApiContext &context, const QString &contentType ) const
185{
186 // Do not append timestamp to openapi
187 if ( !QgsServerOgcApi::contentTypeMimes().value( QgsServerOgcApi::ContentType::OPENAPI3 ).contains( contentType, Qt::CaseSensitivity::CaseInsensitive ) )
188 {
189 QDateTime time { QDateTime::currentDateTime() };
190 time.setTimeSpec( Qt::TimeSpec::UTC );
191 data["timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString();
192 }
193 context.response()->setStatusCode( 200 );
194 context.response()->setHeader( u"Content-Type"_s, contentType );
195#ifdef QGISDEBUG
196 context.response()->write( data.dump( 2 ) );
197#else
198 context.response()->write( data.dump() );
199#endif
200}
201
203{
204 Q_UNUSED( context )
205 return nullptr;
206}
207
208json QgsServerOgcApiHandler::link( const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType, const QgsServerOgcApi::ContentType contentType, const std::string &title ) const
209{
210 json l {
211 { "href", href( context, "/", QgsServerOgcApi::contentTypeToExtension( contentType ) ) },
213 { "type", QgsServerOgcApi::mimeType( contentType ) },
214 { "title", title != "" ? title : linkTitle() },
215 };
216 return l;
217}
218
220 const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType, const QgsServerOgcApi::ContentType contentType, const QgsServerOgcApi::Profile &profile, const QString &title
221) const
222{
223 const QString profileStr { profile != QgsServerOgcApi::Profile::Unset ? QgsServerOgcApi::profileToString( profile ) : QString() };
224 QString hrefStr = QString::fromStdString( href( context, "/", QgsServerOgcApi::contentTypeToExtension( contentType ) ) );
225
226 if ( !profileStr.isEmpty() )
227 {
228 hrefStr += "&profile="_L1 + profileStr;
229 }
230
231 QString titleStr = !title.isEmpty() ? title : QString::fromStdString( linkTitle() );
232 titleStr.replace( '\\', "\\\\"_L1 );
233 titleStr.replace( '"', "\\\""_L1 );
234
235 QString linkStr = u"<%1>; rel=\"%2\"; title=\"%3\"; type=\"%4\""_s.arg(
236 QString::fromStdString( href( context, "/", QgsServerOgcApi::contentTypeToExtension( contentType ) ) ),
237 QString::fromStdString( QgsServerOgcApi::relToString( linkType ) ),
238 titleStr,
239 QString::fromStdString( QgsServerOgcApi::mimeType( contentType ) )
240 );
241
242 if ( !profileStr.isEmpty() )
243 {
244 linkStr += u"; profile=\"%1\""_s.arg( profileStr );
245 }
246
247 return linkStr;
248}
249
251{
252 const QgsServerOgcApi::ContentType currentCt { contentTypeFromRequest( context.request() ) };
253 json links = json::array();
254 const QList<QgsServerOgcApi::ContentType> constCts { contentTypes() };
255 for ( const auto &ct : constCts )
256 {
257 links.push_back( link( context, ( ct == currentCt ? QgsServerOgcApi::Rel::self : QgsServerOgcApi::Rel::alternate ), ct, linkTitle() + " as " + QgsServerOgcApi::contentTypeToStdString( ct ) ) );
258 }
259 return links;
260}
261
263{
264 if ( !context.project() )
265 {
266 throw QgsServerApiImproperlyConfiguredException( u"Project is invalid or undefined"_s );
267 }
268 // Check collectionId
269 const QRegularExpressionMatch match { path().match( context.request()->url().path() ) };
270 if ( !match.hasMatch() )
271 {
272 throw QgsServerApiNotFoundError( u"Collection was not found"_s );
273 }
274 const QString collectionId { match.captured( u"collectionId"_s ) };
275 // May throw if not found
276 return layerFromCollectionId( context, collectionId );
277}
278
279const QString QgsServerOgcApiHandler::staticPath( const QgsServerApiContext &context ) const
280{
281 // resources/server/api + /static
282 return context.serverInterface()->serverSettings()->apiResourcesDirectory() + u"/ogc/static"_s;
283}
284
286{
287 // resources/server/api + /ogc/templates/ + apiRootPath() + / + operationId() + .html
288 QString path { context.serverInterface()->serverSettings()->apiResourcesDirectory() };
289 path += "/ogc/templates"_L1;
290 path += context.apiRootPath();
291 path += '/';
292 path += QString::fromStdString( operationId() );
293 path += ".html"_L1;
294 return path;
295}
296
297void QgsServerOgcApiHandler::htmlDump( const json &data, const QgsServerApiContext &context ) const
298{
299 context.response()->setHeader( u"Content-Type"_s, u"text/html"_s );
300 auto path { templatePath( context ) };
301 if ( !QFile::exists( path ) )
302 {
303 QgsMessageLog::logMessage( u"Template not found error: %1"_s.arg( path ), u"Server"_s, Qgis::MessageLevel::Critical );
304 throw QgsServerApiBadRequestException( u"Template not found: %1"_s.arg( QFileInfo( path ).fileName() ) );
305 }
306
307 QFile f( path );
308 if ( !f.open( QFile::ReadOnly | QFile::Text ) )
309 {
310 QgsMessageLog::logMessage( u"Could not open template file: %1"_s.arg( path ), u"Server"_s, Qgis::MessageLevel::Critical );
311 throw QgsServerApiInternalServerError( u"Could not open template file: %1"_s.arg( QFileInfo( path ).fileName() ) );
312 }
313
314 try
315 {
316 // Get the template directory and the file name
317 QFileInfo pathInfo { path };
318 Environment env { QString( pathInfo.dir().path() + QDir::separator() ).toStdString() };
319 // Do not call env.set_html_autoescape( true ) because that would escape links too
320 // use the escape() function in the templates instead
321
322 // For template debugging:
323 env.add_callback( "json_dump", 0, [data]( Arguments & ) { return data.dump(); } );
324
325 // Path manipulation: appends a directory path to the current url
326 env.add_callback( "path_append", 1, [context]( Arguments &args ) {
327 auto url { context.request()->url() };
328 QFileInfo fi { url.path() };
329 auto suffix { fi.suffix() };
330 auto fName { fi.filePath() };
331 if ( !suffix.isEmpty() )
332 {
333 fName.chop( suffix.length() + 1 );
334 }
335 // Chop any ending slashes
336 while ( fName.endsWith( '/' ) )
337 {
338 fName.chop( 1 );
339 }
340 fName += '/' + QString::fromStdString( args.at( 0 )->get<std::string>() );
341 if ( !suffix.isEmpty() )
342 {
343 fName += '.' + suffix;
344 }
345 fi.setFile( fName );
346 url.setPath( fi.filePath() );
347 return url.toString().toStdString();
348 } );
349
350 // Path manipulation: removes the specified number of directory components from the current url path
351 env.add_callback( "path_chomp", 1, []( Arguments &args ) {
352 QUrl url { QString::fromStdString( args.at( 0 )->get<std::string>() ) };
353 QFileInfo fi { url.path() };
354 auto suffix { fi.suffix() };
355 auto fName { fi.filePath() };
356 fName.chop( suffix.length() + 1 );
357 // Chomp last segment
358 const thread_local QRegularExpression segmentRx( R"raw(\/[^/]+$)raw" );
359 fName = fName.replace( segmentRx, QString() );
360 if ( !suffix.isEmpty() )
361 {
362 fName += '.' + suffix;
363 }
364 fi.setFile( fName );
365 url.setPath( fi.filePath() );
366 return url.toString().toStdString();
367 } );
368
369 // Returns filtered links from a link list
370 // links_filter( <links>, <key>, <value> )
371 env.add_callback( "links_filter", 3, []( Arguments &args ) {
372 json links = args.at( 0 )->get<json>();
373 if ( !links.is_array() )
374 {
375 links = json::array();
376 }
377 std::string key { args.at( 1 )->get<std::string>() };
378 std::string value { args.at( 2 )->get<std::string>() };
379 json result = json::array();
380 for ( const auto &l : links )
381 {
382 if ( l[key] == value )
383 {
384 result.push_back( l );
385 }
386 }
387 return result;
388 } );
389
390 // Returns a short name from content types
391 env.add_callback( "content_type_name", 1, []( Arguments &args ) {
392 const QgsServerOgcApi::ContentType ct { QgsServerOgcApi::contentTypeFromExtension( args.at( 0 )->get<std::string>() ) };
394 } );
395
396 // Replace newlines with <br>
397 env.add_callback( "nl2br", 1, []( Arguments &args ) {
398 QString text { QString::fromStdString( args.at( 0 )->get<std::string>() ) };
399 return text.replace( '\n', "<br>"_L1 ).toStdString();
400 } );
401
402
403 // Returns a list of parameter component data from components -> parameters by ref name
404 // parameter( <ref object> )
405 env.add_callback( "component_parameter", 1, [data]( Arguments &args ) {
406 json ret = json::array();
407 json ref = args.at( 0 )->get<json>();
408 if ( !ref.is_object() )
409 {
410 return ret;
411 }
412 try
413 {
414 QString name = QString::fromStdString( ref["$ref"] );
415 name = name.split( '/' ).last();
416 ret.push_back( data["components"]["parameters"][name.toStdString()] );
417 }
418 catch ( std::exception & )
419 {
420 // Do nothing
421 }
422 return ret;
423 } );
424
425
426 // Static: returns the full URL to the specified static <path>
427 env.add_callback( "static", 1, [context]( Arguments &args ) {
428 auto asset( args.at( 0 )->get<std::string>() );
429 QString matchedPath { context.matchedPath() };
430 // If its the root path '/' strip it!
431 if ( matchedPath == '/' )
432 {
433 matchedPath.clear();
434 }
435 return matchedPath.toStdString() + "/static/" + asset;
436 } );
437
438
439 // Returns true if a string begins with the provided string prefix, false otherwise
440 env.add_callback( "starts_with", 2, []( Arguments &args ) { return string_view::starts_with( args.at( 0 )->get<std::string_view>(), args.at( 1 )->get<std::string_view>() ); } );
441
442 // Returns "null" string if object is null else string object representation
443 env.add_callback( "if_nullptr_null_str", 1, []( Arguments &args ) {
444 json jsonValue = args.at( 0 )->get<json>();
445 std::string out;
446 switch ( jsonValue.type() )
447 {
448 // avoid escaping string value
449 case json::value_t::string:
450 out = jsonValue.get<std::string>();
451 break;
452
453 case json::value_t::array:
454 case json::value_t::object:
455 if ( jsonValue.is_null() )
456 {
457 out = "null";
458 }
459 else
460 {
461 out = jsonValue.dump();
462 }
463
464 break;
465
466 // use dump() for all other value types
467 default:
468 out = jsonValue.dump();
469 }
470 return out;
471 } );
472
473 // HTML escape function
474 env.add_callback( "escape", 1, []( Arguments &args ) {
475 std::string str { args.at( 0 )->get<std::string>() };
476 std::string escaped;
477 escaped.reserve( str.size() );
478 for ( const char c : str )
479 {
480 switch ( c )
481 {
482 case '&':
483 escaped.append( "&amp;" );
484 break;
485 case '<':
486 escaped.append( "&lt;" );
487 break;
488 case '>':
489 escaped.append( "&gt;" );
490 break;
491 case '"':
492 escaped.append( "&quot;" );
493 break;
494 case '\'':
495 escaped.append( "&#39;" );
496 break;
497 default:
498 escaped.push_back( c );
499 }
500 }
501 return escaped;
502 } );
503
504 context.response()->write( env.render_file( pathInfo.fileName().toStdString(), data ) );
505 }
506 catch ( std::exception &e )
507 {
508 QgsMessageLog::logMessage( u"Error parsing template file: %1 - %2"_s.arg( path, e.what() ), u"Server"_s, Qgis::MessageLevel::Critical );
509 throw QgsServerApiInternalServerError( u"Error parsing template file: %1"_s.arg( e.what() ) );
510 }
511}
512
514{
515 // Fallback to default
517 bool found { false };
518 // First file extension ...
519 const QString extension { QFileInfo( request->url().path() ).suffix().toUpper() };
520 if ( !extension.isEmpty() )
521 {
522 static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
523 bool ok { false };
524 const int ct { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
525 if ( ok )
526 {
527 result = static_cast<QgsServerOgcApi::ContentType>( ct );
528 found = true;
529 }
530 else
531 {
532 // Hardcoded aliases
533#if 0
534 // This not supported yet but I am leaving it here because
535 // I am very optimistic that it will be supported soon!
536
537 if ( ( extension.compare( u"JSONFG"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 ) || ( extension.compare( u"JSONFG-PLUS"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 ) )
538 {
540 found = true;
541 }
542 else
543#endif
544 if ( extension.compare( u"FGB"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
545 {
547 found = true;
548 }
549 else
550 {
551 QgsMessageLog::logMessage( u"The client requested an unsupported extension: %1"_s.arg( extension ), u"Server"_s, Qgis::MessageLevel::Warning );
552 }
553 }
554 }
555 // ... then "Accept"
556 const QString accept { request->header( u"Accept"_s ) };
557 if ( !found && !accept.isEmpty() )
558 {
559 const QString ctFromAccept { contentTypeForAccept( accept ) };
560 if ( !ctFromAccept.isEmpty() )
561 {
562 const auto constContentTypes( QgsServerOgcApi::contentTypeMimes() );
563 auto it = constContentTypes.constBegin();
564 while ( !found && it != constContentTypes.constEnd() )
565 {
566 int idx = it.value().indexOf( ctFromAccept );
567 if ( idx >= 0 )
568 {
569 found = true;
570 result = it.key();
571 }
572 it++;
573 }
574 }
575 else
576 {
577 QgsMessageLog::logMessage( u"The client requested an unsupported content type in Accept header: %1"_s.arg( accept ), u"Server"_s, Qgis::MessageLevel::Warning );
578 }
579 }
580 // Validation: check if the requested content type (or an alias) is supported by the handler
581 if ( !contentTypes().contains( result ) )
582 {
583 // Check aliases
584 bool found { false };
585 if ( QgsServerOgcApi::contentTypeAliases().contains( result ) )
586 {
587 const QList<QgsServerOgcApi::ContentType> constCt { contentTypes() };
588 for ( const auto &ct : constCt )
589 {
590 if ( QgsServerOgcApi::contentTypeAliases()[result].contains( ct ) )
591 {
592 result = ct;
593 found = true;
594 break;
595 }
596 }
597 }
598
599 if ( !found )
600 {
601 QgsMessageLog::logMessage( u"Unsupported Content-Type: %1"_s.arg( QgsServerOgcApi::contentTypeToString( result ) ), u"Server"_s, Qgis::MessageLevel::Info );
602 throw QgsServerApiBadRequestException( u"Unsupported Content-Type: %1"_s.arg( QgsServerOgcApi::contentTypeToString( result ) ) );
603 }
604 }
605 return result;
606}
607
609{
610 if ( profile.compare( "RFC7946"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
611 {
612 ok = true;
614 }
615 else if ( profile.compare( "JSON-FG"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
616 {
617 ok = true;
619 }
620 else if ( profile.compare( "JSON-FG-PLUS"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
621 {
622 ok = true;
624 }
625 else if ( profile.compare( "REL-AS-KEY"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
626 {
627 ok = true;
629 }
630 else if ( profile.compare( "REL-AS-URI"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
631 {
632 ok = true;
634 }
635 else if ( profile.compare( "REL-AS-LINK"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
636 {
637 ok = true;
639 }
640 else
641 {
642 ok = false;
644 }
646}
647
648QList<QgsServerOgcApi::Profile> QgsServerOgcApiHandler::profilesFromRequest( const QgsServerRequest *request ) const
649{
650 const QStringList profileStrings { request->queryParameter( u"profile"_s ).split( ',', Qt::SkipEmptyParts ) };
651 QList<QgsServerOgcApi::Profile> profiles;
652 if ( !profileStrings.isEmpty() )
653 {
654 for ( const auto &profileString : std::as_const( profileStrings ) )
655 {
656 bool ok { false };
657 const QgsServerOgcApi::Profile p { profileFromString( profileString, ok ) };
658 if ( ok )
659 {
660 profiles.push_back( p );
661 }
662 else
663 {
664 throw QgsServerApiBadRequestException( u"Unsupported profile requested: %1"_s.arg( profileString ) );
665 }
666 }
667 }
668 return profiles;
669}
670
671QString QgsServerOgcApiHandler::parentLink( const QUrl &url, int levels )
672{
673 QString path { url.path() };
674 const QFileInfo fi { path };
675 const QString suffix { fi.suffix() };
676 if ( !suffix.isEmpty() )
677 {
678 path.chop( suffix.length() + 1 );
679 }
680 while ( path.endsWith( '/' ) )
681 {
682 path.chop( 1 );
683 }
684 const thread_local QRegularExpression re( R"raw(\/[^/]+$)raw" );
685 for ( int i = 0; i < levels; i++ )
686 {
687 path = path.replace( re, QString() );
688 }
689 QUrl result( url );
690 QUrlQuery query( result );
691 QList<QPair<QString, QString>> qi;
692 const auto constItems { query.queryItems() };
693 for ( const auto &i : constItems )
694 {
695 if ( i.first.compare( u"MAP"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
696 {
697 qi.push_back( i );
698 }
699 }
700 // Make sure the parent link ends with a slash
701 if ( !path.endsWith( '/' ) )
702 {
703 path.append( '/' );
704 }
705 QUrlQuery resultQuery;
706 resultQuery.setQueryItems( qi );
707 result.setQuery( resultQuery );
708 result.setPath( path );
709 return result.toString();
710}
711
713{
714 const auto mapLayers { context.project()->mapLayersByShortName<QgsVectorLayer *>( collectionId ) };
715 if ( mapLayers.count() != 1 )
716 {
717 throw QgsServerApiNotFoundError( u"Collection with given id (%1) was not found or multiple matches were found"_s.arg( collectionId ) );
718 }
719 return mapLayers.first();
720}
721
723{
724 static json defRes
725 = { { "description", "An error occurred." }, { "content", { { "application/json", { { "schema", { { "$ref", "#/components/schemas/exception" } } } } }, { "text/html", { { "schema", { { "type", "string" } } } } } } } };
726 return defRes;
727}
728
733
735{
736 mContentTypes.clear();
737 for ( const int &i : std::as_const( contentTypes ) )
738 {
739 mContentTypes.push_back( static_cast<QgsServerOgcApi::ContentType>( i ) );
740 }
741}
742
743void QgsServerOgcApiHandler::setContentTypes( const QList<QgsServerOgcApi::ContentType> &contentTypes )
744{
745 mContentTypes = contentTypes;
746}
@ Warning
Warning message.
Definition qgis.h:162
@ Critical
Critical/error message.
Definition qgis.h:163
@ Info
Information message.
Definition qgis.h:161
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, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
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.
Encapsulates the resources for a particular client 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.
Raised when a configuration error on the server prevents to serve the request, which would be valid o...
Internal server error API exception.
Not found error API exception.
Raised when the client requested a method that is not yet implemented.
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
void jsonDump(json &data, const QgsServerApiContext &context, const QString &contentType=u"application/json"_s) const
Writes data to the context response stream as JSON (indented if debug is active), an optional content...
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 QVariantMap values(const QgsServerApiContext &context) const
Analyzes the incoming request context and returns the validated parameter map, throws QgsServerApiBad...
static QgsServerOgcApi::Profile profileFromString(const QString &profile, bool &ok)
Returns the Profile from the string representation in profile, or Profile::None if the string was not...
QString headerLink(const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType=QgsServerOgcApi::Rel::self, const QgsServerOgcApi::ContentType contentType=QgsServerOgcApi::ContentType::JSON, const QgsServerOgcApi::Profile &profile=QgsServerOgcApi::Profile::Unset, const QString &title="") const
Builds and returns a header link to the resource.
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.
static json defaultResponse()
Returns the defaultResponse as JSON.
QgsVectorLayer * layerFromContext(const QgsServerApiContext &context) const
Returns a vector layer instance from the "collectionId" parameter of the path in the given context,...
QList< QgsServerOgcApi::Profile > profilesFromRequest(const QgsServerRequest *request) const
Return a list of the profiles in the request, extracted from the "profile" query parameter.
QString contentTypeForAccept(const QString &accept) const
Looks for the first ContentType match in the accept header and returns its mime type,...
virtual void handleRequest(const QgsServerApiContext &context) const
Handles the request within its context.
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.
ContentType
Media types used for content negotiation, insert more specific first.
@ SCHEMA_JSON
"application/schema+json"
@ OPENAPI3
"application/openapi+json;version=3.0"
@ FLATGEOBUF
"application/flatgeobuf"
static QString contentTypeToString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
static QString profileToString(const QgsServerOgcApi::Profile &profile)
Returns a string representation of the profile.
Rel
Rel link types.
@ alternate
Refers to a substitute for this context.
@ self
Conveys an identifier for the link’s context.
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.
Profile
JSON profile.
@ Rfc7946
GeoJSON profile according to RFC7946.
@ RelAsUri
JSON responses that include URI for referenced resources http://www.opengis.net/def/profile/ogc/0/rel...
@ JsonFg
JSON Feature Geometry profile according to OGC API - Features 1.0.
@ RelAsLink
JSON responses that include links for referenced resources http://www.opengis.net/def/profile/ogc/0/r...
@ RelAsKey
JSON responses that include key for referenced resources http://www.opengis.net/def/profile/ogc/0/rel...
@ JsonFgPlus
JSON Feature Geometry profile with GeoJSON compatibility extensions.
static std::string relToString(const QgsServerOgcApi::Rel &rel)
Returns the string representation of rel attribute, to be used or "rel" link attribute in an OGC API ...
static const QHash< QgsServerOgcApi::ContentType, QList< QgsServerOgcApi::ContentType > > contentTypeAliases()
Returns contentType specializations (e.g.
static QgsServerOgcApi::ContentType contentTypeFromExtension(const std::string &extension)
Returns the Content-Type value corresponding to extension.
Defines requests passed to QgsService classes.
const QString queryParameter(const QString &name, const QString &defaultValue=QString()) const
Returns the query string parameter with the given name from the request URL, a defaultValue can be sp...
virtual QString header(const QString &name) const
Returns the header value.
QUrl url() const
Returns the request URL as seen by QGIS server.
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 a single header value replacing any existing value(s) for the same key.
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 dataset.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define BUILTIN_UNREACHABLE
Definition qgis.h:8122