feat: implement complete JSON serialization system with response classes
- Add abstract JsonSerializable base class with toJson/fromJson methods - Create comprehensive response classes for complete JSON document handling: * ClusterMembersResponse for cluster member data * TaskStatusResponse and TaskControlResponse for task operations * NodeStatusResponse, NodeEndpointsResponse, NodeOperationResponse for node data - Implement concrete serializable classes for all data types: * NodeInfoSerializable, TaskInfoSerializable, SystemInfoSerializable * TaskSummarySerializable, EndpointInfoSerializable - Refactor all service classes to use new serialization system - Reduce service method complexity from 20-30 lines to 2-3 lines - Eliminate manual JsonDocument creation and field mapping - Ensure type safety and compile-time validation - Maintain backward compatibility while improving maintainability Breaking change: Service classes now use response objects instead of manual JSON creation
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
#include "spore/services/ClusterService.h"
|
||||
#include "spore/core/ApiServer.h"
|
||||
#include "spore/types/ClusterResponse.h"
|
||||
|
||||
using spore::types::ClusterMembersResponse;
|
||||
|
||||
ClusterService::ClusterService(NodeContext& ctx) : ctx(ctx) {}
|
||||
|
||||
@@ -10,33 +13,7 @@ void ClusterService::registerEndpoints(ApiServer& api) {
|
||||
}
|
||||
|
||||
void ClusterService::handleMembersRequest(AsyncWebServerRequest* request) {
|
||||
JsonDocument doc;
|
||||
JsonArray arr = doc["members"].to<JsonArray>();
|
||||
|
||||
for (const auto& pair : *ctx.memberList) {
|
||||
const NodeInfo& node = pair.second;
|
||||
JsonObject obj = arr.add<JsonObject>();
|
||||
obj["hostname"] = node.hostname;
|
||||
obj["ip"] = node.ip.toString();
|
||||
obj["lastSeen"] = node.lastSeen;
|
||||
obj["latency"] = node.latency;
|
||||
obj["status"] = statusToStr(node.status);
|
||||
obj["resources"]["freeHeap"] = node.resources.freeHeap;
|
||||
obj["resources"]["chipId"] = node.resources.chipId;
|
||||
obj["resources"]["sdkVersion"] = node.resources.sdkVersion;
|
||||
obj["resources"]["cpuFreqMHz"] = node.resources.cpuFreqMHz;
|
||||
obj["resources"]["flashChipSize"] = node.resources.flashChipSize;
|
||||
|
||||
// Add labels if present
|
||||
if (!node.labels.empty()) {
|
||||
JsonObject labelsObj = obj["labels"].to<JsonObject>();
|
||||
for (const auto& kv : node.labels) {
|
||||
labelsObj[kv.first.c_str()] = kv.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
ClusterMembersResponse response;
|
||||
response.addNodes(ctx.memberList);
|
||||
request->send(200, "application/json", response.toJsonString());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#include "spore/services/NodeService.h"
|
||||
#include "spore/core/ApiServer.h"
|
||||
#include "spore/util/Logging.h"
|
||||
#include "spore/types/NodeResponse.h"
|
||||
|
||||
using spore::types::NodeStatusResponse;
|
||||
using spore::types::NodeOperationResponse;
|
||||
using spore::types::NodeEndpointsResponse;
|
||||
|
||||
NodeService::NodeService(NodeContext& ctx, ApiServer& apiServer) : ctx(ctx), apiServer(apiServer) {}
|
||||
|
||||
@@ -32,40 +37,31 @@ void NodeService::registerEndpoints(ApiServer& api) {
|
||||
}
|
||||
|
||||
void NodeService::handleStatusRequest(AsyncWebServerRequest* request) {
|
||||
JsonDocument doc;
|
||||
doc["freeHeap"] = ESP.getFreeHeap();
|
||||
doc["chipId"] = ESP.getChipId();
|
||||
doc["sdkVersion"] = ESP.getSdkVersion();
|
||||
doc["cpuFreqMHz"] = ESP.getCpuFreqMHz();
|
||||
doc["flashChipSize"] = ESP.getFlashChipSize();
|
||||
|
||||
// Include local node labels if present
|
||||
NodeStatusResponse response;
|
||||
|
||||
// Get labels from member list or self
|
||||
std::map<String, String> labels;
|
||||
if (ctx.memberList) {
|
||||
auto it = ctx.memberList->find(ctx.hostname);
|
||||
if (it != ctx.memberList->end()) {
|
||||
JsonObject labelsObj = doc["labels"].to<JsonObject>();
|
||||
for (const auto& kv : it->second.labels) {
|
||||
labelsObj[kv.first.c_str()] = kv.second;
|
||||
}
|
||||
labels = it->second.labels;
|
||||
} else if (!ctx.self.labels.empty()) {
|
||||
JsonObject labelsObj = doc["labels"].to<JsonObject>();
|
||||
for (const auto& kv : ctx.self.labels) {
|
||||
labelsObj[kv.first.c_str()] = kv.second;
|
||||
}
|
||||
labels = ctx.self.labels;
|
||||
}
|
||||
}
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
|
||||
response.buildCompleteResponse(labels);
|
||||
request->send(200, "application/json", response.toJsonString());
|
||||
}
|
||||
|
||||
void NodeService::handleUpdateRequest(AsyncWebServerRequest* request) {
|
||||
bool success = !Update.hasError();
|
||||
AsyncWebServerResponse* response = request->beginResponse(200, "application/json",
|
||||
success ? "{\"status\": \"OK\"}" : "{\"status\": \"FAIL\"}");
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
NodeOperationResponse response;
|
||||
response.setSuccess(success ? "OK" : "FAIL");
|
||||
|
||||
AsyncWebServerResponse* httpResponse = request->beginResponse(200, "application/json", response.toJsonString());
|
||||
httpResponse->addHeader("Connection", "close");
|
||||
request->send(httpResponse);
|
||||
request->onDisconnect([this]() {
|
||||
LOG_INFO("API", "Restart device");
|
||||
delay(10);
|
||||
@@ -108,10 +104,12 @@ void NodeService::handleUpdateUpload(AsyncWebServerRequest* request, const Strin
|
||||
}
|
||||
|
||||
void NodeService::handleRestartRequest(AsyncWebServerRequest* request) {
|
||||
AsyncWebServerResponse* response = request->beginResponse(200, "application/json",
|
||||
"{\"status\": \"restarting\"}");
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
NodeOperationResponse response;
|
||||
response.setSuccess("restarting");
|
||||
|
||||
AsyncWebServerResponse* httpResponse = request->beginResponse(200, "application/json", response.toJsonString());
|
||||
httpResponse->addHeader("Connection", "close");
|
||||
request->send(httpResponse);
|
||||
request->onDisconnect([this]() {
|
||||
LOG_INFO("API", "Restart device");
|
||||
delay(10);
|
||||
@@ -120,36 +118,7 @@ void NodeService::handleRestartRequest(AsyncWebServerRequest* request) {
|
||||
}
|
||||
|
||||
void NodeService::handleEndpointsRequest(AsyncWebServerRequest* request) {
|
||||
JsonDocument doc;
|
||||
JsonArray endpointsArr = doc["endpoints"].to<JsonArray>();
|
||||
|
||||
// Add all registered endpoints from ApiServer
|
||||
for (const auto& endpoint : apiServer.getEndpoints()) {
|
||||
JsonObject obj = endpointsArr.add<JsonObject>();
|
||||
obj["uri"] = endpoint.uri;
|
||||
obj["method"] = ApiServer::methodToStr(endpoint.method);
|
||||
if (!endpoint.params.empty()) {
|
||||
JsonArray paramsArr = obj["params"].to<JsonArray>();
|
||||
for (const auto& ps : endpoint.params) {
|
||||
JsonObject p = paramsArr.add<JsonObject>();
|
||||
p["name"] = ps.name;
|
||||
p["location"] = ps.location;
|
||||
p["required"] = ps.required;
|
||||
p["type"] = ps.type;
|
||||
if (!ps.values.empty()) {
|
||||
JsonArray allowed = p["values"].to<JsonArray>();
|
||||
for (const auto& v : ps.values) {
|
||||
allowed.add(v);
|
||||
}
|
||||
}
|
||||
if (ps.defaultValue.length() > 0) {
|
||||
p["default"] = ps.defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
NodeEndpointsResponse response;
|
||||
response.addEndpoints(apiServer.getEndpoints());
|
||||
request->send(200, "application/json", response.toJsonString());
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#include "spore/services/TaskService.h"
|
||||
#include "spore/core/ApiServer.h"
|
||||
#include "spore/types/TaskResponse.h"
|
||||
#include <algorithm>
|
||||
|
||||
using spore::types::TaskStatusResponse;
|
||||
using spore::types::TaskControlResponse;
|
||||
|
||||
TaskService::TaskService(TaskManager& taskManager) : taskManager(taskManager) {}
|
||||
|
||||
void TaskService::registerEndpoints(ApiServer& api) {
|
||||
@@ -35,29 +39,9 @@ void TaskService::handleStatusRequest(AsyncWebServerRequest* request) {
|
||||
JsonDocument scratch;
|
||||
auto taskStatuses = taskManager.getAllTaskStatuses(scratch);
|
||||
|
||||
JsonDocument doc;
|
||||
JsonObject summaryObj = doc["summary"].to<JsonObject>();
|
||||
summaryObj["totalTasks"] = taskStatuses.size();
|
||||
summaryObj["activeTasks"] = std::count_if(taskStatuses.begin(), taskStatuses.end(),
|
||||
[](const auto& pair) { return pair.second["enabled"]; });
|
||||
|
||||
JsonArray tasksArr = doc["tasks"].to<JsonArray>();
|
||||
for (const auto& taskPair : taskStatuses) {
|
||||
JsonObject taskObj = tasksArr.add<JsonObject>();
|
||||
taskObj["name"] = taskPair.first;
|
||||
taskObj["interval"] = taskPair.second["interval"];
|
||||
taskObj["enabled"] = taskPair.second["enabled"];
|
||||
taskObj["running"] = taskPair.second["running"];
|
||||
taskObj["autoStart"] = taskPair.second["autoStart"];
|
||||
}
|
||||
|
||||
JsonObject systemObj = doc["system"].to<JsonObject>();
|
||||
systemObj["freeHeap"] = ESP.getFreeHeap();
|
||||
systemObj["uptime"] = millis();
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(200, "application/json", json);
|
||||
TaskStatusResponse response;
|
||||
response.buildCompleteResponse(taskStatuses);
|
||||
request->send(200, "application/json", response.toJsonString());
|
||||
}
|
||||
|
||||
void TaskService::handleControlRequest(AsyncWebServerRequest* request) {
|
||||
@@ -88,50 +72,27 @@ void TaskService::handleControlRequest(AsyncWebServerRequest* request) {
|
||||
success = true;
|
||||
message = "Task status retrieved";
|
||||
|
||||
JsonDocument statusDoc;
|
||||
statusDoc["success"] = success;
|
||||
statusDoc["message"] = message;
|
||||
statusDoc["task"] = taskName;
|
||||
statusDoc["action"] = action;
|
||||
TaskControlResponse response;
|
||||
response.setResponse(success, message, taskName, action);
|
||||
response.addTaskDetails(taskName,
|
||||
taskManager.isTaskEnabled(taskName.c_str()),
|
||||
taskManager.isTaskRunning(taskName.c_str()),
|
||||
taskManager.getTaskInterval(taskName.c_str()));
|
||||
|
||||
statusDoc["taskDetails"] = JsonObject();
|
||||
JsonObject taskDetails = statusDoc["taskDetails"];
|
||||
taskDetails["name"] = taskName;
|
||||
taskDetails["enabled"] = taskManager.isTaskEnabled(taskName.c_str());
|
||||
taskDetails["running"] = taskManager.isTaskRunning(taskName.c_str());
|
||||
taskDetails["interval"] = taskManager.getTaskInterval(taskName.c_str());
|
||||
|
||||
taskDetails["system"] = JsonObject();
|
||||
JsonObject systemInfo = taskDetails["system"];
|
||||
systemInfo["freeHeap"] = ESP.getFreeHeap();
|
||||
systemInfo["uptime"] = millis();
|
||||
|
||||
String statusJson;
|
||||
serializeJson(statusDoc, statusJson);
|
||||
request->send(200, "application/json", statusJson);
|
||||
request->send(200, "application/json", response.toJsonString());
|
||||
return;
|
||||
} else {
|
||||
success = false;
|
||||
message = "Invalid action. Use: enable, disable, start, stop, or status";
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
doc["success"] = success;
|
||||
doc["message"] = message;
|
||||
doc["task"] = taskName;
|
||||
doc["action"] = action;
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(success ? 200 : 400, "application/json", json);
|
||||
TaskControlResponse response;
|
||||
response.setResponse(success, message, taskName, action);
|
||||
request->send(success ? 200 : 400, "application/json", response.toJsonString());
|
||||
} else {
|
||||
JsonDocument doc;
|
||||
doc["success"] = false;
|
||||
doc["message"] = "Missing parameters. Required: task, action";
|
||||
doc["example"] = "{\"task\": \"discovery_send\", \"action\": \"status\"}";
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
request->send(400, "application/json", json);
|
||||
TaskControlResponse response;
|
||||
response.setError("Missing parameters. Required: task, action",
|
||||
"{\"task\": \"discovery_send\", \"action\": \"status\"}");
|
||||
request->send(400, "application/json", response.toJsonString());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user