19#include <nlohmann/json.hpp>
21#include "inja/inja.hpp"
36using namespace Qt::StringLiterals;
38using namespace nlohmann;
45 const auto constParameters {
parameters( context ) };
46 for (
const auto &p : constParameters )
49 result[p.name()] = p.value( context );
52 const auto match {
path().match( sanitizedPath ) };
53 if ( match.hasMatch() )
55 const auto constNamed {
path().namedCaptureGroups() };
57 for (
const auto &name : constNamed )
59 if ( !name.isEmpty() )
60 result[name] = QUrlQuery( match.captured( name ) ).toString();
91 for (
auto it = constContentTypes.constBegin(); it != constContentTypes.constEnd(); ++it )
93 const auto constValues = it.value();
94 for (
const auto &value : constValues )
96 if ( accept.contains( value, Qt::CaseSensitivity::CaseInsensitive ) )
111 switch ( contentType )
114 data[
"handler"] =
schema( context );
115 if ( !htmlMetadata.is_null() )
117 data[
"metadata"] = htmlMetadata;
148 if ( match.captured().count() > 0 )
150 url.setPath( urlBasePath + match.captured( 0 ) );
154 url.setPath( urlBasePath );
158 const auto suffixLength { QFileInfo( url.path() ).suffix().length() };
159 if ( suffixLength > 0 )
161 auto path { url.path() };
162 path.truncate(
path.length() - ( suffixLength + 1 ) );
167 url.setPath( url.path() + extraPath );
171 if ( !extension.isEmpty() )
174 QString
path { url.path() };
175 while (
path.endsWith(
'/' ) )
179 url.setPath(
path +
'.' + extension );
189 QDateTime time { QDateTime::currentDateTime() };
190 time.setTimeSpec( Qt::TimeSpec::UTC );
191 data[
"timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString();
214 {
"title", title !=
"" ? title :
linkTitle() },
226 if ( !profileStr.isEmpty() )
228 hrefStr +=
"&profile="_L1 + profileStr;
231 QString titleStr = !title.isEmpty() ? title : QString::fromStdString(
linkTitle() );
232 titleStr.replace(
'\\',
"\\\\"_L1 );
233 titleStr.replace(
'"',
"\\\""_L1 );
235 QString linkStr = u
"<%1>; rel=\"%2\"; title=\"%3\"; type=\"%4\""_s.arg(
242 if ( !profileStr.isEmpty() )
244 linkStr += u
"; profile=\"%1\""_s.arg( profileStr );
253 json
links = json::array();
254 const QList<QgsServerOgcApi::ContentType> constCts {
contentTypes() };
255 for (
const auto &ct : constCts )
269 const QRegularExpressionMatch match {
path().match( context.
request()->
url().path() ) };
270 if ( !match.hasMatch() )
274 const QString collectionId { match.captured( u
"collectionId"_s ) };
289 path +=
"/ogc/templates"_L1;
301 if ( !QFile::exists(
path ) )
308 if ( !f.open( QFile::ReadOnly | QFile::Text ) )
317 QFileInfo pathInfo {
path };
318 Environment env { QString( pathInfo.dir().path() + QDir::separator() ).toStdString() };
321 env.add_callback(
"json_dump", 0, [data]( Arguments & ) {
return data.dump(); } );
324 env.add_callback(
"path_append", 1, [context]( Arguments &args ) {
326 QFileInfo fi { url.path() };
327 auto suffix { fi.suffix() };
328 auto fName { fi.filePath() };
329 if ( !suffix.isEmpty() )
331 fName.chop( suffix.length() + 1 );
334 while ( fName.endsWith(
'/' ) )
338 fName +=
'/' + QString::fromStdString( args.at( 0 )->get<std::string>() );
339 if ( !suffix.isEmpty() )
341 fName +=
'.' + suffix;
344 url.setPath( fi.filePath() );
345 return url.toString().toStdString();
349 env.add_callback(
"path_chomp", 1, []( Arguments &args ) {
350 QUrl url { QString::fromStdString( args.at( 0 )->get<std::string>() ) };
351 QFileInfo fi { url.path() };
352 auto suffix { fi.suffix() };
353 auto fName { fi.filePath() };
354 fName.chop( suffix.length() + 1 );
356 const thread_local QRegularExpression segmentRx( R
"raw(\/[^/]+$)raw" );
357 fName = fName.replace( segmentRx, QString() );
358 if ( !suffix.isEmpty() )
360 fName +=
'.' + suffix;
363 url.setPath( fi.filePath() );
364 return url.toString().toStdString();
369 env.add_callback(
"links_filter", 3, []( Arguments &args ) {
370 json
links = args.at( 0 )->get<json>();
371 if ( !
links.is_array() )
373 links = json::array();
375 std::string key { args.at( 1 )->get<std::string>() };
376 std::string value { args.at( 2 )->get<std::string>() };
377 json result = json::array();
378 for (
const auto &l :
links )
380 if ( l[key] == value )
382 result.push_back( l );
389 env.add_callback(
"content_type_name", 1, []( Arguments &args ) {
395 env.add_callback(
"nl2br", 1, []( Arguments &args ) {
396 QString text { QString::fromStdString( args.at( 0 )->get<std::string>() ) };
397 return text.replace(
'\n',
"<br>"_L1 ).toStdString();
403 env.add_callback(
"component_parameter", 1, [data]( Arguments &args ) {
404 json ret = json::array();
405 json ref = args.at( 0 )->get<json>();
406 if ( !ref.is_object() )
412 QString name = QString::fromStdString( ref[
"$ref"] );
413 name = name.split(
'/' ).last();
414 ret.push_back( data[
"components"][
"parameters"][name.toStdString()] );
416 catch ( std::exception & )
425 env.add_callback(
"static", 1, [context]( Arguments &args ) {
426 auto asset( args.at( 0 )->get<std::string>() );
429 if ( matchedPath ==
'/' )
433 return matchedPath.toStdString() +
"/static/" + asset;
438 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 env.add_callback(
"if_nullptr_null_str", 1, []( Arguments &args ) {
442 json jsonValue = args.at( 0 )->get<json>();
444 switch ( jsonValue.type() )
447 case json::value_t::string:
448 out = jsonValue.get<std::string>();
451 case json::value_t::array:
452 case json::value_t::object:
453 if ( jsonValue.is_null() )
459 out = jsonValue.dump();
466 out = jsonValue.dump();
471 context.
response()->
write( env.render_file( pathInfo.fileName().toStdString(), data ) );
473 catch ( std::exception &e )
484 bool found {
false };
486 const QString extension { QFileInfo( request->
url().path() ).suffix().toUpper() };
487 if ( !extension.isEmpty() )
489 static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
491 const int ct { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
504 if ( ( extension.compare( u
"JSONFG"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 ) || ( extension.compare( u
"JSONFG-PLUS"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 ) )
511 if ( extension.compare( u
"FGB"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
523 const QString accept { request->
header( u
"Accept"_s ) };
524 if ( !found && !accept.isEmpty() )
527 if ( !ctFromAccept.isEmpty() )
530 auto it = constContentTypes.constBegin();
531 while ( !found && it != constContentTypes.constEnd() )
533 int idx = it.value().indexOf( ctFromAccept );
551 bool found {
false };
554 const QList<QgsServerOgcApi::ContentType> constCt {
contentTypes() };
555 for (
const auto &ct : constCt )
577 if ( profile.compare(
"RFC7946"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
582 else if ( profile.compare(
"REL-AS-KEY"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
587 else if ( profile.compare(
"REL-AS-URI"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
592 else if ( profile.compare(
"REL-AS-LINK"_L1, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
607 const QStringList profileStrings { request->
queryParameter( u
"profile"_s ).split(
',', Qt::SkipEmptyParts ) };
608 QList<QgsServerOgcApi::Profile> profiles;
609 if ( !profileStrings.isEmpty() )
611 for (
const auto &profileString : std::as_const( profileStrings ) )
617 profiles.push_back( p );
630 QString
path { url.path() };
631 const QFileInfo fi {
path };
632 const QString suffix { fi.suffix() };
633 if ( !suffix.isEmpty() )
635 path.chop( suffix.length() + 1 );
637 while (
path.endsWith(
'/' ) )
641 const thread_local QRegularExpression re( R
"raw(\/[^/]+$)raw" );
642 for (
int i = 0; i < levels; i++ )
644 path =
path.replace( re, QString() );
647 QUrlQuery query( result );
648 QList<QPair<QString, QString>> qi;
649 const auto constItems { query.queryItems() };
650 for (
const auto &i : constItems )
652 if ( i.first.compare( u
"MAP"_s, Qt::CaseSensitivity::CaseInsensitive ) == 0 )
658 if ( !
path.endsWith(
'/' ) )
662 QUrlQuery resultQuery;
663 resultQuery.setQueryItems( qi );
664 result.setQuery( resultQuery );
665 result.setPath(
path );
666 return result.toString();
672 if ( mapLayers.count() != 1 )
674 throw QgsServerApiNotFoundError( u
"Collection with given id (%1) was not found or multiple matches were found"_s.arg( collectionId ) );
676 return mapLayers.first();
682 = { {
"description",
"An error occurred." }, {
"content", { {
"application/json", { {
"schema", { {
"$ref",
"#/components/schemas/exception" } } } } }, {
"text/html", { {
"schema", { {
"type",
"string" } } } } } } } };
693 mContentTypes.clear();
@ Warning
Warning message.
@ Critical
Critical/error message.
@ Info
Information message.
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.
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 ~QgsServerOgcApiHandler()
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.
@ 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.
@ Rfc7946
GeoJSON profile according to RFC7946.
@ RelAsUri
JSON responses that include URI for referenced resources http://www.opengis.net/def/profile/ogc/0/rel...
@ 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...
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.
#define BUILTIN_UNREACHABLE