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:
91
include/spore/types/ApiResponse.h
Normal file
91
include/spore/types/ApiResponse.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include "spore/util/JsonSerializable.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
namespace spore {
|
||||
namespace types {
|
||||
|
||||
/**
|
||||
* Base class for API responses that can be serialized to JSON
|
||||
* Handles complete JsonDocument creation and serialization
|
||||
*/
|
||||
class ApiResponse {
|
||||
protected:
|
||||
mutable JsonDocument doc;
|
||||
|
||||
public:
|
||||
virtual ~ApiResponse() = default;
|
||||
|
||||
/**
|
||||
* Get the complete JSON string representation of this response
|
||||
* @return JSON string ready to send as HTTP response
|
||||
*/
|
||||
virtual String toJsonString() const {
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JsonDocument for direct manipulation if needed
|
||||
* @return Reference to the internal JsonDocument
|
||||
*/
|
||||
JsonDocument& getDocument() { return doc; }
|
||||
const JsonDocument& getDocument() const { return doc; }
|
||||
|
||||
/**
|
||||
* Clear the document and reset for reuse
|
||||
*/
|
||||
virtual void clear() {
|
||||
doc.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for API responses that contain a collection of serializable items
|
||||
*/
|
||||
template<typename ItemType>
|
||||
class CollectionResponse : public ApiResponse {
|
||||
protected:
|
||||
String collectionKey;
|
||||
|
||||
public:
|
||||
explicit CollectionResponse(const String& key) : collectionKey(key) {}
|
||||
|
||||
/**
|
||||
* Add a serializable item to the collection
|
||||
* @param item The serializable item to add
|
||||
*/
|
||||
void addItem(const util::JsonSerializable& item) {
|
||||
JsonArray arr = doc[collectionKey].to<JsonArray>();
|
||||
JsonObject obj = arr.add<JsonObject>();
|
||||
item.toJson(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple items from a container
|
||||
* @param items Container of serializable items
|
||||
*/
|
||||
template<typename Container>
|
||||
void addItems(const Container& items) {
|
||||
JsonArray arr = doc[collectionKey].to<JsonArray>();
|
||||
for (const auto& item : items) {
|
||||
JsonObject obj = arr.add<JsonObject>();
|
||||
item.toJson(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current number of items in the collection
|
||||
* @return Number of items
|
||||
*/
|
||||
size_t getItemCount() const {
|
||||
if (doc[collectionKey].is<JsonArray>()) {
|
||||
return doc[collectionKey].as<JsonArray>().size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace spore
|
||||
48
include/spore/types/ClusterResponse.h
Normal file
48
include/spore/types/ClusterResponse.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include "ApiResponse.h"
|
||||
#include "NodeInfoSerializable.h"
|
||||
#include <map>
|
||||
|
||||
namespace spore {
|
||||
namespace types {
|
||||
|
||||
/**
|
||||
* Response class for cluster members endpoint
|
||||
* Handles complete JSON document creation for cluster member data
|
||||
*/
|
||||
class ClusterMembersResponse : public CollectionResponse<NodeInfoSerializable> {
|
||||
public:
|
||||
ClusterMembersResponse() : CollectionResponse("members") {}
|
||||
|
||||
/**
|
||||
* Add a single node to the response
|
||||
* @param node The NodeInfo to add
|
||||
*/
|
||||
void addNode(const NodeInfo& node) {
|
||||
NodeInfoSerializable serializable(const_cast<NodeInfo&>(node));
|
||||
addItem(serializable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple nodes from a member list
|
||||
* @param memberList Map of hostname to NodeInfo
|
||||
*/
|
||||
void addNodes(const std::map<String, NodeInfo>& memberList) {
|
||||
for (const auto& pair : memberList) {
|
||||
addNode(pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add nodes from a pointer to member list
|
||||
* @param memberList Pointer to map of hostname to NodeInfo
|
||||
*/
|
||||
void addNodes(const std::map<String, NodeInfo>* memberList) {
|
||||
if (memberList) {
|
||||
addNodes(*memberList);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace spore
|
||||
76
include/spore/types/EndpointInfoSerializable.h
Normal file
76
include/spore/types/EndpointInfoSerializable.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include "ApiTypes.h"
|
||||
#include "spore/util/JsonSerializable.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
namespace spore {
|
||||
namespace types {
|
||||
|
||||
/**
|
||||
* Serializable wrapper for EndpointInfo that implements JsonSerializable interface
|
||||
* Handles conversion between EndpointInfo struct and JSON representation
|
||||
*/
|
||||
class EndpointInfoSerializable : public util::JsonSerializable {
|
||||
private:
|
||||
const EndpointInfo& endpoint;
|
||||
|
||||
public:
|
||||
explicit EndpointInfoSerializable(const EndpointInfo& ep) : endpoint(ep) {}
|
||||
|
||||
/**
|
||||
* Serialize EndpointInfo to JsonObject
|
||||
*/
|
||||
void toJson(JsonObject& obj) const override {
|
||||
obj["uri"] = endpoint.uri;
|
||||
obj["method"] = endpoint.method;
|
||||
|
||||
// Add parameters if present
|
||||
if (!endpoint.params.empty()) {
|
||||
JsonArray paramsArr = obj["params"].to<JsonArray>();
|
||||
for (const auto& param : endpoint.params) {
|
||||
JsonObject paramObj = paramsArr.add<JsonObject>();
|
||||
paramObj["name"] = param.name;
|
||||
paramObj["location"] = param.location;
|
||||
paramObj["required"] = param.required;
|
||||
paramObj["type"] = param.type;
|
||||
|
||||
if (!param.values.empty()) {
|
||||
JsonArray valuesArr = paramObj["values"].to<JsonArray>();
|
||||
for (const auto& value : param.values) {
|
||||
valuesArr.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (param.defaultValue.length() > 0) {
|
||||
paramObj["default"] = param.defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize EndpointInfo from JsonObject
|
||||
*/
|
||||
void fromJson(const JsonObject& obj) override {
|
||||
// Note: This would require modifying the EndpointInfo struct to be mutable
|
||||
// For now, this is a placeholder as EndpointInfo is typically read-only
|
||||
// in the context where this serialization is used
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience function to create a JsonArray from a collection of EndpointInfo objects
|
||||
*/
|
||||
template<typename Container>
|
||||
JsonArray endpointInfoToJsonArray(JsonDocument& doc, const Container& endpoints) {
|
||||
JsonArray arr = doc.to<JsonArray>();
|
||||
for (const auto& endpoint : endpoints) {
|
||||
EndpointInfoSerializable serializable(endpoint);
|
||||
JsonObject obj = arr.add<JsonObject>();
|
||||
serializable.toJson(obj);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace spore
|
||||
141
include/spore/types/NodeInfoSerializable.h
Normal file
141
include/spore/types/NodeInfoSerializable.h
Normal file
@@ -0,0 +1,141 @@
|
||||
#pragma once
|
||||
#include "NodeInfo.h"
|
||||
#include "ApiTypes.h"
|
||||
#include "spore/util/JsonSerializable.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
namespace spore {
|
||||
namespace types {
|
||||
|
||||
/**
|
||||
* Serializable wrapper for NodeInfo that implements JsonSerializable interface
|
||||
* Handles conversion between NodeInfo struct and JSON representation
|
||||
*/
|
||||
class NodeInfoSerializable : public util::JsonSerializable {
|
||||
private:
|
||||
NodeInfo& nodeInfo;
|
||||
|
||||
public:
|
||||
explicit NodeInfoSerializable(NodeInfo& node) : nodeInfo(node) {}
|
||||
|
||||
/**
|
||||
* Serialize NodeInfo to JsonObject
|
||||
* Maps all NodeInfo fields to appropriate JSON structure
|
||||
*/
|
||||
void toJson(JsonObject& obj) const override {
|
||||
obj["hostname"] = nodeInfo.hostname;
|
||||
obj["ip"] = nodeInfo.ip.toString();
|
||||
obj["lastSeen"] = nodeInfo.lastSeen;
|
||||
obj["latency"] = nodeInfo.latency;
|
||||
obj["status"] = statusToStr(nodeInfo.status);
|
||||
|
||||
// Serialize resources
|
||||
JsonObject resources = obj["resources"].to<JsonObject>();
|
||||
resources["freeHeap"] = nodeInfo.resources.freeHeap;
|
||||
resources["chipId"] = nodeInfo.resources.chipId;
|
||||
resources["sdkVersion"] = nodeInfo.resources.sdkVersion;
|
||||
resources["cpuFreqMHz"] = nodeInfo.resources.cpuFreqMHz;
|
||||
resources["flashChipSize"] = nodeInfo.resources.flashChipSize;
|
||||
|
||||
// Serialize labels if present
|
||||
if (!nodeInfo.labels.empty()) {
|
||||
JsonObject labels = obj["labels"].to<JsonObject>();
|
||||
for (const auto& kv : nodeInfo.labels) {
|
||||
labels[kv.first.c_str()] = kv.second;
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize endpoints if present
|
||||
if (!nodeInfo.endpoints.empty()) {
|
||||
JsonArray endpoints = obj["api"].to<JsonArray>();
|
||||
for (const auto& endpoint : nodeInfo.endpoints) {
|
||||
JsonObject endpointObj = endpoints.add<JsonObject>();
|
||||
endpointObj["uri"] = endpoint.uri;
|
||||
endpointObj["method"] = endpoint.method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize NodeInfo from JsonObject
|
||||
* Populates NodeInfo fields from JSON structure
|
||||
*/
|
||||
void fromJson(const JsonObject& obj) override {
|
||||
nodeInfo.hostname = obj["hostname"].as<String>();
|
||||
|
||||
// Parse IP address
|
||||
const char* ipStr = obj["ip"];
|
||||
if (ipStr) {
|
||||
nodeInfo.ip.fromString(ipStr);
|
||||
}
|
||||
|
||||
nodeInfo.lastSeen = obj["lastSeen"].as<unsigned long>();
|
||||
nodeInfo.latency = obj["latency"].as<unsigned long>();
|
||||
|
||||
// Parse status
|
||||
const char* statusStr = obj["status"];
|
||||
if (statusStr) {
|
||||
if (strcmp(statusStr, "ACTIVE") == 0) {
|
||||
nodeInfo.status = NodeInfo::ACTIVE;
|
||||
} else if (strcmp(statusStr, "INACTIVE") == 0) {
|
||||
nodeInfo.status = NodeInfo::INACTIVE;
|
||||
} else if (strcmp(statusStr, "DEAD") == 0) {
|
||||
nodeInfo.status = NodeInfo::DEAD;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse resources
|
||||
if (obj["resources"].is<JsonObject>()) {
|
||||
JsonObject resources = obj["resources"].as<JsonObject>();
|
||||
nodeInfo.resources.freeHeap = resources["freeHeap"].as<uint32_t>();
|
||||
nodeInfo.resources.chipId = resources["chipId"].as<uint32_t>();
|
||||
nodeInfo.resources.sdkVersion = resources["sdkVersion"].as<String>();
|
||||
nodeInfo.resources.cpuFreqMHz = resources["cpuFreqMHz"].as<uint32_t>();
|
||||
nodeInfo.resources.flashChipSize = resources["flashChipSize"].as<uint32_t>();
|
||||
}
|
||||
|
||||
// Parse labels
|
||||
nodeInfo.labels.clear();
|
||||
if (obj["labels"].is<JsonObject>()) {
|
||||
JsonObject labels = obj["labels"].as<JsonObject>();
|
||||
for (JsonPair kvp : labels) {
|
||||
nodeInfo.labels[kvp.key().c_str()] = kvp.value().as<String>();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse endpoints
|
||||
nodeInfo.endpoints.clear();
|
||||
if (obj["api"].is<JsonArray>()) {
|
||||
JsonArray endpoints = obj["api"].as<JsonArray>();
|
||||
for (JsonObject endpointObj : endpoints) {
|
||||
EndpointInfo endpoint;
|
||||
endpoint.uri = endpointObj["uri"].as<String>();
|
||||
endpoint.method = endpointObj["method"].as<int>();
|
||||
endpoint.isLocal = false;
|
||||
endpoint.serviceName = "remote";
|
||||
nodeInfo.endpoints.push_back(std::move(endpoint));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience function to create a JsonArray from a collection of NodeInfo objects
|
||||
* @param doc The JsonDocument to create the array in
|
||||
* @param nodes Collection of NodeInfo objects
|
||||
* @return A JsonArray containing all serialized NodeInfo objects
|
||||
*/
|
||||
template<typename Container>
|
||||
JsonArray nodeInfoToJsonArray(JsonDocument& doc, const Container& nodes) {
|
||||
JsonArray arr = doc.to<JsonArray>();
|
||||
for (const auto& pair : nodes) {
|
||||
const NodeInfo& node = pair.second;
|
||||
NodeInfoSerializable serializable(const_cast<NodeInfo&>(node));
|
||||
JsonObject obj = arr.add<JsonObject>();
|
||||
serializable.toJson(obj);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
} // namespace types
|
||||
} // namespace spore
|
||||
102
include/spore/types/NodeResponse.h
Normal file
102
include/spore/types/NodeResponse.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#include "ApiResponse.h"
|
||||
#include "EndpointInfoSerializable.h"
|
||||
#include "NodeInfo.h"
|
||||
#include <vector>
|
||||
|
||||
namespace spore {
|
||||
namespace types {
|
||||
|
||||
/**
|
||||
* Response class for node status endpoint
|
||||
* Handles complete JSON document creation for node status data
|
||||
*/
|
||||
class NodeStatusResponse : public ApiResponse {
|
||||
public:
|
||||
/**
|
||||
* Set basic system information
|
||||
*/
|
||||
void setSystemInfo() {
|
||||
doc["freeHeap"] = ESP.getFreeHeap();
|
||||
doc["chipId"] = ESP.getChipId();
|
||||
doc["sdkVersion"] = ESP.getSdkVersion();
|
||||
doc["cpuFreqMHz"] = ESP.getCpuFreqMHz();
|
||||
doc["flashChipSize"] = ESP.getFlashChipSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add labels to the response
|
||||
* @param labels Map of label key-value pairs
|
||||
*/
|
||||
void addLabels(const std::map<String, String>& labels) {
|
||||
if (!labels.empty()) {
|
||||
JsonObject labelsObj = doc["labels"].to<JsonObject>();
|
||||
for (const auto& kv : labels) {
|
||||
labelsObj[kv.first.c_str()] = kv.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build complete response with system info and labels
|
||||
* @param labels Optional labels to include
|
||||
*/
|
||||
void buildCompleteResponse(const std::map<String, String>& labels = {}) {
|
||||
setSystemInfo();
|
||||
addLabels(labels);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Response class for node endpoints endpoint
|
||||
* Handles complete JSON document creation for endpoint data
|
||||
*/
|
||||
class NodeEndpointsResponse : public CollectionResponse<EndpointInfoSerializable> {
|
||||
public:
|
||||
NodeEndpointsResponse() : CollectionResponse("endpoints") {}
|
||||
|
||||
/**
|
||||
* Add a single endpoint to the response
|
||||
* @param endpoint The EndpointInfo to add
|
||||
*/
|
||||
void addEndpoint(const EndpointInfo& endpoint) {
|
||||
EndpointInfoSerializable serializable(endpoint);
|
||||
addItem(serializable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple endpoints from a container
|
||||
* @param endpoints Container of EndpointInfo objects
|
||||
*/
|
||||
void addEndpoints(const std::vector<EndpointInfo>& endpoints) {
|
||||
for (const auto& endpoint : endpoints) {
|
||||
addEndpoint(endpoint);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Response class for simple status operations (update, restart)
|
||||
* Handles simple JSON responses for node operations
|
||||
*/
|
||||
class NodeOperationResponse : public ApiResponse {
|
||||
public:
|
||||
/**
|
||||
* Set success response
|
||||
* @param status Status message
|
||||
*/
|
||||
void setSuccess(const String& status) {
|
||||
doc["status"] = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error response
|
||||
* @param status Error status message
|
||||
*/
|
||||
void setError(const String& status) {
|
||||
doc["status"] = status;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace spore
|
||||
96
include/spore/types/TaskInfoSerializable.h
Normal file
96
include/spore/types/TaskInfoSerializable.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
#include "spore/util/JsonSerializable.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace spore {
|
||||
namespace types {
|
||||
|
||||
/**
|
||||
* Serializable wrapper for task information that implements JsonSerializable interface
|
||||
* Handles conversion between task data and JSON representation
|
||||
*/
|
||||
class TaskInfoSerializable : public util::JsonSerializable {
|
||||
private:
|
||||
const String& taskName;
|
||||
const JsonObject& taskData;
|
||||
|
||||
public:
|
||||
TaskInfoSerializable(const String& name, const JsonObject& data)
|
||||
: taskName(name), taskData(data) {}
|
||||
|
||||
/**
|
||||
* Serialize task info to JsonObject
|
||||
*/
|
||||
void toJson(JsonObject& obj) const override {
|
||||
obj["name"] = taskName;
|
||||
obj["interval"] = taskData["interval"];
|
||||
obj["enabled"] = taskData["enabled"];
|
||||
obj["running"] = taskData["running"];
|
||||
obj["autoStart"] = taskData["autoStart"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize task info from JsonObject
|
||||
* Note: This is read-only for task status, so fromJson is not implemented
|
||||
*/
|
||||
void fromJson(const JsonObject& obj) override {
|
||||
// Task info is typically read-only in this context
|
||||
// Implementation would go here if needed
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Serializable wrapper for system information
|
||||
*/
|
||||
class SystemInfoSerializable : public util::JsonSerializable {
|
||||
public:
|
||||
/**
|
||||
* Serialize system info to JsonObject
|
||||
*/
|
||||
void toJson(JsonObject& obj) const override {
|
||||
obj["freeHeap"] = ESP.getFreeHeap();
|
||||
obj["uptime"] = millis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize system info from JsonObject
|
||||
* Note: System info is typically read-only, so fromJson is not implemented
|
||||
*/
|
||||
void fromJson(const JsonObject& obj) override {
|
||||
// System info is typically read-only
|
||||
// Implementation would go here if needed
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Serializable wrapper for task summary information
|
||||
*/
|
||||
class TaskSummarySerializable : public util::JsonSerializable {
|
||||
private:
|
||||
size_t totalTasks;
|
||||
size_t activeTasks;
|
||||
|
||||
public:
|
||||
TaskSummarySerializable(size_t total, size_t active)
|
||||
: totalTasks(total), activeTasks(active) {}
|
||||
|
||||
/**
|
||||
* Serialize task summary to JsonObject
|
||||
*/
|
||||
void toJson(JsonObject& obj) const override {
|
||||
obj["totalTasks"] = totalTasks;
|
||||
obj["activeTasks"] = activeTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize task summary from JsonObject
|
||||
*/
|
||||
void fromJson(const JsonObject& obj) override {
|
||||
totalTasks = obj["totalTasks"].as<size_t>();
|
||||
activeTasks = obj["activeTasks"].as<size_t>();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace spore
|
||||
171
include/spore/types/TaskResponse.h
Normal file
171
include/spore/types/TaskResponse.h
Normal file
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
#include "ApiResponse.h"
|
||||
#include "TaskInfoSerializable.h"
|
||||
#include <map>
|
||||
|
||||
namespace spore {
|
||||
namespace types {
|
||||
|
||||
/**
|
||||
* Response class for task status endpoint
|
||||
* Handles complete JSON document creation for task status data
|
||||
*/
|
||||
class TaskStatusResponse : public ApiResponse {
|
||||
public:
|
||||
/**
|
||||
* Set the task summary information
|
||||
* @param totalTasks Total number of tasks
|
||||
* @param activeTasks Number of active tasks
|
||||
*/
|
||||
void setSummary(size_t totalTasks, size_t activeTasks) {
|
||||
TaskSummarySerializable summary(totalTasks, activeTasks);
|
||||
JsonObject summaryObj = doc["summary"].to<JsonObject>();
|
||||
summary.toJson(summaryObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single task to the response
|
||||
* @param taskName Name of the task
|
||||
* @param taskData Task data as JsonObject
|
||||
*/
|
||||
void addTask(const String& taskName, const JsonObject& taskData) {
|
||||
TaskInfoSerializable serializable(taskName, taskData);
|
||||
JsonArray tasksArr = doc["tasks"].to<JsonArray>();
|
||||
JsonObject taskObj = tasksArr.add<JsonObject>();
|
||||
serializable.toJson(taskObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single task to the response (std::string version)
|
||||
* @param taskName Name of the task
|
||||
* @param taskData Task data as JsonObject
|
||||
*/
|
||||
void addTask(const std::string& taskName, const JsonObject& taskData) {
|
||||
TaskInfoSerializable serializable(String(taskName.c_str()), taskData);
|
||||
JsonArray tasksArr = doc["tasks"].to<JsonArray>();
|
||||
JsonObject taskObj = tasksArr.add<JsonObject>();
|
||||
serializable.toJson(taskObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple tasks from a task statuses map
|
||||
* @param taskStatuses Map of task name to task data
|
||||
*/
|
||||
void addTasks(const std::map<String, JsonObject>& taskStatuses) {
|
||||
for (const auto& pair : taskStatuses) {
|
||||
addTask(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the system information
|
||||
*/
|
||||
void setSystemInfo() {
|
||||
SystemInfoSerializable systemInfo;
|
||||
JsonObject systemObj = doc["system"].to<JsonObject>();
|
||||
systemInfo.toJson(systemObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build complete response with all components
|
||||
* @param taskStatuses Map of task name to task data
|
||||
*/
|
||||
void buildCompleteResponse(const std::map<String, JsonObject>& taskStatuses) {
|
||||
// Set summary
|
||||
size_t totalTasks = taskStatuses.size();
|
||||
size_t activeTasks = 0;
|
||||
for (const auto& pair : taskStatuses) {
|
||||
if (pair.second["enabled"]) {
|
||||
activeTasks++;
|
||||
}
|
||||
}
|
||||
setSummary(totalTasks, activeTasks);
|
||||
|
||||
// Add all tasks
|
||||
addTasks(taskStatuses);
|
||||
|
||||
// Set system info
|
||||
setSystemInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build complete response with all components from vector
|
||||
* @param taskStatuses Vector of pairs of task name to task data
|
||||
*/
|
||||
void buildCompleteResponse(const std::vector<std::pair<std::string, JsonObject>>& taskStatuses) {
|
||||
// Set summary
|
||||
size_t totalTasks = taskStatuses.size();
|
||||
size_t activeTasks = 0;
|
||||
for (const auto& pair : taskStatuses) {
|
||||
if (pair.second["enabled"]) {
|
||||
activeTasks++;
|
||||
}
|
||||
}
|
||||
setSummary(totalTasks, activeTasks);
|
||||
|
||||
// Add all tasks
|
||||
for (const auto& pair : taskStatuses) {
|
||||
addTask(pair.first, pair.second);
|
||||
}
|
||||
|
||||
// Set system info
|
||||
setSystemInfo();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Response class for task control operations
|
||||
* Handles JSON responses for task enable/disable/start/stop operations
|
||||
*/
|
||||
class TaskControlResponse : public ApiResponse {
|
||||
public:
|
||||
/**
|
||||
* Set the response data for a task control operation
|
||||
* @param success Whether the operation was successful
|
||||
* @param message Response message
|
||||
* @param taskName Name of the task
|
||||
* @param action Action performed
|
||||
*/
|
||||
void setResponse(bool success, const String& message, const String& taskName, const String& action) {
|
||||
doc["success"] = success;
|
||||
doc["message"] = message;
|
||||
doc["task"] = taskName;
|
||||
doc["action"] = action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add detailed task information to the response
|
||||
* @param taskName Name of the task
|
||||
* @param enabled Whether task is enabled
|
||||
* @param running Whether task is running
|
||||
* @param interval Task interval
|
||||
*/
|
||||
void addTaskDetails(const String& taskName, bool enabled, bool running, unsigned long interval) {
|
||||
JsonObject taskDetails = doc["taskDetails"].to<JsonObject>();
|
||||
taskDetails["name"] = taskName;
|
||||
taskDetails["enabled"] = enabled;
|
||||
taskDetails["running"] = running;
|
||||
taskDetails["interval"] = interval;
|
||||
|
||||
// Add system info
|
||||
SystemInfoSerializable systemInfo;
|
||||
JsonObject systemObj = taskDetails["system"].to<JsonObject>();
|
||||
systemInfo.toJson(systemObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error response
|
||||
* @param message Error message
|
||||
* @param example Optional example for correct usage
|
||||
*/
|
||||
void setError(const String& message, const String& example = "") {
|
||||
doc["success"] = false;
|
||||
doc["message"] = message;
|
||||
if (example.length() > 0) {
|
||||
doc["example"] = example;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace types
|
||||
} // namespace spore
|
||||
56
include/spore/util/JsonSerializable.h
Normal file
56
include/spore/util/JsonSerializable.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
namespace spore {
|
||||
namespace util {
|
||||
|
||||
/**
|
||||
* Abstract base class for objects that can be serialized to/from JSON
|
||||
* Provides a clean interface for converting objects to JsonObject and back
|
||||
*/
|
||||
class JsonSerializable {
|
||||
public:
|
||||
virtual ~JsonSerializable() = default;
|
||||
|
||||
/**
|
||||
* Serialize this object to a JsonObject
|
||||
* @param obj The JsonObject to populate with this object's data
|
||||
*/
|
||||
virtual void toJson(JsonObject& obj) const = 0;
|
||||
|
||||
/**
|
||||
* Deserialize this object from a JsonObject
|
||||
* @param obj The JsonObject containing the data to populate this object
|
||||
*/
|
||||
virtual void fromJson(const JsonObject& obj) = 0;
|
||||
|
||||
/**
|
||||
* Convenience method to create a JsonObject from this object
|
||||
* @param doc The JsonDocument to create the object in
|
||||
* @return A JsonObject containing this object's serialized data
|
||||
*/
|
||||
JsonObject toJsonObject(JsonDocument& doc) const {
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
toJson(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to create a JsonArray from a collection of serializable objects
|
||||
* @param doc The JsonDocument to create the array in
|
||||
* @param objects Collection of objects implementing JsonSerializable
|
||||
* @return A JsonArray containing all serialized objects
|
||||
*/
|
||||
template<typename Container>
|
||||
static JsonArray toJsonArray(JsonDocument& doc, const Container& objects) {
|
||||
JsonArray arr = doc.to<JsonArray>();
|
||||
for (const auto& obj : objects) {
|
||||
JsonObject item = arr.add<JsonObject>();
|
||||
obj.toJson(item);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace spore
|
||||
Reference in New Issue
Block a user