From ff37102ebf8668e0e40ad986763c57e158be0aa6 Mon Sep 17 00:00:00 2001 From: Gaute Lindkvist Date: Thu, 14 Mar 2024 14:09:34 +0000 Subject: [PATCH] Use request system for object services --- RestInterface/cafRestObjectService.cpp | 574 +++++++++++++++---------- RestInterface/cafRestObjectService.h | 39 +- RestInterface/cafRestRequest.cpp | 23 +- 3 files changed, 399 insertions(+), 237 deletions(-) diff --git a/RestInterface/cafRestObjectService.cpp b/RestInterface/cafRestObjectService.cpp index 94a2dcd5..de11d10c 100644 --- a/RestInterface/cafRestObjectService.cpp +++ b/RestInterface/cafRestObjectService.cpp @@ -34,85 +34,219 @@ #include "cafRestServerApplication.h" #include "cafRpcClientPassByRefObjectFactory.h" #include "cafRpcObjectConversion.h" +#include "cafUuidGenerator.h" #include #include #include +using namespace caffa; using namespace caffa::rpc; -RestObjectService::ServiceResponse RestObjectService::perform( http::verb verb, - std::list path, - const nlohmann::json& queryParams, - const nlohmann::json& body ) +using namespace std::placeholders; + +RestObjectService::RestObjectService() { - caffa::SessionMaintainer session; + m_requestPathRoot = std::make_unique( "objects" ); - CAFFA_ASSERT( !path.empty() ); + auto skeletonParameter = std::make_unique>( "skeleton", + RestParameter::Location::QUERY, + false, + "Whether to only send the skeleton" ); + skeletonParameter->setDefaultValue( false ); - if ( queryParams.contains( "session_uuid" ) ) - { - auto session_uuid = queryParams["session_uuid"].get(); - session = RestServerApplication::instance()->getExistingSession( session_uuid ); - } + auto uuidEntry = std::make_unique( "{uuid}" ); + uuidEntry->setPathArgumentMatcher( &caffa::UuidGenerator::isUuid ); + auto uuidParameter = std::make_unique>( "uuid", + RestParameter::Location::PATH, + true, + "The UUID of the object" ); - path.pop_front(); + auto getAction = + std::make_unique( http::verb::get, "Get object by UUID", "getObject", &RestObjectService::object ); - if ( path.empty() ) - { - return std::make_tuple( http::status::bad_request, "Object uuid not specified", nullptr ); - } + getAction->addResponse( http::status::ok, + std::make_unique( anyObjectResponseContent(), "Specific object" ) ); - auto uuid = path.front(); - path.pop_front(); + getAction->addResponse( http::status::unknown, RestResponse::plainErrorResponse() ); + getAction->addParameter( uuidParameter->clone() ); + getAction->addParameter( skeletonParameter->clone() ); + getAction->setRequiresAuthentication( false ); + getAction->setRequiresSession( true ); - CAFFA_TRACE( "Trying to look for uuid '" << uuid << "'" ); + uuidEntry->addAction( std::move( getAction ) ); + + auto fieldEntry = std::make_unique( "fields" ); + auto fieldKeywordEntry = std::make_unique( "{fieldKeyword}" ); + fieldKeywordEntry->setPathArgumentMatcher( &caffa::ObjectHandle::isValidKeyword ); + + auto fieldKeywordParameter = std::make_unique>( "fieldKeyword", + RestParameter::Location::PATH, + true, + "The keyword of the field" ); + + auto indexParameter = std::make_unique>( "index", + RestParameter::Location::QUERY, + false, + "The index of the field (for array fields)" ); - auto object = findObject( uuid, session.get() ); - if ( !object ) { - return std::make_tuple( http::status::not_found, "Object " + uuid + " not found", nullptr ); + auto fieldGetAction = std::make_unique( http::verb::get, + "Get field value", + "getFieldValue", + &RestObjectService::performFieldOperation ); + fieldGetAction->addResponse( http::status::ok, + std::make_unique( anyFieldResponseContent(), "Specific field" ) ); + fieldGetAction->addResponse( http::status::unknown, RestResponse::plainErrorResponse() ); + fieldGetAction->addParameter( fieldKeywordParameter->clone() ); + fieldGetAction->addParameter( indexParameter->clone() ); + fieldGetAction->addParameter( skeletonParameter->clone() ); + fieldGetAction->setRequiresAuthentication( false ); + fieldGetAction->setRequiresSession( true ); + fieldKeywordEntry->addAction( std::move( fieldGetAction ) ); + } + { + auto fieldPutAction = std::make_unique( http::verb::put, + "Set field value", + "replaceFieldValue", + &RestObjectService::performFieldOperation ); + fieldPutAction->addResponse( http::status::accepted, RestResponse::emptyResponse( "Success" ) ); + fieldPutAction->addResponse( http::status::unknown, RestResponse::plainErrorResponse() ); + fieldPutAction->addParameter( fieldKeywordParameter->clone() ); + fieldPutAction->addParameter( indexParameter->clone() ); + fieldPutAction->setRequiresAuthentication( false ); + fieldPutAction->setRequiresSession( true ); + + auto requestBody = nlohmann::json::object(); + requestBody["content"] = anyFieldResponseContent(); + + fieldPutAction->setRequestBodySchema( requestBody ); + fieldKeywordEntry->addAction( std::move( fieldPutAction ) ); } + { + auto fieldPostAction = std::make_unique( http::verb::post, + "Insert into field", + "insertFieldValue", + &RestObjectService::performFieldOperation ); + fieldPostAction->addResponse( http::status::accepted, RestResponse::emptyResponse( "Success" ) ); + fieldPostAction->addResponse( http::status::unknown, RestResponse::plainErrorResponse() ); + fieldPostAction->addParameter( fieldKeywordParameter->clone() ); + fieldPostAction->addParameter( indexParameter->clone() ); + fieldPostAction->setRequiresAuthentication( false ); + fieldPostAction->setRequiresSession( true ); + + auto requestBody = nlohmann::json::object(); + requestBody["content"] = anyFieldResponseContent(); + + fieldPostAction->setRequestBodySchema( requestBody ); + fieldKeywordEntry->addAction( std::move( fieldPostAction ) ); + } + { + auto fieldDeleteAction = std::make_unique( http::verb::delete_, + "Delete from field", + "deleteFieldValue", + &RestObjectService::performFieldOperation ); + fieldDeleteAction->addResponse( http::status::accepted, RestResponse::emptyResponse( "Success" ) ); + fieldDeleteAction->addResponse( http::status::unknown, RestResponse::plainErrorResponse() ); + fieldDeleteAction->addParameter( fieldKeywordParameter->clone() ); + fieldDeleteAction->addParameter( indexParameter->clone() ); + fieldDeleteAction->setRequiresAuthentication( false ); + fieldDeleteAction->setRequiresSession( true ); + + fieldKeywordEntry->addAction( std::move( fieldDeleteAction ) ); + } + fieldEntry->addEntry( std::move( fieldKeywordEntry ) ); + + auto methodEntry = std::make_unique( "methods" ); + auto methodKeywordEntry = std::make_unique( "{methodKeyword}" ); + methodKeywordEntry->setPathArgumentMatcher( &caffa::ObjectHandle::isValidKeyword ); + auto methodKeywordParameter = std::make_unique>( "methodKeyword", + RestParameter::Location::PATH, + true, + "The keyword of the method" ); - CAFFA_ASSERT( object ); - if ( path.empty() ) { - bool skeleton = queryParams.contains( "skeleton" ) && body["skeleton"].get(); - if ( skeleton ) - { - return std::make_tuple( http::status::ok, createJsonSkeletonFromProjectObject( object ).dump(), nullptr ); - } - else - { - return std::make_tuple( http::status::ok, createJsonFromProjectObject( object ).dump(), nullptr ); - } + auto methodExecuteAction = std::make_unique( http::verb::post, + "Execute method", + "executeMethod", + &RestObjectService::executeMethod ); + + methodExecuteAction->addResponse( http::status::ok, + std::make_unique( anyFieldResponseContent(), "Success" ) ); + methodExecuteAction->addResponse( http::status::unknown, RestResponse::plainErrorResponse() ); + methodExecuteAction->addParameter( methodKeywordParameter->clone() ); + methodExecuteAction->setRequiresAuthentication( false ); + methodExecuteAction->setRequiresSession( true ); + + auto methodContent = nlohmann::json{ { "application/json", { { "schema", nlohmann::json::object() } } } }; + + auto methodBody = nlohmann::json{ { "description", "JSON content representing the parameters of the method" }, + { "content", methodContent } }; + + methodExecuteAction->setRequestBodySchema( methodBody ); + methodKeywordEntry->addAction( std::move( methodExecuteAction ) ); + methodEntry->addEntry( std::move( methodKeywordEntry ) ); } - auto fieldOrMethod = path.front(); - path.pop_front(); + uuidEntry->addEntry( std::move( fieldEntry ) ); + uuidEntry->addEntry( std::move( methodEntry ) ); + + m_requestPathRoot->addEntry( std::move( uuidEntry ) ); +} - if ( path.empty() ) +nlohmann::json RestObjectService::anyObjectResponseContent() +{ + auto objectContent = nlohmann::json::object(); + auto classArray = nlohmann::json::array(); + for ( auto classKeyword : DefaultObjectFactory::instance()->classes() ) { - return std::make_tuple( http::status::bad_request, "No field or method keyword specified", nullptr ); + auto schemaRef = nlohmann::json{ { "$ref", "#/components/object_schemas/" + classKeyword } }; + classArray.push_back( schemaRef ); } + auto classSchema = nlohmann::json{ { "oneOf", classArray }, { "discriminator", "keyword" } }; + objectContent["application/json"] = { { "schema", classSchema } }; + return objectContent; +} - auto keyword = path.front(); - path.pop_front(); +nlohmann::json RestObjectService::anyFieldResponseContent() +{ + auto objectContent = nlohmann::json::object(); + auto classArray = nlohmann::json::array(); + for ( auto classKeyword : DefaultObjectFactory::instance()->classes() ) + { + auto schemaRef = nlohmann::json{ { "$ref", "#/components/object_schemas/" + classKeyword } }; + classArray.push_back( schemaRef ); + } - if ( fieldOrMethod == "fields" ) + auto oneOf = nlohmann::json::array(); + for ( auto dataType : caffa::rpc::ClientPassByRefObjectFactory::instance()->supportedDataTypes() ) { - return performFieldOperation( *session, verb, object, keyword, queryParams, body ); + oneOf.push_back( nlohmann::json::parse( dataType ) ); } - else if ( fieldOrMethod == "methods" ) + for ( auto classEntry : classArray ) { - return performMethodOperation( *session, verb, object, keyword, queryParams, body ); + oneOf.push_back( classEntry ); + auto array = nlohmann::json{ { "type", "array" }, { "items", classEntry } }; + oneOf.push_back( array ); } - else + return nlohmann::json{ { "application/json", { { "schema", { { "oneOf", oneOf } } } } } }; +} + +RestObjectService::ServiceResponse RestObjectService::perform( http::verb verb, + std::list path, + const nlohmann::json& queryParams, + const nlohmann::json& body ) +{ + CAFFA_ASSERT( !path.empty() ); + + auto [request, pathArguments] = m_requestPathRoot->findPathEntry( path ); + if ( !request ) { - return std::make_tuple( http::status::bad_request, - "No such target " + fieldOrMethod + " available for Objects", - nullptr ); + return std::make_tuple( http::status::bad_request, "Path not found", nullptr ); } + CAFFA_DEBUG( "Found request handler: " << request->name() ); + + return request->perform( verb, pathArguments, queryParams, body ); } //-------------------------------------------------------------------------------------------------- @@ -120,149 +254,41 @@ RestObjectService::ServiceResponse RestObjectService::perform( http::verb //-------------------------------------------------------------------------------------------------- bool RestObjectService::requiresAuthentication( http::verb verb, const std::list& path ) const { - return false; + auto [request, pathArguments] = m_requestPathRoot->findPathEntry( path ); + if ( !request ) + { + return true; + } + return request->requiresAuthentication( verb ); } bool RestObjectService::requiresSession( http::verb verb, const std::list& path ) const { - return true; + auto [request, pathArguments] = m_requestPathRoot->findPathEntry( path ); + if ( !request ) + { + return true; + } + return request->requiresSession( verb ); } std::map RestObjectService::servicePathEntries() const { - auto emptyResponseContent = nlohmann::json{ { "description", "Success" } }; + CAFFA_DEBUG( "Get service path entries" ); - auto acceptedOrFailureResponses = nlohmann::json{ { HTTP_ACCEPTED, emptyResponseContent }, - { "default", RestServiceInterface::plainErrorResponse() } }; + auto services = nlohmann::json::object(); - auto uuidParameter = nlohmann::json{ { "name", "uuid" }, - { "in", "path" }, - { "required", true }, - { "description", "The object UUID of the object to get" }, - { "schema", { { "type", "string" } } } }; + RequestFinder finder( m_requestPathRoot.get() ); + finder.search(); - auto objectContent = nlohmann::json::object(); - auto classArray = nlohmann::json::array(); - for ( auto classKeyword : DefaultObjectFactory::instance()->classes() ) + CAFFA_DEBUG( "Got " << finder.allPathEntriesWithActions().size() << " service path entries" ); + + for ( const auto& [path, request] : finder.allPathEntriesWithActions() ) { - auto schemaRef = nlohmann::json{ { "$ref", "#/components/object_schemas/" + classKeyword } }; - classArray.push_back( schemaRef ); + CAFFA_DEBUG( "Got path: " << path ); + services[path] = request->schema(); } - auto classSchema = nlohmann::json{ { "oneOf", classArray }, { "discriminator", "keyword" } }; - objectContent["application/json"] = { { "schema", classSchema } }; - auto objectResponse = nlohmann::json{ { "description", "Specific object" }, { "content", objectContent } }; - - auto getResponses = - nlohmann::json{ { HTTP_OK, objectResponse }, { "default", RestServiceInterface::plainErrorResponse() } }; - - auto object = nlohmann::json::object(); - object["get"] = - createOperation( "getObject", "Get a particular object", uuidParameter, getResponses, nullptr, { "objects" } ); - object["delete"] = createOperation( "deleteObject", - "Destroy a particular object", - uuidParameter, - acceptedOrFailureResponses, - nullptr, - { "objects" } ); - - auto field = nlohmann::json::object(); - { - auto fieldKeywordParameter = nlohmann::json{ { "name", "fieldKeyword" }, - { "in", "path" }, - { "required", true }, - { "description", "The field keyword" }, - { "schema", { { "type", "string" } } } }; - - auto indexParameter = nlohmann::json{ { "name", "index" }, - { "in", "query" }, - { "required", false }, - { "default", -1 }, - { "description", "The index of the child object field." }, - { "schema", { { "type", "integer" } } } }; - auto skeletonParameter = - nlohmann::json{ { "name", "skeleton" }, - { "in", "query" }, - { "required", false }, - { "default", false }, - { "description", "Whether to only retrieve the structure and not field values" }, - { "schema", { { "type", "boolean" } } } }; - - auto oneOf = nlohmann::json::array(); - for ( auto dataType : caffa::rpc::ClientPassByRefObjectFactory::instance()->supportedDataTypes() ) - { - oneOf.push_back( nlohmann::json::parse( dataType ) ); - } - for ( auto classEntry : classArray ) - { - oneOf.push_back( classEntry ); - auto array = nlohmann::json{ { "type", "array" }, { "items", classEntry } }; - oneOf.push_back( array ); - } - - auto fieldValue = nlohmann::json{ { "application/json", { { "schema", { { "oneOf", oneOf } } } } } }; - auto fieldContent = nlohmann::json{ { "description", "JSON content representing a valid Caffa data type" }, - { "content", fieldValue } }; - - auto fieldParameters = nlohmann::json::array( { uuidParameter, fieldKeywordParameter, indexParameter } ); - auto fieldResponses = - nlohmann::json{ { HTTP_OK, fieldContent }, { "default", RestServiceInterface::plainErrorResponse() } }; - - field["put"] = createOperation( "replaceFieldValue", - "Replace a particular field value", - fieldParameters, - acceptedOrFailureResponses, - fieldContent, - { "fields" } ); - field["post"] = createOperation( "insertFieldValue", - "Insert a particular field value", - fieldParameters, - acceptedOrFailureResponses, - fieldContent, - { "fields" } ); - field["delete"] = createOperation( "deleteFieldValue", - "Delete a value from a field. For array fields.", - fieldParameters, - acceptedOrFailureResponses, - nullptr, - { "fields" } ); - - fieldParameters.push_back( skeletonParameter ); - field["get"] = createOperation( "getFieldValue", - "Get a particular field value", - fieldParameters, - fieldResponses, - nullptr, - { "fields" } ); - } - - auto method = nlohmann::json::object(); - { - auto methodKeywordParameter = nlohmann::json{ { "name", "methodKeyword" }, - { "in", "path" }, - { "required", true }, - { "description", "The method keyword" }, - { "schema", { { "type", "string" } } } }; - - auto methodBody = nlohmann::json{ { "application/json", { { "schema", nlohmann::json::object() } } } }; - - auto jsonContentObject = nlohmann::json{ { "description", "JSON content representing a valid Caffa data type" }, - { "content", methodBody } }; - - auto methodParameters = nlohmann::json::array( { uuidParameter, methodKeywordParameter } ); - auto methodResponses = - nlohmann::json{ { HTTP_OK, jsonContentObject }, { "default", RestServiceInterface::plainErrorResponse() } }; - - method["post"] = createOperation( "executeMethod", - "Execute an Object Method", - methodParameters, - methodResponses, - jsonContentObject, - { "methods" } ); - } - - return { { "/objects/{uuid}", object }, - { "/objects/{uuid}/fields/{fieldKeyword}", field }, - { "/objects/{uuid}/methods/{methodKeyword}", method } }; + return services; } std::map RestObjectService::serviceComponentEntries() const @@ -279,25 +305,74 @@ std::map RestObjectService::serviceComponentEntries return { { "object_schemas", schemas } }; } -caffa::ObjectHandle* RestObjectService::findObject( const std::string& uuid, const caffa::Session* session ) +std::pair + RestObjectService::findObject( std::list& pathArguments, const nlohmann::json& queryParams ) { - return findCafObjectFromUuid( session, uuid ); + caffa::SessionMaintainer session; + + if ( pathArguments.empty() ) + { + return std::make_pair( nullptr, std::make_tuple( http::status::bad_request, "Object uuid not specified", nullptr ) ); + } + + auto uuid = pathArguments.front(); + pathArguments.pop_front(); + + if ( queryParams.contains( "session_uuid" ) ) + { + auto session_uuid = queryParams["session_uuid"].get(); + session = RestServerApplication::instance()->getExistingSession( session_uuid ); + } + + if ( !session || session->isExpired() ) + { + return std::make_pair( nullptr, std::make_tuple( http::status::forbidden, "No valid session provided", nullptr ) ); + } + + CAFFA_TRACE( "Trying to look for uuid '" << uuid << "'" ); + + auto object = findCafObjectFromUuid( session.get(), uuid ); + if ( !object ) + { + return std::make_pair( nullptr, + std::make_tuple( http::status::not_found, "Object " + uuid + " not found", nullptr ) ); + } + + return std::make_pair( object, std::make_tuple( http::status::ok, "", nullptr ) ); } -RestObjectService::ServiceResponse RestObjectService::performFieldOperation( std::shared_ptr session, - http::verb verb, - caffa::ObjectHandle* object, - const std::string& keyword, +RestObjectService::ServiceResponse RestObjectService::performFieldOperation( http::verb verb, + const std::list& pathArguments, const nlohmann::json& queryParams, const nlohmann::json& body ) { - if ( auto field = object->findField( keyword ); field ) + CAFFA_DEBUG( "Full arguments for field operation: " + << caffa::StringTools::join( pathArguments.begin(), pathArguments.end(), "/" ) ); + + auto arguments = pathArguments; + auto [object, response] = findObject( arguments, queryParams ); + if ( !object ) + { + return response; + } + + if ( arguments.empty() ) { - CAFFA_DEBUG( "Found field: " << field->keyword() ); + return std::make_tuple( http::status::bad_request, "field keyword not provided", nullptr ); + } + CAFFA_DEBUG( "Got arguments:" ); + for ( auto arg : arguments ) + { + CAFFA_DEBUG( " /" << arg ); + } + + auto keyword = arguments.front(); + + if ( auto field = object->findField( keyword ); field ) + { int index = queryParams.contains( "index" ) ? queryParams["index"].get() : -1; bool skeleton = queryParams.contains( "skeleton" ) && queryParams["skeleton"].get(); - if ( verb == http::verb::get ) { return getFieldValue( field, index, skeleton ); @@ -317,25 +392,8 @@ RestObjectService::ServiceResponse RestObjectService::performFieldOperation( std } return std::make_tuple( http::status::bad_request, "Verb not implemented", nullptr ); } - return std::make_tuple( http::status::not_found, "No field named " + keyword + " found", nullptr ); -} - -RestObjectService::ServiceResponse RestObjectService::performMethodOperation( std::shared_ptr session, - http::verb verb, - caffa::ObjectHandle* object, - const std::string& keyword, - const nlohmann::json& queryParams, - const nlohmann::json& body ) -{ - if ( auto method = object->findMethod( keyword ); method ) - { - CAFFA_TRACE( "Found method: " << method->keyword() ); - - auto result = method->execute( session, body.dump() ); - return std::make_tuple( http::status::ok, result, nullptr ); - } - return std::make_tuple( http::status::not_found, "No method named " + keyword + " found", nullptr ); + return std::make_tuple( http::status::not_found, "No field named " + keyword + " found", nullptr ); } RestObjectService::ServiceResponse @@ -393,13 +451,13 @@ RestObjectService::ServiceResponse } RestObjectService::ServiceResponse - RestObjectService::replaceFieldValue( caffa::FieldHandle* field, int64_t index, const nlohmann::json& body ) + RestObjectService::replaceFieldValue( FieldHandle* field, int64_t index, const nlohmann::json& body ) { - auto scriptability = field->capability(); + auto scriptability = field->capability(); if ( !scriptability || !scriptability->isWritable() ) return std::make_tuple( http::status::forbidden, "Field " + field->keyword() + " is not remote writable", nullptr ); - auto ioCapability = field->capability(); + auto ioCapability = field->capability(); if ( !ioCapability ) { return std::make_tuple( http::status::forbidden, @@ -411,7 +469,7 @@ RestObjectService::ServiceResponse try { - auto childField = dynamic_cast( field ); + auto childField = dynamic_cast( field ); if ( childField ) { if ( index >= 0 ) @@ -424,7 +482,7 @@ RestObjectService::ServiceResponse return std::make_tuple( http::status::accepted, "", nullptr ); } - auto childArrayField = dynamic_cast( field ); + auto childArrayField = dynamic_cast( field ); if ( childArrayField ) { CAFFA_DEBUG( "Replacing child array object at index " << index ); @@ -451,13 +509,13 @@ RestObjectService::ServiceResponse } RestObjectService::ServiceResponse - RestObjectService::insertFieldValue( caffa::FieldHandle* field, int64_t index, const nlohmann::json& body ) + RestObjectService::insertFieldValue( FieldHandle* field, int64_t index, const nlohmann::json& body ) { - auto scriptability = field->capability(); + auto scriptability = field->capability(); if ( !scriptability || !scriptability->isWritable() ) return std::make_tuple( http::status::forbidden, "Field " + field->keyword() + " is not remote writable", nullptr ); - auto ioCapability = field->capability(); + auto ioCapability = field->capability(); if ( !ioCapability ) { return std::make_tuple( http::status::forbidden, @@ -469,10 +527,10 @@ RestObjectService::ServiceResponse try { - auto childArrayField = dynamic_cast( field ); + auto childArrayField = dynamic_cast( field ); if ( childArrayField ) { - CAFFA_INFO( "Inserting into child array field with index " << index ); + CAFFA_DEBUG( "Inserting into child array field with index " << index ); auto existingSize = childArrayField->size(); if ( index >= 0 && static_cast( index ) < existingSize ) { @@ -499,13 +557,13 @@ RestObjectService::ServiceResponse } } -RestObjectService::ServiceResponse RestObjectService::deleteFieldValue( caffa::FieldHandle* field, int64_t index ) +RestObjectService::ServiceResponse RestObjectService::deleteFieldValue( FieldHandle* field, int64_t index ) { - auto scriptability = field->capability(); + auto scriptability = field->capability(); if ( !scriptability || !scriptability->isWritable() ) return std::make_tuple( http::status::forbidden, "Field is not remote writable", nullptr ); - auto ioCapability = field->capability(); + auto ioCapability = field->capability(); if ( !ioCapability ) { return std::make_tuple( http::status::forbidden, @@ -517,7 +575,7 @@ RestObjectService::ServiceResponse RestObjectService::deleteFieldValue( caffa::F try { - auto childField = dynamic_cast( field ); + auto childField = dynamic_cast( field ); if ( childField ) { if ( index >= 0 ) @@ -535,7 +593,7 @@ RestObjectService::ServiceResponse RestObjectService::deleteFieldValue( caffa::F } } - auto childArrayField = dynamic_cast( field ); + auto childArrayField = dynamic_cast( field ); if ( childArrayField ) { if ( index >= 0 ) @@ -556,3 +614,81 @@ RestObjectService::ServiceResponse RestObjectService::deleteFieldValue( caffa::F return std::make_tuple( http::status::internal_server_error, e.what(), nullptr ); } } + +RestObjectService::ServiceResponse RestObjectService::object( http::verb verb, + const std::list& pathArguments, + const nlohmann::json& queryParams, + const nlohmann::json& body ) +{ + auto arguments = pathArguments; + auto [object, response] = findObject( arguments, queryParams ); + if ( !object ) + { + return response; + } + + bool skeleton = queryParams.contains( "skeleton" ) && queryParams["skeleton"].get(); + nlohmann::json jsonObject; + if ( skeleton ) + { + jsonObject = createJsonSkeletonFromProjectObject( object ); + } + else + { + jsonObject = createJsonFromProjectObject( object ); + } + return std::make_tuple( http::status::ok, jsonObject.dump(), nullptr ); +} + +RestObjectService::ServiceResponse RestObjectService::executeMethod( http::verb verb, + const std::list& pathArguments, + const nlohmann::json& queryParams, + const nlohmann::json& body ) +{ + caffa::SessionMaintainer session; + auto arguments = pathArguments; + + if ( arguments.empty() ) + { + return std::make_tuple( http::status::bad_request, "Object uuid not specified", nullptr ); + } + + auto uuid = arguments.front(); + arguments.pop_front(); + + if ( queryParams.contains( "session_uuid" ) ) + { + auto session_uuid = queryParams["session_uuid"].get(); + session = RestServerApplication::instance()->getExistingSession( session_uuid ); + } + + if ( !session || session->isExpired() ) + { + return std::make_tuple( http::status::forbidden, "No valid session provided", nullptr ); + } + + CAFFA_TRACE( "Trying to look for uuid '" << uuid << "'" ); + + auto object = findCafObjectFromUuid( session.get(), uuid ); + if ( !object ) + { + return std::make_tuple( http::status::not_found, "Object " + uuid + " not found", nullptr ); + } + + if ( arguments.empty() ) + { + return std::make_tuple( http::status::bad_request, "method keyword not provided", nullptr ); + } + + auto keyword = arguments.front(); + + if ( auto method = object->findMethod( keyword ); method ) + { + CAFFA_TRACE( "Found method: " << method->keyword() ); + + auto result = method->execute( *session, body.dump() ); + return std::make_tuple( http::status::ok, result, nullptr ); + } + + return std::make_tuple( http::status::not_found, "No method named " + keyword + " found", nullptr ); +} \ No newline at end of file diff --git a/RestInterface/cafRestObjectService.h b/RestInterface/cafRestObjectService.h index 083e6e5a..ec838009 100644 --- a/RestInterface/cafRestObjectService.h +++ b/RestInterface/cafRestObjectService.h @@ -18,6 +18,7 @@ // #pragma once +#include "cafRestRequest.h" #include "cafRestServiceInterface.h" #include @@ -44,6 +45,8 @@ namespace caffa::rpc class RestObjectService : public RestServiceInterface { public: + RestObjectService(); + ServiceResponse perform( http::verb verb, std::list path, const nlohmann::json& queryParams, @@ -56,25 +59,33 @@ class RestObjectService : public RestServiceInterface std::map serviceComponentEntries() const override; private: - static caffa::ObjectHandle* findObject( const std::string& uuid, const caffa::Session* session ); - - static ServiceResponse performFieldOperation( std::shared_ptr session, - http::verb verb, - caffa::ObjectHandle* object, - const std::string& keyword, - const nlohmann::json& queryParams, - const nlohmann::json& body ); - static ServiceResponse performMethodOperation( std::shared_ptr session, - http::verb verb, - caffa::ObjectHandle* object, - const std::string& keyword, - const nlohmann::json& queryParams, - const nlohmann::json& body ); + static std::pair findObject( std::list& pathArguments, + const nlohmann::json& queryParam ); + + static nlohmann::json anyObjectResponseContent(); + static nlohmann::json anyFieldResponseContent(); + + static ServiceResponse object( http::verb verb, + const std::list& pathArguments, + const nlohmann::json& queryParams, + const nlohmann::json& body ); static ServiceResponse getFieldValue( const caffa::FieldHandle* fieldHandle, int64_t index, bool skeleton ); static ServiceResponse replaceFieldValue( caffa::FieldHandle* fieldHandle, int64_t index, const nlohmann::json& body ); static ServiceResponse insertFieldValue( caffa::FieldHandle* fieldHandle, int64_t index, const nlohmann::json& body ); static ServiceResponse deleteFieldValue( caffa::FieldHandle* field, int64_t index ); + + static ServiceResponse performFieldOperation( http::verb verb, + const std::list& pathArguments, + const nlohmann::json& queryParams, + const nlohmann::json& body ); + + static ServiceResponse executeMethod( http::verb verb, + const std::list& pathArguments, + const nlohmann::json& queryParams, + const nlohmann::json& body ); + + std::unique_ptr m_requestPathRoot; }; } // namespace caffa::rpc diff --git a/RestInterface/cafRestRequest.cpp b/RestInterface/cafRestRequest.cpp index c4e1f5cf..9bc2f3ab 100644 --- a/RestInterface/cafRestRequest.cpp +++ b/RestInterface/cafRestRequest.cpp @@ -239,6 +239,9 @@ std::pair> RestPathEntry::findPathE if ( currentLevel == name() ) { + CAFFA_TRACE( "Found regular path entry " << currentLevel << " == " << name() << ", Rest of path is: " + << caffa::StringTools::join( path.begin(), path.end(), "/" ) ); + path.pop_front(); if ( path.empty() ) { @@ -255,14 +258,27 @@ std::pair> RestPathEntry::findPathE } else if ( matchesPathArgument( currentLevel ) ) { + CAFFA_TRACE( "Found path argument " << currentLevel << " == " << name() << ", Rest of path is: " + << caffa::StringTools::join( path.begin(), path.end(), "/" ) ); + + path.pop_front(); + std::list parameters = { currentLevel }; + // Matches an argument in the path (such as an UUID) // Look for (optional) children for ( const auto& [name, child] : m_children ) { - auto requestAndRemainingParameters = child->findPathEntry( path ); - if ( requestAndRemainingParameters.first ) return requestAndRemainingParameters; + auto [request, subParameters] = child->findPathEntry( path ); + if ( request ) + { + for ( const auto& param : subParameters ) + { + parameters.push_back( param ); + } + return std::make_pair( request, parameters ); + } } - return std::make_pair( this, path ); + return std::make_pair( this, parameters ); } return std::make_pair( nullptr, path ); @@ -364,7 +380,6 @@ void RequestFinder::searchPath( const RestPathEntry* pathEntry, std::string curr for ( auto child : pathEntry->children() ) { auto nextLevelPath = currentPath + "/" + child->name(); - CAFFA_INFO( "FOUND PATH ENTRY: " << nextLevelPath << " with " << child->actions().size() << " actions" ); searchPath( child, nextLevelPath ); } }