Caffa  1.1.0
C++ Application Framework for Embedded Systems with introspection
cafMethod.h
1 // ##################################################################################################
2 //
3 // CAFFA
4 // Copyright (C) 2023- Kontur AS
5 //
6 // GNU Lesser General Public License Usage
7 // This library is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU Lesser General Public License as published by
9 // the Free Software Foundation; either version 2.1 of the License, or
10 // (at your option) any later version.
11 //
12 // This library is distributed in the hope that it will be useful, but WITHOUT ANY
13 // WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 // FITNESS FOR A PARTICULAR PURPOSE.
15 //
16 // See the GNU Lesser General Public License at <<http://www.gnu.org/licenses/lgpl-2.1.html>>
17 // for more details.
18 //
19 // ##################################################################################################
20 #pragma once
21 
22 #include "cafAssert.h"
23 #include "cafJsonDataType.h"
24 #include "cafJsonSerializer.h"
25 #include "cafLogger.h"
26 #include "cafMethodHandle.h"
27 #include "cafObjectFactory.h"
28 #include "cafObjectJsonConversion.h"
29 #include "cafSession.h"
30 
31 #include <nlohmann/json.hpp>
32 
33 #include <functional>
34 
35 namespace caffa
36 {
37 class ObjectHandle;
38 
39 template <class CallbackT>
40 class Method : public MethodHandle
41 {
42 public:
43  std::function<CallbackT> m_callback;
44 };
45 
46 template <typename Result, typename... ArgTypes>
47 class Method<Result( ArgTypes... )> : public MethodHandle
48 {
49 public:
50  using Callback = std::function<Result( ArgTypes... )>;
51  using CallbackWithSession = std::function<Result( std::shared_ptr<Session>, ArgTypes... )>;
52 
53  Result operator()( std::shared_ptr<Session> session, ArgTypes... args ) const
54  {
55  if ( !m_callbackWithSession )
56  {
57  return this->operator()( args... ); // pass on to operator without session
58  }
59 
60  CAFFA_ASSERT( m_callbackWithSession );
61  CAFFA_ASSERT( !this->accessor() );
62  return m_callbackWithSession( session, args... );
63  }
64 
65  Result operator()( ArgTypes... args ) const
66  {
67  if ( auto accessor = this->accessor(); accessor )
68  {
69  auto serializedMethod = toJson( args... ).dump();
70  CAFFA_DEBUG( "Serialized method: " << serializedMethod );
71  auto serialisedResult = accessor->execute( serializedMethod );
72  CAFFA_DEBUG( "Got serialized result: " << serialisedResult );
73  return resultFromJsonString( serialisedResult, accessor->objectFactory() );
74  }
75  CAFFA_ASSERT( m_callback );
76  return m_callback( args... );
77  }
78 
84  std::string execute( std::shared_ptr<Session> session, const std::string& jsonArgumentsString ) const override
85  {
86  return executeJson( session, nlohmann::json::parse( jsonArgumentsString ) ).dump();
87  }
88 
89  std::string schema() const override { return this->jsonSchema().dump(); }
90 
91  Result resultFromJsonString( const std::string& jsonResultString, ObjectFactory* objectFactory ) const
92  {
93  nlohmann::json jsonResult;
94  if ( !jsonResultString.empty() )
95  {
96  jsonResult = nlohmann::json::parse( jsonResultString );
97  }
98  return jsonToValue<Result>( jsonResult, objectFactory );
99  }
100 
101  nlohmann::json toJson( ArgTypes... args ) const
102  {
103  auto jsonMethod = nlohmann::json::object();
104  CAFFA_ASSERT( !keyword().empty() );
105 
106  constexpr std::size_t n = sizeof...( args );
107  if constexpr ( n > 0 )
108  {
109  auto jsonArguments = nlohmann::json::array();
110  size_t i = 0;
111 
112  // Fold expression
113  // https://en.cppreference.com/w/cpp/language/fold
114  (
115  [&i, &args, &jsonArguments]
116  {
117  jsonArguments.push_back( args );
118  i++;
119  }(),
120  ... );
121  jsonMethod["positionalArguments"] = jsonArguments;
122  }
123  return jsonMethod;
124  }
125 
126  nlohmann::json jsonSchema() const
127  {
128  auto jsonMethod = nlohmann::json::object();
129  CAFFA_ASSERT( !keyword().empty() );
130  jsonMethod["type"] = "object";
131  if ( !this->documentation().empty() )
132  {
133  jsonMethod["description"] = this->documentation();
134  }
135  auto jsonProperties = nlohmann::json::object();
136  auto jsonArgumentItems = jsonArgumentSchemaArray( std::index_sequence_for<ArgTypes...>() );
137 
138  if ( !jsonArgumentItems.empty() )
139  {
140  auto jsonpositionalArguments = nlohmann::json::object();
141  jsonpositionalArguments["type"] = "array";
142  jsonpositionalArguments["minItems"] = jsonArgumentItems.size();
143  jsonpositionalArguments["maxItems"] = jsonArgumentItems.size();
144 
145  auto jsonNumberedArgumentItems = nlohmann::json::array();
146  auto jsonLabelledArguments = nlohmann::json::object();
147  jsonLabelledArguments["type"] = "object";
148  auto jsonLabelledArgumentProperties = nlohmann::json::object();
149  for ( const nlohmann::json& argument : jsonArgumentItems )
150  {
151  CAFFA_ASSERT( argument.is_object() );
152  auto keyword = argument["keyword"].get<std::string>();
153  auto type = argument["type"];
154  jsonLabelledArgumentProperties[keyword] = type;
155  jsonNumberedArgumentItems.push_back( type );
156  }
157  jsonpositionalArguments["items"] = jsonNumberedArgumentItems;
158  jsonProperties["positionalArguments"] = jsonpositionalArguments;
159  jsonLabelledArguments["properties"] = jsonLabelledArgumentProperties;
160  jsonProperties["labelledArguments"] = jsonLabelledArguments;
161  }
162  if ( !JsonDataType<Result>::jsonType().empty() )
163  {
164  jsonProperties["returns"] = JsonDataType<Result>::jsonType();
165  }
166 
167  jsonMethod["properties"] = jsonProperties;
168 
169  return jsonMethod;
170  }
171 
172  void setCallback( Callback callback ) { this->m_callback = callback; }
173  void setCallbackWithSession( CallbackWithSession callback ) { this->m_callbackWithSession = callback; }
174 
175 private:
176  template <typename ArgType>
177  requires std::same_as<ArgType, void>
178  static ArgType jsonToValue( const nlohmann::json& jsonData, ObjectFactory* objectFactory )
179  {
180  return;
181  }
182 
183  template <typename ArgType>
184  requires IsSharedPtr<ArgType>
185  static ArgType jsonToValue( const nlohmann::json& jsonData, ObjectFactory* objectFactory )
186  {
187  JsonSerializer serializer( objectFactory );
188  return std::dynamic_pointer_cast<typename ArgType::element_type>(
189  serializer.createObjectFromString( jsonData.dump() ) );
190  }
191 
192  template <typename ArgType>
193  requires( not IsSharedPtr<ArgType> && not std::same_as<ArgType, void> )
194  static ArgType jsonToValue( const nlohmann::json& jsonData, ObjectFactory* objectFactory )
195  {
196  return jsonData.get<ArgType>();
197  }
198 
199  template <typename ReturnType, std::size_t... Is>
200  requires std::same_as<ReturnType, void>
201  nlohmann::json executeJson( std::shared_ptr<Session> session, const nlohmann::json& args, std::index_sequence<Is...> ) const
202  {
203  this->operator()( session, jsonToValue<ArgTypes>( args[Is], nullptr )... );
204 
205  nlohmann::json returnValue = nlohmann::json::object();
206  return returnValue;
207  }
208 
209  template <typename ReturnType, std::size_t... Is>
210  requires( not std::same_as<ReturnType, void> )
211  nlohmann::json executeJson( std::shared_ptr<Session> session, const nlohmann::json& args, std::index_sequence<Is...> ) const
212  {
213  return this->operator()( session, jsonToValue<ArgTypes>( args[Is], nullptr )... );
214  }
215 
216  nlohmann::json executeJson( std::shared_ptr<Session> session, const nlohmann::json& jsonMethod ) const
217  {
218  auto jsonArguments = nlohmann::json::array();
219  if ( jsonMethod.contains( "positionalArguments" ) )
220  {
221  jsonArguments = jsonMethod["positionalArguments"];
222  }
223  else if ( jsonMethod.contains( "labelledArguments" ) )
224  {
225  jsonArguments = jsonMethod["labelledArguments"];
226  sortArguments( jsonArguments, argumentNames() );
227  }
228  auto expectedSize = jsonArgumentSchemaArray( std::index_sequence_for<ArgTypes...>() ).size();
229  if ( jsonArguments.size() != expectedSize )
230  {
231  throw std::runtime_error( "Wrong number of arguments! Got " + std::to_string( jsonArguments.size() ) +
232  ", Expected " + std::to_string( expectedSize ) );
233  }
234  return this->executeJson<Result>( session, jsonArguments, std::index_sequence_for<ArgTypes...>() );
235  }
236 
237  void sortArguments( nlohmann::json& jsonMap, const std::vector<std::string>& argumentNames ) const
238  {
239  nlohmann::json sortedArray = nlohmann::json::array();
240  for ( const auto& argumentName : argumentNames )
241  {
242  auto it = jsonMap.find( argumentName );
243  if ( it != jsonMap.end() )
244  {
245  sortedArray.push_back( *it );
246  jsonMap.erase( it );
247  }
248  }
249  // All unnamed arguments
250  sortedArray.insert( sortedArray.end(), jsonMap.begin(), jsonMap.end() );
251  jsonMap.swap( sortedArray );
252  }
253 
254  template <typename... T>
255  void argumentHelper( nlohmann::json& jsonArguments, const T&... argumentTypes ) const
256  {
257  const auto& argumentNames = this->argumentNames();
258  constexpr std::size_t n = sizeof...( argumentTypes );
259  if constexpr ( n > 0 )
260  {
261  size_t i = 0;
262  (
263  [&]
264  {
265  nlohmann::json jsonArg = nlohmann::json::object();
266  if ( i < argumentNames.size() )
267  {
268  jsonArg["keyword"] = argumentNames[i];
269  }
270  jsonArg["type"] = argumentTypes;
271  jsonArguments.push_back( jsonArg );
272  i++;
273  }(),
274  ... );
275  }
276  }
277 
278  template <std::size_t... Is>
279  nlohmann::json jsonArgumentSchemaArray( std::index_sequence<Is...> ) const
280  {
281  auto jsonArguments = nlohmann::json::array();
282  argumentHelper( jsonArguments, JsonDataType<ArgTypes>::jsonType()... );
283  return jsonArguments;
284  }
285 
286 private:
287  Callback m_callback;
288  CallbackWithSession m_callbackWithSession;
289 };
290 
291 } // namespace caffa
Definition: cafMethod.h:40
Definition: cafMethodHandle.h:52
Definition: cafObjectFactory.h:36
Definition: cafJsonDataType.h:55
std::string execute(std::shared_ptr< Session > session, const std::string &jsonArgumentsString) const override
Definition: cafMethod.h:84
Definition: cafJsonSerializer.h:38
std::shared_ptr< ObjectHandle > createObjectFromString(const std::string &string) const
Definition: cafJsonSerializer.cpp:391
Main Caffa namespace.
Definition: cafApplication.h:30