FastLED 3.10.8
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 126 of file rpc.cpp.hpp.

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

References 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: