kicad/include/api/api_handler.h

152 lines
5.1 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KICAD_API_HANDLER_H
#define KICAD_API_HANDLER_H
#include <functional>
#include <optional>
#include <fmt/format.h>
#include <tl/expected.hpp>
#include <wx/debug.h>
#include <wx/string.h>
#include <google/protobuf/message.h>
#include <kicommon.h>
#include <api/common/envelope.pb.h>
#include <core/typeinfo.h>
using kiapi::common::ApiRequest, kiapi::common::ApiResponse;
using kiapi::common::ApiResponseStatus, kiapi::common::ApiStatusCode;
typedef tl::expected<ApiResponse, ApiResponseStatus> API_RESULT;
template <typename T>
using HANDLER_RESULT = tl::expected<T, ApiResponseStatus>;
struct HANDLER_CONTEXT
{
std::string ClientName;
};
class KICOMMON_API API_HANDLER
{
public:
API_HANDLER() {}
virtual ~API_HANDLER() {}
/**
* Attempt to handle the given API request, if a handler exists in this class for the message.
* @param aMsg is a request to attempt to handle
* @return a response to send to the client, or an appropriate error
*/
API_RESULT Handle( ApiRequest& aMsg );
protected:
/**
* A handler for outer messages (envelopes) that will unpack to inner messages and call a
* specific handler function. @see registerHandler.
*/
typedef std::function<HANDLER_RESULT<ApiResponse>( ApiRequest& )> REQUEST_HANDLER;
/**
* Registers an API command handler for the given message types.
*
* When an API request matching the given type comes in, the handler will be called and its
* response will be packed into an envelope for sending back to the API client.
*
* If the given message does not unpack into the request type, an envelope is returned with
* status AS_BAD_REQUEST, which probably indicates corruption in the message.
*
* @tparam RequestType is a protobuf message type containing a command
* @tparam ResponseType is a protobuf message type containing a command response
* @tparam HandlerType is the implied type of the API_HANDLER subclass
* @param aHandler is the handler function for the given request and response types
*/
template <class RequestType, class ResponseType, class HandlerType>
void registerHandler(
HANDLER_RESULT<ResponseType>( HandlerType::* aHandler )( RequestType&,
const HANDLER_CONTEXT& ) )
{
std::string typeName = RequestType().GetTypeName();
wxASSERT_MSG( !m_handlers.count( typeName ),
wxString::Format( "Duplicate API handler for type %s", typeName ) );
m_handlers[typeName] =
[=]( ApiRequest& aRequest ) -> API_RESULT
{
RequestType cmd;
ApiResponse envelope;
if( !tryUnpack( aRequest, envelope, cmd ) )
return envelope;
HANDLER_CONTEXT ctx;
ctx.ClientName = aRequest.header().client_name();
HANDLER_RESULT<ResponseType> response =
std::invoke( aHandler, static_cast<HandlerType*>( this ), cmd, ctx );
if( response.has_value() )
{
envelope.mutable_status()->set_status( ApiStatusCode::AS_OK );
envelope.mutable_message()->PackFrom( *response );
return envelope;
}
else
{
return tl::unexpected( response.error() );
}
};
}
/// Maps type name (without the URL prefix) to a handler method
std::map<std::string, REQUEST_HANDLER> m_handlers;
static const wxString m_defaultCommitMessage;
private:
template<typename MessageType>
bool tryUnpack( ApiRequest& aRequest, ApiResponse& aReply, MessageType& aDest )
{
if( !aRequest.message().UnpackTo( &aDest ) )
{
std::string msg = fmt::format( "could not unpack message of type {} from request",
aDest.GetTypeName() );
aReply.mutable_status()->set_status( ApiStatusCode::AS_BAD_REQUEST );
aReply.mutable_status()->set_error_message( msg );
return false;
}
return true;
}
};
#endif //KICAD_API_HANDLER_H