FastLED 3.10.9
Loading...
Searching...
No Matches

◆ handle()

json fl::Rpc::handle ( const json & request)

Process a JSON-RPC request.

Request format: {"method": "name", "params": [...], "id": ...} Response format: {"result": ..., "id": ...} or {"error": {...}, "id": ...}

Definition at line 134 of file rpc.cpp.hpp.

134 {
135 // Extract method name
136 if (!request.contains("method")) {
137 FL_ERROR("RPC: Invalid Request - missing 'method' field");
138 return detail::makeJsonRpcError(-32600, FL_RPC_ERR_NO_METHOD, request["id"]);
139 }
140
141 auto methodOpt = request["method"].as_string();
142 if (!methodOpt.has_value()) {
143 FL_ERROR("RPC: Invalid Request - 'method' must be a string");
144 return detail::makeJsonRpcError(-32600, FL_RPC_ERR_METHOD_NOT_STR, request["id"]);
145 }
146 fl::string methodName = methodOpt.value();
147
148#if FL_PLATFORM_HAS_LARGE_MEMORY
149 // Handle built-in rpc.discover method. Gated on Low-memory targets because
150 // it transitively anchors `TypedSchemaGenerator<Sig>::params()` for every
151 // registered method (each 800+ B per signature) plus the schema-building
152 // JSON tree. The LPC8xx / AVR-class JSON-RPC bring-up surface treats the
153 // RPC catalog as a known constant -- callers don't introspect at runtime.
154 // See FastLED #3081.
155 if (methodName == "rpc.discover") {
156 // Stream when possible: the schema tree for a large registry can
157 // exceed the free heap on ESP32-S3 (FastLED #4899). Scheduled calls
158 // (non-zero timestamp) keep the inline result so it is recorded.
159 // Stream sinks run the callback synchronously, so `self` outlives it.
160 const bool scheduled = request.contains("timestamp") &&
161 request["timestamp"].as_int().value_or(0) != 0;
162 if (!scheduled && mResponseStreamSink &&
163 (!mResponseStreamAvailable || mResponseStreamAvailable())) {
164 fl::json requestId = request.contains("id") ? request["id"] : json(nullptr);
165 const bool includeId = request.contains("id");
166 const Rpc* self = this;
167 mResponseStreamSink([self, requestId, includeId](fl::JsonStreamWriter& writer) {
169 writer, requestId, includeId,
170 [self](fl::JsonStreamWriter& resultWriter) {
171 self->writeSchema(resultWriter);
172 });
173 });
174 fl::json skip = fl::json::object();
175 skip.set("noEnqueue", true);
176 return skip;
177 }
178 json response = json::object();
179 response.set("jsonrpc", "2.0");
180 response.set("result", schema());
181 if (request.contains("id")) {
182 response.set("id", request["id"]);
183 }
184 return response;
185 }
186#endif
187
188 // Look up the method
189 auto it = mRegistry.find(methodName);
190 if (it == mRegistry.end()) {
191 FL_WARN("RPC: Method not found: " << methodName.c_str());
192 return detail::makeJsonRpcError(-32601, fl::string(FL_RPC_ERR_METHOD_NOT_FOUND_PREFIX) + methodName, request["id"]);
193 }
194
195 // Extract params (default to empty array)
196 json params = request.contains("params") ? request["params"] : json::parse("[]");
197 if (!params.is_array()) {
198 FL_ERROR("RPC: Invalid params - must be an array for method: " << methodName.c_str());
199 return detail::makeJsonRpcError(-32602, FL_RPC_ERR_PARAMS_NOT_ARRAY, request["id"]);
200 }
201
202 // Check if this is an async function
203 const detail::RpcEntry& entry = it->second;
204#if FL_PLATFORM_HAS_LARGE_MEMORY
205 if (entry.mIsStreaming) {
206 if (!mResponseStreamSink) {
207 return detail::makeJsonRpcError(-32603, "Streaming response sink not configured", request["id"]);
208 }
209
210 fl::StreamingRpcHandler streamFn = entry.mStreamingFn;
211 fl::json paramsCopy = params;
212 fl::json requestId = request.contains("id") ? request["id"] : json(nullptr);
213 const bool includeId = request.contains("id");
214 mResponseStreamSink([streamFn, paramsCopy, requestId, includeId](fl::JsonStreamWriter& writer) {
216 writer,
217 requestId,
218 includeId,
219 [streamFn, paramsCopy, requestId](fl::JsonStreamWriter& resultWriter) {
220 if (streamFn) {
221 streamFn(resultWriter, paramsCopy, requestId);
222 } else {
223 resultWriter.valueNull();
224 }
225 });
226 });
227
228 fl::json skip = fl::json::object();
229 skip.set("noEnqueue", true);
230 return skip;
231 }
232
233 bool isAsync = (entry.mMode == RpcMode::ASYNC || entry.mMode == RpcMode::ASYNC_STREAM);
234
235 // Check if this is a response-aware function (uses ResponseSend&)
236 bool isResponseAware = entry.mIsResponseAware;
237
238 // For async functions, send ACK immediately
239 if (isAsync && mResponseSink && request.contains("id")) {
240 json ack = json::object();
241 ack.set("jsonrpc", "2.0");
242 ack.set("id", request["id"]);
243
244 json ackResult = json::object();
245 ackResult.set("acknowledged", true);
246 ack.set("result", ackResult);
247
248 mResponseSink(ack);
249 FL_DBG("RPC: Sent ACK for async method: " << methodName.c_str());
250 }
251#endif
252
253 fl::tuple<TypeConversionResult, json> resultTuple;
254
255#if FL_PLATFORM_HAS_LARGE_MEMORY
256 // Handle response-aware methods (with ResponseSend& parameter)
257 if (isResponseAware) {
258 // Create ResponseSend instance
259 fl::json requestId = request.contains("id") ? request["id"] : json(nullptr);
260 ResponseSend responseSend(requestId, mResponseSink);
261
262 // Invoke user function with ResponseSend& and raw JSON params
263 entry.mResponseAwareFn(responseSend, params);
264
265 // Return success with null result (actual responses sent via ResponseSend)
266 resultTuple = fl::make_tuple(TypeConversionResult::success(), json(nullptr));
267 } else {
268 // Regular invocation
269 resultTuple = entry.mInvoker->invoke(params);
270 }
271#else
272 // Low-memory targets only register regular (non-response-aware) sync RPCs;
273 // the bindAsync path is gated out (see #3224 Tier 1B). Drop the
274 // isResponseAware branch entirely to slim Rpc::handle.
275 resultTuple = entry.mInvoker->invoke(params);
276#endif
277
278 TypeConversionResult convResult = fl::get<0>(resultTuple);
279 json returnVal = fl::get<1>(resultTuple);
280
281 // Check for conversion errors
282 if (!convResult.ok()) {
283 FL_ERROR("RPC: Invalid params for method '" << methodName.c_str() << "': " << convResult.errorMessage().c_str());
284 return detail::makeJsonRpcError(-32602, fl::string(FL_RPC_ERR_INVALID_PARAMS_PREFIX) + convResult.errorMessage(), request["id"]);
285 }
286
287 // Build success response
288 json response = json::object();
289 response.set("jsonrpc", "2.0");
290 response.set("result", returnVal);
291
292 // Include id if present (for request/response correlation)
293 if (request.contains("id")) {
294 response.set("id", request["id"]);
295 }
296
297#if FL_PLATFORM_HAS_LARGE_MEMORY
298 // Include warnings if any. Low-memory targets emit warnings only as
299 // explicit error returns (e.g. our gated `float -> int not supported`
300 // path in #3224 Tier 1A) -- the warnings-array variant emplace + nested
301 // json::array() builder + push_back path was a measurable contributor
302 // to the LowMemory .text mass on its own.
303 if (convResult.hasWarning()) {
304 json warnings = json::array();
305 for (fl::size i = 0; i < convResult.warnings().size(); ++i) {
306 warnings.push_back(json(convResult.warnings()[i]));
307 }
308 response.set("warnings", warnings);
309 }
310
311 // For async functions, mark response to signal "ACK already sent" so
312 // downstream queue machinery skips pushing a duplicate response.
313 // Renamed from `__async` in #3228 -- field is a public envelope-control
314 // marker, not a reserved-namespace identifier.
315 if (isAsync) {
316 response.set("ackOnly", true);
317 }
318#endif
319
320 return response;
321}
int requestId
fl::unordered_map< fl::string, detail::RpcEntry > mRegistry
Definition rpc.h:417
Rpc() FL_NO_EXCEPT=default
fl::function< void(const fl::json &)> mResponseSink
Definition rpc.h:418
json schema() const FL_NO_EXCEPT
Returns flat schema document.
Definition rpc.cpp.hpp:399
static TypeConversionResult success() FL_NO_EXCEPT
const char * c_str() const FL_NO_EXCEPT
static json parse(const fl::string &txt) FL_NO_EXCEPT
Definition json.h:849
static json object() FL_NO_EXCEPT
Definition json.h:864
static json array() FL_NO_EXCEPT
Definition json.h:860
#define FL_WARN(...)
Definition log.h:437
#define FL_DBG
Definition log.h:553
#define FL_ERROR(...)
Definition log.h:373
tuple< typename fl::decay< Ts >::type... > make_tuple(Ts &&... args) FL_NO_EXCEPT
Definition tuple.h:106
json makeJsonRpcError(int code, const fl::string &message, const json &id) FL_NO_EXCEPT
fl::function< void(fl::JsonStreamWriter &, const fl::json &, const fl::json &)> StreamingRpcHandler
void streamJsonRpcResultEnvelope(fl::JsonStreamWriter &writer, const fl::json &requestId, bool includeId, const fl::JsonStreamCallback &writeResult) FL_NO_EXCEPT
pair_element< I, T1, T2 >::type & get(pair< T1, T2 > &p) FL_NO_EXCEPT
Definition pair.h:119
#define FL_RPC_ERR_NO_METHOD
Definition rpc.cpp.hpp:127
#define FL_RPC_ERR_INVALID_PARAMS_PREFIX
Definition rpc.cpp.hpp:131
#define FL_RPC_ERR_METHOD_NOT_STR
Definition rpc.cpp.hpp:128
#define FL_RPC_ERR_PARAMS_NOT_ARRAY
Definition rpc.cpp.hpp:130
#define FL_RPC_ERR_METHOD_NOT_FOUND_PREFIX
Definition rpc.cpp.hpp:129

