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 "cafLogger.h"
25 #include "cafMethodHandle.h"
26 
27 #include "cafObjectFactory.h"
28 #include "cafObjectJsonSpecializations.h"
29 
30 #include <nlohmann/json.hpp>
31 
32 #include <functional>
33 
34 namespace caffa
35 {
36 class ObjectHandle;
37 
38 template <class CallbackT>
39 class Method : public MethodHandle
40 {
41 public:
42  std::function<CallbackT> m_callback;
43 };
44 
45 template <typename Result, typename... ArgTypes>
46 class Method<Result( ArgTypes... )> : public MethodHandle
47 {
48 public:
49  using Callback = std::function<Result( ArgTypes... )>;
50 
51  Result operator()( ArgTypes... args ) const
52  {
53  CAFFA_ASSERT( m_callback );
54  if ( auto accessor = this->accessor(); accessor )
55  {
56  auto serializedMethod = toJson( args... ).dump();
57  CAFFA_DEBUG( "Serialized method: " << serializedMethod );
58  auto serialisedResult = accessor->execute( serializedMethod );
59  CAFFA_DEBUG( "Got serialized result: " << serialisedResult );
60  return resultFromJsonString( serialisedResult, accessor->objectFactory() );
61  }
62  return m_callback( args... );
63  }
64 
70  std::string execute( const std::string& jsonArgumentsString ) const override
71  {
72  return executeJson( nlohmann::json::parse( jsonArgumentsString ) ).dump();
73  }
74 
75  std::string schema() const override { return this->jsonSchema().dump(); }
76 
77  Result resultFromJsonString( const std::string& jsonResultString, ObjectFactory* objectFactory ) const
78  {
79  CAFFA_DEBUG( "Attempting to get a value of type " << JsonDataType<Result>::type()
80  << " from JSON: " << jsonResultString );
81  nlohmann::json jsonResult;
82  if ( !jsonResultString.empty() )
83  {
84  jsonResult = nlohmann::json::parse( jsonResultString );
85  }
86  return jsonToValue<Result>( jsonResult, objectFactory );
87  }
88 
89  nlohmann::json toJson( ArgTypes... args ) const
90  {
91  auto jsonMethod = nlohmann::json::object();
92  CAFFA_ASSERT( !keyword().empty() );
93 
94  constexpr std::size_t n = sizeof...( args );
95  if constexpr ( n > 0 )
96  {
97  auto jsonArguments = nlohmann::json::array();
98  size_t i = 0;
99 
100  // Fold expression
101  // https://en.cppreference.com/w/cpp/language/fold
102  (
103  [&]
104  {
105  jsonArguments.push_back( args );
106  i++;
107  }(),
108  ... );
109  jsonMethod["positionalArguments"] = jsonArguments;
110  }
111  return jsonMethod;
112  }
113 
114  nlohmann::json jsonSchema() const override
115  {
116  auto jsonMethod = nlohmann::json::object();
117  CAFFA_ASSERT( !keyword().empty() );
118  jsonMethod["type"] = "object";
119  if ( !this->documentation().empty() )
120  {
121  jsonMethod["description"] = this->documentation();
122  }
123  auto jsonProperties = nlohmann::json::object();
124  auto jsonArgumentItems = jsonArgumentSchemaArray( std::index_sequence_for<ArgTypes...>() );
125 
126  if ( !jsonArgumentItems.empty() )
127  {
128  auto jsonpositionalArguments = nlohmann::json::object();
129  jsonpositionalArguments["type"] = "array";
130  jsonpositionalArguments["minItems"] = jsonArgumentItems.size();
131  jsonpositionalArguments["maxItems"] = jsonArgumentItems.size();
132 
133  auto jsonNumberedArgumentItems = nlohmann::json::array();
134  auto jsonLabelledArguments = nlohmann::json::object();
135  jsonLabelledArguments["type"] = "object";
136  auto jsonLabelledArgumentProperties = nlohmann::json::object();
137  for ( const nlohmann::json& argument : jsonArgumentItems )
138  {
139  CAFFA_ASSERT( argument.is_object() );
140  auto keyword = argument["keyword"].get<std::string>();
141  auto type = argument["type"];
142  jsonLabelledArgumentProperties[keyword] = type;
143  jsonNumberedArgumentItems.push_back( type );
144  }
145  jsonpositionalArguments["items"] = jsonNumberedArgumentItems;
146  jsonProperties["positionalArguments"] = jsonpositionalArguments;
147  jsonLabelledArguments["properties"] = jsonLabelledArgumentProperties;
148  jsonProperties["labelledArguments"] = jsonLabelledArguments;
149  }
150  if ( !JsonDataType<Result>::type().empty() )
151  {
152  jsonProperties["returns"] = JsonDataType<Result>::type();
153  }
154 
155  jsonMethod["properties"] = jsonProperties;
156 
157  return jsonMethod;
158  }
159 
160  void setCallback( Callback callback ) { this->m_callback = callback; }
161 
162 private:
163  template <typename ArgType>
164  requires std::same_as<ArgType, void>
165  static ArgType jsonToValue( const nlohmann::json& jsonData, ObjectFactory* objectFactory )
166  {
167  return;
168  }
169 
170  template <typename ArgType>
171  requires IsSharedPtr<ArgType>
172  static ArgType jsonToValue( const nlohmann::json& jsonData, ObjectFactory* objectFactory )
173  {
174  JsonSerializer serializer( objectFactory );
175  return std::dynamic_pointer_cast<typename ArgType::element_type>(
176  serializer.createObjectFromString( jsonData.dump() ) );
177  }
178 
179  template <typename ArgType>
180  requires( not IsSharedPtr<ArgType> && not std::same_as<ArgType, void> )
181  static ArgType jsonToValue( const nlohmann::json& jsonData, ObjectFactory* objectFactory )
182  {
183  return jsonData.get<ArgType>();
184  }
185 
186  template <typename ReturnType, std::size_t... Is>
187  requires std::same_as<ReturnType, void>
188  nlohmann::json executeJson( const nlohmann::json& args, std::index_sequence<Is...> ) const
189  {
190  this->operator()( jsonToValue<ArgTypes>( args[Is], nullptr )... );
191 
192  nlohmann::json returnValue = nlohmann::json::object();
193  return returnValue;
194  }
195 
196  template <typename ReturnType, std::size_t... Is>
197  requires( not std::same_as<ReturnType, void> )
198  nlohmann::json executeJson( const nlohmann::json& args, std::index_sequence<Is...> ) const
199  {
200  return this->operator()( jsonToValue<ArgTypes>( args[Is], nullptr )... );
201  }
202 
203  nlohmann::json executeJson( const nlohmann::json& jsonMethod ) const
204  {
205  auto jsonArguments = nlohmann::json::array();
206  if ( jsonMethod.contains( "positionalArguments" ) )
207  {
208  jsonArguments = jsonMethod["positionalArguments"];
209  }
210  else if ( jsonMethod.contains( "labelledArguments" ) )
211  {
212  jsonArguments = jsonMethod["labelledArguments"];
213  sortArguments( jsonArguments, argumentNames() );
214  }
215  auto expectedSize = jsonArgumentSchemaArray( std::index_sequence_for<ArgTypes...>() ).size();
216  if ( jsonArguments.size() != expectedSize )
217  {
218  throw std::runtime_error( "Wrong number of arguments! Got " + std::to_string( jsonArguments.size() ) +
219  ", Expected " + std::to_string( expectedSize ) );
220  }
221  return this->executeJson<Result>( jsonArguments, std::index_sequence_for<ArgTypes...>() );
222  }
223 
224  void sortArguments( nlohmann::json& jsonMap, const std::vector<std::string>& argumentNames ) const
225  {
226  nlohmann::json sortedArray = nlohmann::json::array();
227  for ( const auto& argumentName : argumentNames )
228  {
229  auto it = jsonMap.find( argumentName );
230  if ( it != jsonMap.end() )
231  {
232  sortedArray.push_back( *it );
233  jsonMap.erase( it );
234  }
235  }
236  // All unnamed arguments
237  sortedArray.insert( sortedArray.end(), jsonMap.begin(), jsonMap.end() );
238  jsonMap.swap( sortedArray );
239  }
240 
241  template <typename... T>
242  void argumentHelper( nlohmann::json& jsonArguments, const T&... argumentTypes ) const
243  {
244  const auto& argumentNames = this->argumentNames();
245  constexpr std::size_t n = sizeof...( argumentTypes );
246  if constexpr ( n > 0 )
247  {
248  size_t i = 0;
249  (
250  [&]
251  {
252  nlohmann::json jsonArg = nlohmann::json::object();
253  jsonArg["keyword"] = argumentNames[i];
254  jsonArg["type"] = argumentTypes;
255  jsonArguments.push_back( jsonArg );
256  i++;
257  }(),
258  ... );
259  }
260  }
261 
262  template <std::size_t... Is>
263  nlohmann::json jsonArgumentSchemaArray( std::index_sequence<Is...> ) const
264  {
265  auto jsonArguments = nlohmann::json::array();
266  argumentHelper( jsonArguments, JsonDataType<ArgTypes>::type()... );
267  return jsonArguments;
268  }
269 
270 private:
271  Callback m_callback;
272 };
273 
274 } // namespace caffa
Definition: cafMethodHandle.h:56
std::string execute(const std::string &jsonArgumentsString) const override
Definition: cafMethod.h:70
Definition: method.py:22
Main Caffa namespace.
Definition: __init__.py:1