QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 
17 #include <QDateTime>
18 #include <QFileInfo>
19 #include <QDir>
20 #include <QDebug>
21 
22 #include "qgsmessagelog.h"
23 #include "qgsproject.h"
24 #include "qgsjsonutils.h"
25 #include "qgsvectorlayer.h"
26 
27 #include "qgsserverogcapihandler.h"
28 #include "qgsserverapiutils.h"
29 #include "qgsserverresponse.h"
30 #include "qgsserverinterface.h"
31 
32 #include "nlohmann/json.hpp"
33 #include "inja/inja.hpp"
34 
35 using namespace nlohmann;
36 using namespace inja;
37 
38 
39 
40 QVariantMap QgsServerOgcApiHandler::values( const QgsServerApiContext &context ) const
41 {
42  QVariantMap result ;
43  QVariantList positional;
44  const auto constParameters { parameters( context ) };
45  for ( const auto &p : constParameters )
46  {
47  // value() calls the validators and throws an exception if validation fails
48  result[p.name()] = p.value( context );
49  }
50  const auto match { path().match( context.request()->url().toString() ) };
51  if ( match.hasMatch() )
52  {
53  const auto constNamed { path().namedCaptureGroups() };
54  // Get named path parameters
55  for ( const auto &name : constNamed )
56  {
57  if ( ! name.isEmpty() )
58  result[name] = QUrlQuery( match.captured( name ) ).toString() ;
59  }
60  }
61  return result;
62 }
63 
65 {
66  //qDebug() << "handler destroyed";
67 }
68 
70 {
71  return contentTypes().size() > 0 ? contentTypes().first() : QgsServerOgcApi::ContentType::JSON;
72 }
73 
74 QList<QgsServerOgcApi::ContentType> QgsServerOgcApiHandler::contentTypes() const
75 {
76  return mContentTypes;
77 }
78 
80 {
81  Q_UNUSED( context )
82  throw QgsServerApiNotImplementedException( QStringLiteral( "Subclasses must implement handleRequest" ) );
83 }
84 
85 QString QgsServerOgcApiHandler::contentTypeForAccept( const QString &accept ) const
86 {
87  for ( auto it = QgsServerOgcApi::contentTypeMimes().constBegin();
88  it != QgsServerOgcApi::contentTypeMimes().constEnd(); ++it )
89  {
90  const auto constValues = it.value();
91  for ( const auto &value : constValues )
92  {
93  if ( accept.contains( value, Qt::CaseSensitivity::CaseInsensitive ) )
94  {
95  return value;
96  }
97  }
98  }
99  // Log level info because this is not completely unexpected
100  QgsMessageLog::logMessage( QStringLiteral( "Content type for accept %1 not found!" ).arg( accept ),
101  QStringLiteral( "Server" ),
102  Qgis::Info );
103 
104  return QString();
105 }
106 
107 void QgsServerOgcApiHandler::write( json &data, const QgsServerApiContext &context, const json &htmlMetadata ) const
108 {
109  const QgsServerOgcApi::ContentType contentType { contentTypeFromRequest( context.request() ) };
110  switch ( contentType )
111  {
112  case QgsServerOgcApi::ContentType::HTML:
113  data["handler"] = schema( context );
114  if ( ! htmlMetadata.is_null() )
115  {
116  data["metadata"] = htmlMetadata;
117  }
118  htmlDump( data, context );
119  break;
120  case QgsServerOgcApi::ContentType::GEOJSON:
121  case QgsServerOgcApi::ContentType::JSON:
122  case QgsServerOgcApi::ContentType::OPENAPI3:
123  jsonDump( data, context, QgsServerOgcApi::contentTypeMimes().value( contentType ).first() );
124  break;
125  }
126 }
127 
128 void QgsServerOgcApiHandler::write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata ) const
129 {
130  json j = QgsJsonUtils::jsonFromVariant( data );
131  json jm = QgsJsonUtils::jsonFromVariant( htmlMetadata );
132  QgsServerOgcApiHandler::write( j, context, jm );
133 }
134 
135 std::string QgsServerOgcApiHandler::href( const QgsServerApiContext &context, const QString &extraPath, const QString &extension ) const
136 {
137  QUrl url { context.request()->url() };
138  QString urlBasePath { context.matchedPath() };
139  const auto match { path().match( url.path() ) };
140  if ( match.captured().count() > 0 )
141  {
142  url.setPath( urlBasePath + match.captured( 0 ) );
143  }
144  else
145  {
146  url.setPath( urlBasePath );
147  }
148 
149  // Remove any existing extension
150  const auto suffixLength { QFileInfo( url.path() ).completeSuffix().length() };
151  if ( suffixLength > 0 )
152  {
153  auto path {url.path()};
154  path.truncate( path.length() - ( suffixLength + 1 ) );
155  url.setPath( path );
156  }
157 
158  // Add extra path
159  url.setPath( url.path() + extraPath );
160 
161  // (re-)add extension
162  // JSON is the default anyway so we don't need to add it
163  if ( ! extension.isEmpty() )
164  {
165  // Remove trailing slashes if any.
166  QString path { url.path() };
167  while ( path.endsWith( '/' ) )
168  {
169  path.chop( 1 );
170  }
171  url.setPath( path + '.' + extension );
172  }
173  return QgsServerOgcApi::sanitizeUrl( url ).toString( QUrl::FullyEncoded ).toStdString();
174 
175 }
176 
177 void QgsServerOgcApiHandler::jsonDump( json &data, const QgsServerApiContext &context, const QString &contentType ) const
178 {
179  // Do not append timestamp to openapi
180  if ( ! QgsServerOgcApi::contentTypeMimes().value( QgsServerOgcApi::ContentType::OPENAPI3 ).contains( contentType, Qt::CaseSensitivity::CaseInsensitive ) )
181  {
182  QDateTime time { QDateTime::currentDateTime() };
183  time.setTimeSpec( Qt::TimeSpec::UTC );
184  data["timeStamp"] = time.toString( Qt::DateFormat::ISODate ).toStdString() ;
185  }
186  context.response()->setStatusCode( 200 );
187  context.response()->setHeader( QStringLiteral( "Content-Type" ), contentType );
188 #ifdef QGISDEBUG
189  context.response()->write( data.dump( 2 ) );
190 #else
191  context.response()->write( data.dump( ) );
192 #endif
193 }
194 
196 {
197  Q_UNUSED( context )
198  return nullptr;
199 }
200 
201 json QgsServerOgcApiHandler::link( const QgsServerApiContext &context, const QgsServerOgcApi::Rel &linkType, const QgsServerOgcApi::ContentType contentType, const std::string &title ) const
202 {
203  json l
204  {
205  {
206  "href", href( context, "/",
208  },
209  { "rel", QgsServerOgcApi::relToString( linkType ) },
210  { "type", QgsServerOgcApi::mimeType( contentType ) },
211  { "title", title != "" ? title : linkTitle() },
212  };
213  return l;
214 }
215 
217 {
218  const QgsServerOgcApi::ContentType currentCt { contentTypeFromRequest( context.request() ) };
219  json links = json::array();
220  const QList<QgsServerOgcApi::ContentType> constCts { contentTypes() };
221  for ( const auto &ct : constCts )
222  {
223  links.push_back( link( context, ( ct == currentCt ? QgsServerOgcApi::Rel::self :
224  QgsServerOgcApi::Rel::alternate ), ct,
225  linkTitle() + " as " + QgsServerOgcApi::contentTypeToStdString( ct ) ) );
226  }
227  return links;
228 }
229 
231 {
232  if ( ! context.project() )
233  {
234  throw QgsServerApiImproperlyConfiguredException( QStringLiteral( "Project is invalid or undefined" ) );
235  }
236  // Check collectionId
237  const QRegularExpressionMatch match { path().match( context.request()->url().path( ) ) };
238  if ( ! match.hasMatch() )
239  {
240  throw QgsServerApiNotFoundError( QStringLiteral( "Collection was not found" ) );
241  }
242  const QString collectionId { match.captured( QStringLiteral( "collectionId" ) ) };
243  // May throw if not found
244  return layerFromCollectionId( context, collectionId );
245 
246 }
247 
248 const QString QgsServerOgcApiHandler::staticPath( const QgsServerApiContext &context ) const
249 {
250  // resources/server/api + /static
251  return context.serverInterface()->serverSettings()->apiResourcesDirectory() + QStringLiteral( "/ogc/static" );
252 }
253 
254 const QString QgsServerOgcApiHandler::templatePath( const QgsServerApiContext &context ) const
255 {
256  // resources/server/api + /ogc/templates/ + operationId + .html
257  QString path { context.serverInterface()->serverSettings()->apiResourcesDirectory() };
258  path += QStringLiteral( "/ogc/templates" );
259  path += context.apiRootPath();
260  path += '/';
261  path += QString::fromStdString( operationId() );
262  path += QStringLiteral( ".html" );
263  return path;
264 }
265 
266 
267 void QgsServerOgcApiHandler::htmlDump( const json &data, const QgsServerApiContext &context ) const
268 {
269  context.response()->setHeader( QStringLiteral( "Content-Type" ), QStringLiteral( "text/html" ) );
270  auto path { templatePath( context ) };
271  if ( ! QFile::exists( path ) )
272  {
273  QgsMessageLog::logMessage( QStringLiteral( "Template not found error: %1" ).arg( path ), QStringLiteral( "Server" ), Qgis::Critical );
274  throw QgsServerApiBadRequestException( QStringLiteral( "Template not found: %1" ).arg( QFileInfo( path ).fileName() ) );
275  }
276 
277  QFile f( path );
278  if ( ! f.open( QFile::ReadOnly | QFile::Text ) )
279  {
280  QgsMessageLog::logMessage( QStringLiteral( "Could not open template file: %1" ).arg( path ), QStringLiteral( "Server" ), Qgis::Critical );
281  throw QgsServerApiInternalServerError( QStringLiteral( "Could not open template file: %1" ).arg( QFileInfo( path ).fileName() ) );
282  }
283 
284  try
285  {
286  // Get the template directory and the file name
287  QFileInfo pathInfo { path };
288  Environment env { ( pathInfo.dir().path() + QDir::separator() ).toStdString() };
289 
290  // For template debugging:
291  env.add_callback( "json_dump", 0, [ = ]( Arguments & )
292  {
293  return data.dump();
294  } );
295 
296  // Path manipulation: appends a directory path to the current url
297  env.add_callback( "path_append", 1, [ = ]( Arguments & args )
298  {
299  auto url { context.request()->url() };
300  QFileInfo fi{ url.path() };
301  auto suffix { fi.suffix() };
302  auto fName { fi.filePath()};
303  fName.chop( suffix.length() + 1 );
304  fName += '/' + QString::number( args.at( 0 )->get<QgsFeatureId>( ) );
305  if ( !suffix.isEmpty() )
306  {
307  fName += '.' + suffix;
308  }
309  fi.setFile( fName );
310  url.setPath( fi.filePath() );
311  return url.toString().toStdString();
312  } );
313 
314  // Path manipulation: removes the specified number of directory components from the current url path
315  env.add_callback( "path_chomp", 1, [ = ]( Arguments & args )
316  {
317  QUrl url { QString::fromStdString( args.at( 0 )->get<std::string>( ) ) };
318  QFileInfo fi{ url.path() };
319  auto suffix { fi.suffix() };
320  auto fName { fi.filePath()};
321  fName.chop( suffix.length() + 1 );
322  // Chomp last segment
323  fName = fName.replace( QRegularExpression( R"raw(\/[^/]+$)raw" ), QString() );
324  if ( !suffix.isEmpty() )
325  {
326  fName += '.' + suffix;
327  }
328  fi.setFile( fName );
329  url.setPath( fi.filePath() );
330  return url.toString().toStdString();
331  } );
332 
333  // Returns filtered links from a link list
334  // links_filter( <links>, <key>, <value> )
335  env.add_callback( "links_filter", 3, [ = ]( Arguments & args )
336  {
337  json links = args.at( 0 )->get<json>( );
338  if ( ! links.is_array() )
339  {
340  links = json::array();
341  }
342  std::string key { args.at( 1 )->get<std::string>( ) };
343  std::string value { args.at( 2 )->get<std::string>( ) };
344  json result = json::array();
345  for ( const auto &l : links )
346  {
347  if ( l[key] == value )
348  {
349  result.push_back( l );
350  }
351  }
352  return result;
353  } );
354 
355  // Returns a short name from content types
356  env.add_callback( "content_type_name", 1, [ = ]( Arguments & args )
357  {
358  const QgsServerOgcApi::ContentType ct { QgsServerOgcApi::contenTypeFromExtension( args.at( 0 )->get<std::string>( ) ) };
360  } );
361 
362 
363  // Static: returns the full URL to the specified static <path>
364  env.add_callback( "static", 1, [ = ]( Arguments & args )
365  {
366  auto asset( args.at( 0 )->get<std::string>( ) );
367  return context.matchedPath().toStdString() + "/static/" + asset;
368  } );
369 
370  context.response()->write( env.render_file( pathInfo.fileName().toStdString(), data ) );
371  }
372  catch ( std::exception &e )
373  {
374  QgsMessageLog::logMessage( QStringLiteral( "Error parsing template file: %1 - %2" ).arg( path, e.what() ), QStringLiteral( "Server" ), Qgis::Critical );
375  throw QgsServerApiInternalServerError( QStringLiteral( "Error parsing template file: %1" ).arg( e.what() ) );
376  }
377 }
378 
380 {
381  // Fallback to default
382  QgsServerOgcApi::ContentType result { defaultContentType() };
383  bool found { false };
384  // First file extension ...
385  const QString extension { QFileInfo( request->url().path() ).completeSuffix().toUpper() };
386  if ( ! extension.isEmpty() )
387  {
388  static QMetaEnum metaEnum { QMetaEnum::fromType<QgsServerOgcApi::ContentType>() };
389  bool ok { false };
390  const int ct { metaEnum.keyToValue( extension.toLocal8Bit().constData(), &ok ) };
391  if ( ok )
392  {
393  result = static_cast<QgsServerOgcApi::ContentType>( ct );
394  found = true;
395  }
396  else
397  {
398  QgsMessageLog::logMessage( QStringLiteral( "The client requested an unsupported extension: %1" ).arg( extension ), QStringLiteral( "Server" ), Qgis::Warning );
399  }
400  }
401  // ... then "Accept"
402  const QString accept { request->header( QStringLiteral( "Accept" ) ) };
403  if ( ! found && ! accept.isEmpty() )
404  {
405  const QString ctFromAccept { contentTypeForAccept( accept ) };
406  if ( ! ctFromAccept.isEmpty() )
407  {
408  auto it = QgsServerOgcApi::contentTypeMimes().constBegin();
409  while ( ! found && it != QgsServerOgcApi::contentTypeMimes().constEnd() )
410  {
411  int idx = it.value().indexOf( ctFromAccept );
412  if ( idx >= 0 )
413  {
414  found = true;
415  result = it.key();
416  }
417  it++;
418  }
419  }
420  else
421  {
422  QgsMessageLog::logMessage( QStringLiteral( "The client requested an unsupported content type in Accept header: %1" ).arg( accept ), QStringLiteral( "Server" ), Qgis::Warning );
423  }
424  }
425  // Validation: check if the requested content type (or an alias) is supported by the handler
426  if ( ! contentTypes().contains( result ) )
427  {
428  // Check aliases
429  bool found { false };
430  if ( QgsServerOgcApi::contentTypeAliases().keys().contains( result ) )
431  {
432  const QList<QgsServerOgcApi::ContentType> constCt { contentTypes() };
433  for ( const auto &ct : constCt )
434  {
435  if ( QgsServerOgcApi::contentTypeAliases()[result].contains( ct ) )
436  {
437  result = ct;
438  found = true;
439  break;
440  }
441  }
442  }
443 
444  if ( ! found )
445  {
446  QgsMessageLog::logMessage( QStringLiteral( "Unsupported Content-Type: %1" ).arg( QgsServerOgcApi::contentTypeToString( result ) ), QStringLiteral( "Server" ), Qgis::Info );
447  throw QgsServerApiBadRequestException( QStringLiteral( "Unsupported Content-Type: %1" ).arg( QgsServerOgcApi::contentTypeToString( result ) ) );
448  }
449  }
450  return result;
451 }
452 
453 QString QgsServerOgcApiHandler::parentLink( const QUrl &url, int levels )
454 {
455  QString path { url.path() };
456  const QFileInfo fi { path };
457  const QString suffix { fi.suffix() };
458  if ( ! suffix.isEmpty() )
459  {
460  path.chop( suffix.length() + 1 );
461  }
462  while ( path.endsWith( '/' ) )
463  {
464  path.chop( 1 );
465  }
466  QRegularExpression re( R"raw(\/[^/]+$)raw" );
467  for ( int i = 0; i < levels ; i++ )
468  {
469  path = path.replace( re, QString() );
470  }
471  QUrl result( url );
472  QList<QPair<QString, QString> > qi;
473  const auto constItems { result.queryItems( ) };
474  for ( const auto &i : constItems )
475  {
476  if ( i.first.compare( QStringLiteral( "MAP" ), Qt::CaseSensitivity::CaseInsensitive ) == 0 )
477  {
478  qi.push_back( i );
479  }
480  }
481  result.setQueryItems( qi );
482  result.setPath( path );
483  return result.toString();
484 }
485 
487 {
488  const auto mapLayers { context.project()->mapLayersByShortName<QgsVectorLayer *>( collectionId ) };
489  if ( mapLayers.count() != 1 )
490  {
491  throw QgsServerApiNotFoundError( QStringLiteral( "Collection with given id (%1) was not found or multiple matches were found" ).arg( collectionId ) );
492  }
493  return mapLayers.first();
494 }
495 
497 {
498  static json defRes =
499  {
500  { "description", "An error occurred." },
501  {
502  "content", {
503  {
504  "application/json", {
505  {
506  "schema", {
507  { "$ref", "#/components/schemas/exception" }
508  }
509  }
510  }
511  },
512  {
513  "text/html", {
514  {
515  "schema", {
516  { "type", "string" }
517  }
518  }
519  }
520  }
521  }
522  }
523  };
524  return defRes;
525 }
526 
528 {
529  return QgsJsonUtils::jsonFromVariant( tags() );
530 }
531 
532 void QgsServerOgcApiHandler::setContentTypesInt( const QList<int> &contentTypes )
533 {
534  mContentTypes.clear();
535  for ( const int &i : qgis::as_const( contentTypes ) )
536  {
537  mContentTypes.push_back( static_cast<QgsServerOgcApi::ContentType>( i ) );
538  }
539 }
540 
541 void QgsServerOgcApiHandler::setContentTypes( const QList<QgsServerOgcApi::ContentType> &contentTypes )
542 {
543  mContentTypes = contentTypes;
544 }
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...
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...
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
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...
Not found error API exception.
virtual QgsServerSettings * serverSettings()=0
Returns the server settings.
const QgsProject * project() const
Returns the (possibly NULL) project.
QString header(const QString &name) const
Returns the header value.
void htmlDump(const json &data, const QgsServerApiContext &context) const
Writes data as HTML to the response stream in context using a template.
static QString contentTypeToString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
QgsServerResponse * response() const
Returns the server response object.
QString apiRootPath() const
Returns the API root path.
QList< QgsMapLayer * > mapLayersByShortName(const QString &shortName) const
Retrieves a list of matching registered layers by layer shortName.
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
static const QHash< QgsServerOgcApi::ContentType, QList< QgsServerOgcApi::ContentType > > contentTypeAliases()
Returns contenType specializations (e.g.
QgsServerOgcApi::ContentType contentTypeFromRequest(const QgsServerRequest *request) const
Returns the content type from the request.
static std::string relToString(const QgsServerOgcApi::Rel &rel)
Returns the string representation of rel attribute.
static QgsServerOgcApi::ContentType contenTypeFromExtension(const std::string &extension)
Returns the Content-Type value corresponding to extension.
void setContentTypesInt(const QList< int > &contentTypes)
Set the content types to contentTypes.
virtual void write(const QString &data)
Write string This is a convenient method that will write directly to the underlying I/O device...
Internal server error API exception.
static const QMap< QgsServerOgcApi::ContentType, QStringList > contentTypeMimes()
Returns a map of contentType => list of mime types.
QgsServerInterface * serverInterface() const
Returns the server interface.
ContentType
Media types used for content negotiation, insert more specific first.
this method is not yet implemented
static json defaultResponse()
Returns the defaultResponse as JSON.
virtual void setStatusCode(int code)=0
Set the http status code.
static std::string contentTypeToStdString(const QgsServerOgcApi::ContentType &ct)
Returns the string representation of a ct (Content-Type) attribute.
configuration error on the server prevents to serve the request, which would be valid otherwise...
static std::string mimeType(const QgsServerOgcApi::ContentType &contentType)
Returns the mime-type for the contentType or an empty string if not found.
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.
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...
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
void setContentTypes(const QList< QgsServerOgcApi::ContentType > &contentTypes)
Set the content types to contentTypes.
virtual QgsServerOgcApi::ContentType defaultContentType() const
Returns the default response content type in case the client did not specifically ask for any particu...
json links(const QgsServerApiContext &context) const
Returns all the links for the given request context.
static QUrl sanitizeUrl(const QUrl &url)
Returns a sanitized url with extra slashes removed.
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...
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 ...
The QgsServerApiContext class encapsulates the resources for a particular client request: the request...
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
Bad request error API exception.
virtual QVariantMap values(const QgsServerApiContext &context) const SIP_THROW(QgsServerApiBadRequestException)
Analyzes the incoming request context and returns the validated parameter map, throws QgsServerApiBad...
QList< QgsServerOgcApi::ContentType > contentTypes() const
Returns the list of content types this handler can serve, default to JSON and HTML.
Rel
Rel link types.
QString apiResourcesDirectory() const
Returns the server-wide base directory where HTML templates and static assets (e.g.
static QString contentTypeToExtension(const QgsServerOgcApi::ContentType &ct)
Returns the file extension for a ct (Content-Type).
const QString matchedPath() const
Returns the initial part of the incoming request URL path that matches the API root path...
virtual void handleRequest(const QgsServerApiContext &context) const SIP_THROW(QgsServerApiBadRequestException)
Handles the request within its context.
json jsonTags() const
Returns tags as JSON.
QString contentTypeForAccept(const QString &accept) const
Looks for the first ContentType match in the accept header and returns its mime type, returns an empty string if there are not matches.
QgsVectorLayer * layerFromContext(const QgsServerApiContext &context) const
Returns a vector layer instance from the "collectionId" parameter of the path in the given context...
Represents a vector layer which manages a vector based data sets.
virtual const QString templatePath(const QgsServerApiContext &context) const
Returns the HTML template path for the handler in the given context.
static QgsVectorLayer * layerFromCollectionId(const QgsServerApiContext &context, const QString &collectionId)
Returns a vector layer from the collectionId in the given context.
virtual json schema(const QgsServerApiContext &context) const
Returns handler information from the context for the OPENAPI description (id, description and other m...
const QgsServerRequest * request() const
Returns the server request object.