References Rpc(), fl::json::array(), fl::ASYNC, fl::ASYNC_STREAM, fl::basic_string::c_str(), fl::json::contains(), fl::TypeConversionResult::errorMessage(), FL_DBG, FL_ERROR, fl::FL_NO_EXCEPT, FL_RPC_ERR_INVALID_PARAMS_PREFIX, FL_RPC_ERR_METHOD_NOT_FOUND_PREFIX, FL_RPC_ERR_METHOD_NOT_STR, FL_RPC_ERR_NO_METHOD, FL_RPC_ERR_PARAMS_NOT_ARRAY, FL_WARN, fl::get(), fl::TypeConversionResult::hasWarning(), fl::json::is_array(), fl::adl_barrier::make_tuple(), fl::detail::makeJsonRpcError(), fl::detail::RpcEntry::mInvoker, fl::detail::RpcEntry::mIsResponseAware, fl::detail::RpcEntry::mMode, mRegistry, fl::detail::RpcEntry::mResponseAwareFn, mResponseSink, fl::json::object(), fl::TypeConversionResult::ok(), fl::json::parse(), fl::json::push_back(), requestId, schema(), fl::json::set(), fl::streamJsonRpcResultEnvelope(), fl::TypeConversionResult::success(), fl::JsonStreamWriter::valueNull(), and fl::TypeConversionResult::warnings().

Referenced by handle_maybe().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: