32 #include "nlohmann/json.hpp"
33 #include "inja/inja.hpp"
35 using namespace nlohmann;
43 const auto constParameters { parameters( context ) };
44 for (
const auto &p : constParameters )
47 result[p.name()] = p.value( context );
50 const auto match { path().match( sanitizedPath ) };
51 if ( match.hasMatch() )
53 const auto constNamed { path().namedCaptureGroups() };
55 for (
const auto &name : constNamed )
57 if ( ! name.isEmpty() )
58 result[name] = QUrlQuery( match.captured( name ) ).toString() ;
71 const auto constContentTypes( contentTypes() );
72 return constContentTypes.size() > 0 ? constContentTypes.first() : QgsServerOgcApi::ContentType::JSON;
89 for (
auto it = constContentTypes.constBegin();
90 it != constContentTypes.constEnd(); ++it )
92 const auto constValues = it.value();
93 for (
const auto &value : constValues )
95 if ( accept.contains( value, Qt::CaseSensitivity::CaseInsensitive ) )
103 QStringLiteral(
"Server" ),
104 Qgis::MessageLevel::Info );
112 switch ( contentType )
114 case QgsServerOgcApi::ContentType::HTML:
115 data[
"handler"] = schema( context );
116 if ( ! htmlMetadata.is_null() )
118 data[
"metadata"] = htmlMetadata;
120 htmlDump( data, context );
122 case QgsServerOgcApi::ContentType::GEOJSON:
123 case QgsServerOgcApi::ContentType::JSON:
124 case QgsServerOgcApi::ContentType::OPENAPI3:
127 case QgsServerOgcApi::ContentType::XML:
145 if ( match.captured().count() > 0 )
147 url.setPath( urlBasePath + match.captured( 0 ) );
151 url.setPath( urlBasePath );
155 const auto suffixLength { QFileInfo( url.path() ).suffix().length() };
156 if ( suffixLength > 0 )
158 auto path {url.path()};
159 path.truncate( path.length() - ( suffixLength + 1 ) );
164 url.setPath( url.path() + extraPath );
168 if ( ! extension.isEmpty() )
171 QString path { url.path() };
172 while ( path.endsWith(
'/' ) )
176 url.setPath( path +
'.' + extension );
187 QDateTime time { QDateTime::currentDateTime() };
188 time.setTimeSpec( Qt::TimeSpec::UTC );
189 data[
"timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString() ;
211 "href", href( context,
"/",
216 {
"title", title !=
"" ? title : linkTitle() },
224 json links = json::array();
225 const QList<QgsServerOgcApi::ContentType> constCts { contentTypes() };
226 for (
const auto &ct : constCts )
228 links.push_back( link( context, ( ct == currentCt ? QgsServerOgcApi::Rel::self :
229 QgsServerOgcApi::Rel::alternate ), ct,
242 const QRegularExpressionMatch match { path().match( context.
request()->
url().path( ) ) };
243 if ( ! match.hasMatch() )
247 const QString collectionId { match.captured( QStringLiteral(
"collectionId" ) ) };
249 return layerFromCollectionId( context, collectionId );
263 path += QLatin1String(
"/ogc/templates" );
266 path += QString::fromStdString( operationId() );
267 path += QLatin1String(
".html" );
274 context.
response()->
setHeader( QStringLiteral(
"Content-Type" ), QStringLiteral(
"text/html" ) );
275 auto path { templatePath( context ) };
276 if ( ! QFile::exists( path ) )
278 QgsMessageLog::logMessage( QStringLiteral(
"Template not found error: %1" ).arg( path ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Critical );
283 if ( ! f.open( QFile::ReadOnly | QFile::Text ) )
285 QgsMessageLog::logMessage( QStringLiteral(
"Could not open template file: %1" ).arg( path ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Critical );
292 QFileInfo pathInfo { path };
293 Environment env { QString( pathInfo.dir().path() + QDir::separator() ).toStdString() };
296 env.add_callback(
"json_dump", 0, [ = ]( Arguments & )
302 env.add_callback(
"path_append", 1, [ = ]( Arguments & args )
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() )
310 fName.chop( suffix.length() + 1 );
313 while ( fName.endsWith(
'/' ) )
317 fName +=
'/' + QString::fromStdString( args.at( 0 )->get<std::string>( ) );
318 if ( !suffix.isEmpty() )
320 fName +=
'.' + suffix;
323 url.setPath( fi.filePath() );
324 return url.toString().toStdString();
328 env.add_callback(
"path_chomp", 1, [ = ]( Arguments & args )
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 );
336 fName = fName.replace( QRegularExpression( R
"raw(\/[^/]+$)raw" ), QString() );
337 if ( !suffix.isEmpty() )
339 fName +=
'.' + suffix;
342 url.setPath( fi.filePath() );
343 return url.toString().toStdString();
348 env.add_callback(
"links_filter", 3, [ = ]( Arguments & args )
350 json links = args.at( 0 )->get<json>( );
351 if ( ! links.is_array() )
353 links = json::array();
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 )
360 if ( l[key] == value )
362 result.push_back( l );
369 env.add_callback(
"content_type_name", 1, [ = ]( Arguments & args )
376 env.add_callback(
"nl2br", 1, [ = ]( Arguments & args )
378 QString text { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
379 return text.replace(
'\n', QLatin1String(
"<br>" ) ).toStdString();
385 env.add_callback(
"component_parameter", 1, [ = ]( Arguments & args )
387 json ret = json::array();
388 json ref = args.at( 0 )->get<json>( );
389 if ( ! ref.is_object() )
395 QString name = QString::fromStdString( ref[
"$ref"] );
396 name = name.split(
'/' ).last();
397 ret.push_back( data[
"components"][
"parameters"][name.toStdString()] );
399 catch ( std::exception & )
408 env.add_callback(
"static", 1, [ = ]( Arguments & args )
410 auto asset( args.at( 0 )->get<std::string>( ) );
413 if ( matchedPath ==
'/' )
417 return matchedPath.toStdString() +
"/static/" + asset;
420 context.
response()->
write( env.render_file( pathInfo.fileName().toStdString(), data ) );
422 catch ( std::exception &e )
424 QgsMessageLog::logMessage( QStringLiteral(
"Error parsing template file: %1 - %2" ).arg( path, e.what() ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Critical );
433 bool found {
false };
435 const QString extension { QFileInfo( request->
url().path() ).suffix().toUpper() };
436 if ( ! extension.isEmpty() )
438 static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
440 const int ct { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
448 QgsMessageLog::logMessage( QStringLiteral(
"The client requested an unsupported extension: %1" ).arg( extension ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Warning );
452 const QString accept { request->
header( QStringLiteral(
"Accept" ) ) };
453 if ( ! found && ! accept.isEmpty() )
455 const QString ctFromAccept { contentTypeForAccept( accept ) };
456 if ( ! ctFromAccept.isEmpty() )
459 auto it = constContentTypes.constBegin();
460 while ( ! found && it != constContentTypes.constEnd() )
462 int idx = it.value().indexOf( ctFromAccept );
473 QgsMessageLog::logMessage( QStringLiteral(
"The client requested an unsupported content type in Accept header: %1" ).arg( accept ), QStringLiteral(
"Server" ), Qgis::MessageLevel::Warning );
477 if ( ! contentTypes().contains( result ) )
480 bool found {
false };
483 const QList<QgsServerOgcApi::ContentType> constCt { contentTypes() };
484 for (
const auto &ct : constCt )
506 QString path { url.path() };
507 const QFileInfo fi { path };
508 const QString suffix { fi.suffix() };
509 if ( ! suffix.isEmpty() )
511 path.chop( suffix.length() + 1 );
513 while ( path.endsWith(
'/' ) )
517 QRegularExpression re( R
"raw(\/[^/]+$)raw" );
518 for (
int i = 0; i < levels ; i++ )
520 path = path.replace( re, QString() );
523 QUrlQuery query( result );
524 QList<QPair<QString, QString> > qi;
525 const auto constItems { query.queryItems( ) };
526 for (
const auto &i : constItems )
528 if ( i.first.compare( QStringLiteral(
"MAP" ), Qt::CaseSensitivity::CaseInsensitive ) == 0 )
534 if ( ! path.endsWith(
'/' ) )
538 QUrlQuery resultQuery;
539 resultQuery.setQueryItems( qi );
540 result.setQuery( resultQuery );
541 result.setPath( path );
542 return result.toString();
548 if ( mapLayers.count() != 1 )
550 throw QgsServerApiNotFoundError( QStringLiteral(
"Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) );
552 return mapLayers.first();
559 {
"description",
"An error occurred." },
563 "application/json", {
566 {
"$ref",
"#/components/schemas/exception" }
593 mContentTypes.clear();
594 for (
const int &i : std::as_const( contentTypes ) )
602 mContentTypes = contentTypes;
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.
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 ~QgsServerOgcApiHandler()
virtual json schema(const QgsServerApiContext &context) const
Returns handler information from the context for the OPENAPI description (id, description and other m...
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 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,...
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.
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.