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.