32#include "nlohmann/json.hpp" 
   33#include "inja/inja.hpp" 
   35using 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() ;
 
   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;
 
  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" ) ) };
 
  263  path += QLatin1String( 
"/ogc/templates" );
 
  267  path += QLatin1String( 
".html" );
 
  274  context.
response()->
setHeader( QStringLiteral( 
"Content-Type" ), QStringLiteral( 
"text/html" ) );
 
  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      const thread_local QRegularExpression segmentRx( R
"raw(\/[^/]+$)raw" ); 
  337      fName = fName.replace( segmentRx, QString() ); 
  338      if ( !suffix.isEmpty() )
 
  340        fName += 
'.' + suffix;
 
  343      url.setPath( fi.filePath() );
 
  344      return url.toString().toStdString();
 
  349    env.add_callback( 
"links_filter", 3, [ = ]( Arguments & args )
 
  351      json 
links = args.at( 0 )->get<json>( );
 
  352      if ( ! 
links.is_array() )
 
  354        links = json::array();
 
  356      std::string key { args.at( 1 )->get<std::string>( ) };
 
  357      std::string value { args.at( 2 )->get<std::string>( ) };
 
  358      json result = json::array();
 
  359      for ( 
const auto &l : 
links )
 
  361        if ( l[key] == value )
 
  363          result.push_back( l );
 
  370    env.add_callback( 
"content_type_name", 1, [ = ]( Arguments & args )
 
  377    env.add_callback( 
"nl2br", 1, [ = ]( Arguments & args )
 
  379      QString text { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
 
  380      return text.replace( 
'\n', QLatin1String( 
"<br>" ) ).toStdString();
 
  386    env.add_callback( 
"component_parameter", 1, [ = ]( Arguments & args )
 
  388      json ret = json::array();
 
  389      json ref = args.at( 0 )->get<json>( );
 
  390      if ( ! ref.is_object() )
 
  396        QString name = QString::fromStdString( ref[
"$ref"] );
 
  397        name = name.split( 
'/' ).last();
 
  398        ret.push_back( data[
"components"][
"parameters"][name.toStdString()] );
 
  400      catch ( std::exception & )
 
  409    env.add_callback( 
"static", 1, [ = ]( Arguments & args )
 
  411      auto asset( args.at( 0 )->get<std::string>( ) );
 
  414      if ( matchedPath == 
'/' )
 
  418      return matchedPath.toStdString() + 
"/static/" + asset;
 
  423    env.add_callback( 
"starts_with", 2, [ ]( Arguments & args )
 
  425      return string_view::starts_with( args.at( 0 )->get<std::string_view>( ), args.at( 1 )->get<std::string_view>( ) );
 
  428    context.
response()->
write( env.render_file( pathInfo.fileName().toStdString(), data ) );
 
  430  catch ( std::exception &e )
 
  432    QgsMessageLog::logMessage( QStringLiteral( 
"Error parsing template file: %1 - %2" ).arg( 
path, e.what() ), QStringLiteral( 
"Server" ), Qgis::MessageLevel::Critical );
 
  441  bool found { 
false };
 
  443  const QString extension { QFileInfo( request->
url().path() ).suffix().toUpper() };
 
  444  if ( ! extension.isEmpty() )
 
  446    static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
 
  448    const int ct  { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
 
  456      QgsMessageLog::logMessage( QStringLiteral( 
"The client requested an unsupported extension: %1" ).arg( extension ), QStringLiteral( 
"Server" ), Qgis::MessageLevel::Warning );
 
  460  const QString accept { request->
header( QStringLiteral( 
"Accept" ) ) };
 
  461  if ( ! found && ! accept.isEmpty() )
 
  464    if ( ! ctFromAccept.isEmpty() )
 
  467      auto it = constContentTypes.constBegin();
 
  468      while ( ! found && it != constContentTypes.constEnd() )
 
  470        int idx = it.value().indexOf( ctFromAccept );
 
  481      QgsMessageLog::logMessage( QStringLiteral( 
"The client requested an unsupported content type in Accept header: %1" ).arg( accept ), QStringLiteral( 
"Server" ), Qgis::MessageLevel::Warning );
 
  488    bool found { 
false };
 
  491      const QList<QgsServerOgcApi::ContentType> constCt { 
contentTypes() };
 
  492      for ( 
const auto &ct : constCt )
 
  514  QString 
path { url.path() };
 
  515  const QFileInfo fi { 
path };
 
  516  const QString suffix { fi.suffix() };
 
  517  if ( ! suffix.isEmpty() )
 
  519    path.chop( suffix.length() + 1 );
 
  521  while ( 
path.endsWith( 
'/' ) )
 
  525  const thread_local QRegularExpression re( R
"raw(\/[^/]+$)raw" ); 
  526  for ( 
int i = 0; i < levels ; i++ )
 
  528    path = 
path.replace( re, QString() );
 
  531  QUrlQuery query( result );
 
  532  QList<QPair<QString, QString> > qi;
 
  533  const auto constItems { query.queryItems( ) };
 
  534  for ( 
const auto &i : constItems )
 
  536    if ( i.first.compare( QStringLiteral( 
"MAP" ), Qt::CaseSensitivity::CaseInsensitive ) == 0 )
 
  542  if ( ! 
path.endsWith( 
'/' ) )
 
  546  QUrlQuery resultQuery;
 
  547  resultQuery.setQueryItems( qi );
 
  548  result.setQuery( resultQuery );
 
  549  result.setPath( 
path );
 
  550  return result.toString();
 
  556  if ( mapLayers.count() != 1 )
 
  558    throw QgsServerApiNotFoundError( QStringLiteral( 
"Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) );
 
  560  return mapLayers.first();
 
  567    { 
"description", 
"An error occurred." },
 
  571          "application/json", {
 
  574                { 
"$ref", 
"#/components/schemas/exception" }
 
  601  mContentTypes.clear();
 
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 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.
 
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.
 
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.