You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
5.1 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #ifndef KICAD_API_HANDLER_H
  21. #define KICAD_API_HANDLER_H
  22. #include <functional>
  23. #include <optional>
  24. #include <fmt/format.h>
  25. #include <tl/expected.hpp>
  26. #include <wx/debug.h>
  27. #include <wx/string.h>
  28. #include <google/protobuf/message.h>
  29. #include <kicommon.h>
  30. #include <api/common/envelope.pb.h>
  31. #include <core/typeinfo.h>
  32. using kiapi::common::ApiRequest, kiapi::common::ApiResponse;
  33. using kiapi::common::ApiResponseStatus, kiapi::common::ApiStatusCode;
  34. typedef tl::expected<ApiResponse, ApiResponseStatus> API_RESULT;
  35. template <typename T>
  36. using HANDLER_RESULT = tl::expected<T, ApiResponseStatus>;
  37. template <typename RequestMessageType>
  38. struct HANDLER_CONTEXT
  39. {
  40. std::string ClientName;
  41. RequestMessageType Request;
  42. };
  43. class KICOMMON_API API_HANDLER
  44. {
  45. public:
  46. API_HANDLER() {}
  47. virtual ~API_HANDLER() {}
  48. /**
  49. * Attempt to handle the given API request, if a handler exists in this class for the message.
  50. * @param aMsg is a request to attempt to handle
  51. * @return a response to send to the client, or an appropriate error
  52. */
  53. API_RESULT Handle( ApiRequest& aMsg );
  54. protected:
  55. /**
  56. * A handler for outer messages (envelopes) that will unpack to inner messages and call a
  57. * specific handler function. @see registerHandler.
  58. */
  59. typedef std::function<HANDLER_RESULT<ApiResponse>( ApiRequest& )> REQUEST_HANDLER;
  60. /**
  61. * Registers an API command handler for the given message types.
  62. *
  63. * When an API request matching the given type comes in, the handler will be called and its
  64. * response will be packed into an envelope for sending back to the API client.
  65. *
  66. * If the given message does not unpack into the request type, an envelope is returned with
  67. * status AS_BAD_REQUEST, which probably indicates corruption in the message.
  68. *
  69. * @tparam RequestType is a protobuf message type containing a command
  70. * @tparam ResponseType is a protobuf message type containing a command response
  71. * @tparam HandlerType is the implied type of the API_HANDLER subclass
  72. * @param aHandler is the handler function for the given request and response types
  73. */
  74. template <class RequestType, class ResponseType, class HandlerType>
  75. void registerHandler( HANDLER_RESULT<ResponseType> ( HandlerType::*aHandler )(
  76. const HANDLER_CONTEXT<RequestType>& ) )
  77. {
  78. std::string typeName { RequestType().GetTypeName() };
  79. wxASSERT_MSG( !m_handlers.contains( typeName ),
  80. wxString::Format( "Duplicate API handler for type %s", typeName ) );
  81. m_handlers[typeName] =
  82. [this, aHandler]( ApiRequest& aRequest ) -> API_RESULT
  83. {
  84. HANDLER_CONTEXT<RequestType> ctx;
  85. ApiResponse envelope;
  86. if( !tryUnpack( aRequest, envelope, ctx.Request ) )
  87. return envelope;
  88. ctx.ClientName = aRequest.header().client_name();
  89. HANDLER_RESULT<ResponseType> response =
  90. std::invoke( aHandler, static_cast<HandlerType*>( this ), ctx );
  91. if( response.has_value() )
  92. {
  93. envelope.mutable_status()->set_status( ApiStatusCode::AS_OK );
  94. envelope.mutable_message()->PackFrom( *response );
  95. return envelope;
  96. }
  97. else
  98. {
  99. return tl::unexpected( response.error() );
  100. }
  101. };
  102. }
  103. /// Maps type name (without the URL prefix) to a handler method
  104. std::map<std::string, REQUEST_HANDLER> m_handlers;
  105. static const wxString m_defaultCommitMessage;
  106. private:
  107. template<typename MessageType>
  108. bool tryUnpack( ApiRequest& aRequest, ApiResponse& aReply, MessageType& aDest )
  109. {
  110. if( !aRequest.message().UnpackTo( &aDest ) )
  111. {
  112. std::string msg = fmt::format( "could not unpack message of type {} from request",
  113. aDest.GetTypeName() );
  114. aReply.mutable_status()->set_status( ApiStatusCode::AS_BAD_REQUEST );
  115. aReply.mutable_status()->set_error_message( msg );
  116. return false;
  117. }
  118. return true;
  119. }
  120. };
  121. #endif //KICAD_API_HANDLER_H