FastLED 3.9.15
Loading...
Searching...
No Matches
rpc.h
Go to the documentation of this file.
1#pragma once
2
3#include "fl/stl/json.h" // IWYU pragma: keep
4
5// =============================================================================
6// RPC System - Main Public API
7// =============================================================================
8//
9// This header provides the complete typed RPC system for FastLED.
10//
11// PUBLIC API:
12// - fl::Rpc : Main RPC registry class
13// - fl::RpcHandle<S> : Callable handle returned from method() registration
14//
15// EXAMPLE USAGE:
16//
17// #include "fl/remote/rpc/rpc.h"
18//
19// fl::Rpc rpc;
20//
21// // Bind (register) method - simple (constructor style)
22// rpc.bind(Rpc::Config{"add", [](int a, int b) { return a + b; }});
23//
24// // Bind method - simple (designated initializer style)
25// rpc.bind({.name = "add", .fn = [](int a, int b) { return a + b; }});
26//
27// // With metadata - designated initializer style
28// rpc.bind({
29// .name = "multiply",
30// .fn = [](int a, int b) { return a * b; },
31// .params = {"a", "b"},
32// .description = "Multiplies two integers",
33// .tags = {"math"}
34// });
35//
36// // With metadata - constructor style
37// rpc.bind(Rpc::Config{
38// "divide", // name
39// [](int a, int b) { return a / b; }, // function
40// {"dividend", "divisor"}, // params
41// "Divides two integers" // description
42// });
43//
44// // Group methods using dot notation for namespacing:
45// rpc.bind({"led.setBrightness", [](int b) { /* ... */ }});
46// rpc.bind({"led.setColor", [](int r, int g, int b) { /* ... */ }});
47// rpc.bind({"system.status", []() -> fl::string { return "ok"; }});
48//
49// // Get by name and call later
50// auto addFn = rpc.get<int(int, int)>("add");
51// if (addFn) { // Check if binding succeeded
52// int sum = addFn(5, 7); // Call directly!
53// } else if (addFn.error() == fl::BindError::NotFound) {
54// // Method not registered
55// } else { // fl::BindError::SignatureMismatch
56// // Method exists but signature doesn't match
57// }
58//
59// // JSON-RPC transport
60// fl::json request = fl::json::parse(R"({"method":"add","params":[6,7],"id":1})");
61// fl::json response = rpc.handle(request);
62//
63// // Schema generation (flat tuple format) - always available
64// fl::json schema = rpc.schema(); // Flat schema document
65// fl::json methods = rpc.methods(); // Flat method array
66//
67// // Built-in rpc.discover method (always available)
68// fl::json request = fl::json::parse(R"({"method":"rpc.discover","id":1})");
69// fl::json response = rpc.handle(request); // Returns flat schema
70//
71// =============================================================================
72
73#include "fl/stl/json.h" // IWYU pragma: keep
74// Internal RPC headers
78
79// STL headers required for public API
80#include "fl/stl/stdint.h"
81#include "fl/stl/string.h"
82#include "fl/stl/vector.h"
83#include "fl/stl/optional.h"
84#include "fl/stl/expected.h"
85#include "fl/stl/function.h"
87#include "fl/stl/type_traits.h"
88#include "fl/stl/initializer_list.h" // IWYU pragma: keep
89#include "fl/stl/noexcept.h"
90
91namespace fl {
92
93// Forward declarations
94class ResponseSend;
95
96// =============================================================================
97// BindError - Error codes for bind() failures
98// =============================================================================
99
100enum class BindError {
101 NotFound, // No method registered with that name
102 SignatureMismatch // Method exists but signature doesn't match
103};
104
105// =============================================================================
106// RPC Schema Types
107// =============================================================================
108
116
127
128// =============================================================================
129// BindResult - Result wrapper for bind() operations
130// =============================================================================
131
135template<typename Sig>
137
138template<typename R, typename... Args>
139struct BindResult<R(Args...)> {
141
144
146 BindResult(RpcFn<R(Args...)> fn) : inner(fl::expected<RpcFn<R(Args...)>, BindError>::success(fl::move(fn))) {}
147
149 bool has_value() const { return inner.has_value(); }
150 bool ok() const { return inner.has_value(); }
151 explicit operator bool() const { return inner.has_value(); }
152
154 RpcFn<R(Args...)> value() const { return inner.value(); }
155 RpcFn<R(Args...)>& value() { return inner.value(); }
156
158 BindError error() const { return inner.error(); }
159
161 const fl::expected<RpcFn<R(Args...)>, BindError>& get() const { return inner; }
162
165 R operator()(Args... args) const {
166 return inner.value()(fl::forward<Args>(args)...);
167 }
168};
169
170// =============================================================================
171// Rpc - Main typed RPC registry
172// =============================================================================
173//
174// The primary class for registering and invoking RPC methods.
175// Methods can be registered with auto-deduced signatures and called either
176// directly (via RpcHandle), by binding, or through JSON-RPC transport.
177
178class Rpc {
179public:
182 template<typename Callable>
209
210 Rpc() FL_NOEXCEPT = default;
211 ~Rpc() FL_NOEXCEPT = default;
212
213 // Non-copyable but movable
214 Rpc(const Rpc&) FL_NOEXCEPT = delete;
215 Rpc& operator=(const Rpc&) FL_NOEXCEPT = delete;
216 Rpc(Rpc&&) FL_NOEXCEPT = default;
217 Rpc& operator=(Rpc&&) FL_NOEXCEPT = default;
218
219 // =========================================================================
220 // Response Sink for Async ACKs
221 // =========================================================================
222
224 void setResponseSink(fl::function<void(const fl::json&)> sink);
225
226 // =========================================================================
227 // Method Registration (Binding)
228 // =========================================================================
229
233 template<typename Callable>
234 void bind(const Config<Callable>& config) {
235 using Sig = typename callable_traits<typename decay<Callable>::type>::signature;
236 RpcFn<Sig> wrapped(config.fn);
237 registerMethod<Sig>(config.name.c_str(), wrapped, config.params, config.description, config.tags, config.mode);
238 }
239
241 template<typename Callable>
242 void bind(const char* name, Callable fn, fl::RpcMode mode = fl::RpcMode::SYNC) {
243 bind(Config<Callable>{name, fl::move(fn), mode});
244 }
245
257 void bindAsync(const char* name,
258 fl::function<void(ResponseSend&, const json&)> fn,
260
261 // =========================================================================
262 // Method Retrieval
263 // =========================================================================
264
267 template<class Sig>
268 BindResult<Sig> get(const char* name) const {
269 fl::string key(name);
270 auto it = mRegistry.find(key);
271 if (it == mRegistry.end()) {
273 }
274 if (it->second.mTypeTag != detail::TypeTag<Sig>::id()) {
276 }
277 auto* holder = static_cast<detail::TypedCallableHolder<Sig>*>(
278 it->second.mTypedCallable.get());
279 if (!holder) {
281 }
282 return BindResult<Sig>(holder->mFn);
283 }
284
286 bool has(const char* name) const {
287 return mRegistry.find(fl::string(name)) != mRegistry.end();
288 }
289
292 bool unbind(const char* name) {
293 fl::string key(name);
294 auto it = mRegistry.find(key);
295 if (it != mRegistry.end()) {
296 mRegistry.erase(it);
297 return true;
298 }
299 return false;
300 }
301
303 void clear() {
304 mRegistry.clear();
305 }
306
307 // =========================================================================
308 // JSON-RPC Transport
309 // =========================================================================
310
314 json handle(const json& request);
315
317 fl::optional<json> handle_maybe(const json& request);
318
319 // =========================================================================
320 // Schema and Discovery
321 // =========================================================================
322
326 json methods() const;
327
331 json schema() const;
332
334 fl::size count() const {
335 return mRegistry.size();
336 }
337
340
341 // =========================================================================
342 // Internal Registration (used by MethodBuilder)
343 // =========================================================================
344
345 template<class Sig>
346 bool registerMethod(const char* name, RpcFn<Sig> fn,
347 const fl::vector<fl::string>& paramNames,
348 const fl::string& description,
351 fl::string key(name);
352 auto it = mRegistry.find(key);
353 if (it != mRegistry.end()) {
354 if (it->second.mTypeTag != detail::TypeTag<Sig>::id()) {
355 return false;
356 }
357 }
358
359 detail::RpcEntry entry;
363#if FL_PLATFORM_HAS_LARGE_MEMORY
364 // Schema generator construction gated on Low-memory targets per
365 // FastLED #3081 / #3079. The schema generator is only consumed by
366 // `rpc.discover` (also gated). Skipping it on Low-memory drops
367 // `TypedSchemaGenerator<Sig>::params()` (~800 B per signature).
369#endif
371 entry.mTags = tags;
372 entry.mMode = mode;
373
374#if FL_PLATFORM_HAS_LARGE_MEMORY
375 if (!paramNames.empty()) {
376 entry.mSchemaGenerator->setParamNames(paramNames);
377 }
378#else
379 (void)paramNames;
380#endif
381
382 mRegistry[key] = fl::move(entry);
383 return true;
384 }
385
386private:
388 fl::function<void(const fl::json&)> mResponseSink; // For sending ACK responses
389};
390
391// RpcFactory is kept as an alias for backwards compatibility
393
394} // namespace fl
fl::UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick")
Helper class for sending responses in async/streaming RPC methods.
void setResponseSink(fl::function< void(const fl::json &)> sink)
Set response sink for sending ACK responses (used by async functions)
Definition rpc.cpp.hpp:24
fl::unordered_map< fl::string, detail::RpcEntry > mRegistry
Definition rpc.h:387
json handle(const json &request)
Process a JSON-RPC request.
Definition rpc.cpp.hpp:67
Rpc() FL_NOEXCEPT=default
bool unbind(const char *name)
Unbind (unregister) a previously registered method.
Definition rpc.h:292
bool registerMethod(const char *name, RpcFn< Sig > fn, const fl::vector< fl::string > &paramNames, const fl::string &description, const fl::vector< fl::string > &tags, fl::RpcMode mode=fl::RpcMode::SYNC)
Definition rpc.h:346
fl::vector< fl::string > tags() const
Returns list of unique tags used across all methods.
Definition rpc.cpp.hpp:219
fl::function< void(const fl::json &)> mResponseSink
Definition rpc.h:388
void bind(const char *name, Callable fn, fl::RpcMode mode=fl::RpcMode::SYNC)
Convenience overload: bind method by name, function, and optional mode.
Definition rpc.h:242
BindResult< Sig > get(const char *name) const
Get a registered method by name.
Definition rpc.h:268
void bind(const Config< Callable > &config)
Bind a method with configuration (name, function, optional metadata).
Definition rpc.h:234
json schema() const
Returns flat schema document.
Definition rpc.cpp.hpp:264
fl::optional< json > handle_maybe(const json &request)
For notifications (no id), returns nullopt.
Definition rpc.cpp.hpp:192
json methods() const
Returns flat method array: [["name", "returnType", [["param1", "type1"], ...]], .....
Definition rpc.cpp.hpp:242
void clear()
Clear all registered methods.
Definition rpc.h:303
void bindAsync(const char *name, fl::function< void(ResponseSend &, const json &)> fn, fl::RpcMode mode=fl::RpcMode::ASYNC)
Bind async method with ResponseSend& parameter (for ASYNC/ASYNC_STREAM modes) Signature: void(Respons...
Definition rpc.cpp.hpp:32
fl::size count() const
Returns number of registered methods.
Definition rpc.h:334
bool has(const char *name) const
Check if a method is registered (regardless of signature).
Definition rpc.h:286
Definition rpc.h:178
expected type for operations that can fail (C++23-style)
Definition expected.h:79
bool empty() const FL_NOEXCEPT
Generic expected<T, E> type for error handling without exceptions.
FastLED's Elegant JSON Library: fl::json
fl::shared_ptr< ErasedInvoker > mInvoker
fl::shared_ptr< ErasedSchemaGenerator > mSchemaGenerator
fl::shared_ptr< CallableHolderBase > mTypedCallable
fl::string mDescription
fl::vector< fl::string > mTags
const void * mTypeTag
constexpr T && forward(typename remove_reference< T >::type &t) FL_NOEXCEPT
Definition s16x16x4.h:234
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition s16x16x4.h:28
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition move.h:28
fl::function< Sig > RpcFn
Definition rpc_handle.h:14
Optional< T > optional
Definition optional.h:16
Rpc RpcFactory
Definition rpc.h:392
BindError
Definition rpc.h:100
@ SignatureMismatch
Definition rpc.h:102
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
enable_if< is_fixed_point< T >::value, T >::type exp(T x) FL_NOEXCEPT
RpcMode
Definition rpc_mode.h:9
Base definition for an LED controller.
Definition crgb.hpp:179
fl::vector< ParamInfo > params
Definition rpc.h:122
fl::vector< fl::string > tags
Definition rpc.h:125
fl::string name
Definition rpc.h:121
fl::string type
Definition rpc.h:114
fl::string description
Definition rpc.h:124
fl::string name
Definition rpc.h:113
fl::string returnType
Definition rpc.h:123
Method parameter information.
Definition rpc.h:112
Method information.
Definition rpc.h:120
Wraps the result of binding to a method by name.
Definition rpc.h:136
corkscrew_args args
Definition old.h:149
#define FL_NOEXCEPT
bool has_value() const
Check if binding succeeded.
Definition rpc.h:149
const fl::expected< RpcFn< R(Args...)>, BindError > & get() const
Access the underlying expected.
Definition rpc.h:161
R operator()(Args... args) const
Call the bound function directly (undefined behavior if binding failed) Always check ok() or has_valu...
Definition rpc.h:165
BindError error() const
Get the error (undefined if has_value())
Definition rpc.h:158
fl::expected< RpcFn< R(Args...)>, BindError > inner
Definition rpc.h:140
RpcFn< R(Args...)> & value()
Definition rpc.h:155
BindResult(fl::expected< RpcFn< R(Args...)>, BindError > exp)
Construct from expected.
Definition rpc.h:143
RpcFn< R(Args...)> value() const
Get the callable (throws if error)
Definition rpc.h:154
BindResult(RpcFn< R(Args...)> fn)
Construct from RpcFn (success case)
Definition rpc.h:146
fl::vector< fl::string > params
Parameter names (optional)
Definition rpc.h:187
Config(fl::string n, Callable f, fl::vector< fl::string > p, fl::string desc, fl::vector< fl::string > t={}, fl::RpcMode m=fl::RpcMode::SYNC)
Constructor with all fields.
Definition rpc.h:200
fl::string name
Method name (REQUIRED)
Definition rpc.h:184
Config(fl::string n, Callable f, fl::vector< fl::string > p, fl::RpcMode m=fl::RpcMode::SYNC)
Constructor with params.
Definition rpc.h:196
fl::vector< fl::string > tags
Tags for grouping (optional)
Definition rpc.h:189
Callable fn
Function to register (REQUIRED)
Definition rpc.h:185
Config(fl::string n, Callable f, fl::RpcMode m=fl::RpcMode::SYNC)
Constructor requiring name and function (metadata is optional)
Definition rpc.h:192
fl::RpcMode mode
Execution mode (SYNC or ASYNC, default SYNC)
Definition rpc.h:186
fl::string description
Method description (optional)
Definition rpc.h:188
Configuration for method registration with optional metadata.
Definition rpc.h:183
static const void * id()