Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apply spec updates to c++ server implementation #709

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions cpp/foxglove-websocket/include/foxglove/websocket/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,23 @@ struct ClientMessage {
}
};

struct ServiceRequestDefinition {
std::string encoding;
std::string schemaName;
std::string schemaEncoding;
std::string schema;
};

using ServiceResponseDefinition = ServiceRequestDefinition;

struct ServiceWithoutId {
std::string name;
std::string type;
std::string requestSchema;
std::string responseSchema;
std::optional<ServiceRequestDefinition> request;
std::optional<ServiceResponseDefinition> response;

std::optional<std::string> requestSchema; // Prefer request instead
std::optional<std::string> responseSchema; // Prefer response instead
};

struct Service : ServiceWithoutId {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,7 @@ void to_json(nlohmann::json& j, const Parameter& p);
void from_json(const nlohmann::json& j, Parameter& p);
void to_json(nlohmann::json& j, const Service& p);
void from_json(const nlohmann::json& j, Service& p);
void to_json(nlohmann::json& j, const ServiceRequestDefinition& p);
void from_json(const nlohmann::json& j, ServiceRequestDefinition& p);

} // namespace foxglove
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,15 @@ inline std::vector<ServiceId> Server<ServerConfiguration>::addServices(
std::vector<ServiceId> serviceIds;
json newServices;
for (const auto& service : services) {
if (!service.request.has_value() && !service.requestSchema.has_value()) {
throw std::runtime_error(
"Invalid service definition: Either `request` or `requestSchema` must be defined");
}
if (!service.response.has_value() && !service.responseSchema.has_value()) {
throw std::runtime_error(
"Invalid service definition: Either `response` or `responseSchema` must be defined");
}

const ServiceId serviceId = ++_nextServiceId;
_services.emplace(serviceId, service);
serviceIds.push_back(serviceId);
Expand Down
49 changes: 45 additions & 4 deletions cpp/foxglove-websocket/src/serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,58 @@ void to_json(nlohmann::json& j, const Service& service) {
{"id", service.id},
{"name", service.name},
{"type", service.type},
{"requestSchema", service.requestSchema},
{"responseSchema", service.responseSchema},
};

if (service.request) {
j["request"] = *service.request;
}
if (service.response) {
j["response"] = *service.response;
}
if (service.requestSchema) {
j["requestSchema"] = *service.requestSchema;
}
if (service.responseSchema) {
j["responseSchema"] = *service.responseSchema;
}
}

void from_json(const nlohmann::json& j, Service& p) {
p.id = j["id"].get<ServiceId>();
p.name = j["name"].get<std::string>();
p.type = j["type"].get<std::string>();
p.requestSchema = j["requestSchema"].get<std::string>();
p.responseSchema = j["responseSchema"].get<std::string>();

if (const auto it = j.find("request"); it != j.end()) {
p.request = it->get<ServiceRequestDefinition>();
}

if (const auto it = j.find("response"); it != j.end()) {
p.response = it->get<ServiceResponseDefinition>();
}

if (const auto it = j.find("requestSchema"); it != j.end()) {
p.requestSchema = it->get<std::string>();
}

if (const auto it = j.find("responseSchema"); it != j.end()) {
p.responseSchema = it->get<std::string>();
}
}

void to_json(nlohmann::json& j, const ServiceRequestDefinition& r) {
j = {
{"encoding", r.encoding},
{"schemaName", r.schemaName},
{"schemaEncoding", r.schemaEncoding},
{"schema", r.schema},
};
}

void from_json(const nlohmann::json& j, ServiceRequestDefinition& r) {
r.encoding = j["encoding"].get<std::string>();
r.schemaName = j["schemaName"].get<std::string>();
r.schemaEncoding = j["schemaEncoding"].get<std::string>();
r.schema = j["schema"].get<std::string>();
}

void ServiceResponse::read(const uint8_t* data, size_t dataLength) {
Expand Down
46 changes: 29 additions & 17 deletions python/src/foxglove_websocket/examples/json_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,23 +87,35 @@ async def on_service_request(
await server.add_service(
{
"name": "example_set_bool",
"requestSchema": json.dumps(
{
"type": "object",
"properties": {
"data": {"type": "boolean"},
},
}
),
"responseSchema": json.dumps(
{
"type": "object",
"properties": {
"success": {"type": "boolean"},
"message": {"type": "string"},
},
}
),
"request": {
"encoding": "json",
"schemaName": "requestSchema",
"schemaEncoding": "jsonschema",
"schema": json.dumps(
{
"type": "object",
"properties": {
"data": {"type": "boolean"},
},
}
),
},
"response": {
"encoding": "json",
"schemaName": "responseSchema",
"schemaEncoding": "jsonschema",
"schema": json.dumps(
{
"type": "object",
"properties": {
"success": {"type": "boolean"},
"message": {"type": "string"},
},
}
),
},
"requestSchema": None,
"responseSchema": None,
"type": "example_set_bool",
}
)
Expand Down
9 changes: 9 additions & 0 deletions python/src/foxglove_websocket/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,15 @@ async def remove_channel(self, chan_id: ChannelId):
)

async def add_service(self, service: ServiceWithoutId) -> ServiceId:
if "request" not in service.keys() and "requestSchema" not in service.keys():
raise ValueError(
f"Invalid service definition: Either 'request' or 'requestSchema' must be defined"
)
if "response" not in service.keys() and "responseSchema" not in service.keys():
raise ValueError(
f"Invalid service definition: Either 'response' or 'responseSchema' must be defined"
)

new_id = self._next_service_id
self._next_service_id = ServiceId(new_id + 1)
new_service = Service(id=new_id, **service)
Expand Down
17 changes: 15 additions & 2 deletions python/src/foxglove_websocket/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,24 @@ class Channel(ChannelWithoutId):
id: ChannelId


class ServiceRequestDefinition(TypedDict):
encoding: str
schemaName: str
schemaEncoding: str
schema: str


class ServiceResponseDefinition(ServiceRequestDefinition):
pass


class ServiceWithoutId(TypedDict):
name: str
type: str
requestSchema: str
responseSchema: str
request: Optional[ServiceRequestDefinition]
response: Optional[ServiceResponseDefinition]
requestSchema: Optional[str] # Prefer request instead
responseSchema: Optional[str] # Prefer response instead


class Service(ServiceWithoutId):
Expand Down
44 changes: 28 additions & 16 deletions python/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,22 +311,34 @@ async def on_service_request(
service: ServiceWithoutId = {
"name": "set_bool",
"type": "set_bool",
"requestSchema": json.dumps(
{
"type": "object",
"properties": {
"data": {"type": "boolean"},
},
}
),
"responseSchema": json.dumps(
{
"type": "object",
"properties": {
"success": {"type": "boolean"},
},
}
),
"request": {
"encoding": "json",
"schemaName": "requestSchema",
"schemaEncoding": "jsonschema",
"schema": json.dumps(
{
"type": "object",
"properties": {
"data": {"type": "boolean"},
},
}
),
},
"response": {
"encoding": "json",
"schemaName": "responseSchema",
"schemaEncoding": "jsonschema",
"schema": json.dumps(
{
"type": "object",
"properties": {
"success": {"type": "boolean"},
},
}
),
},
"requestSchema": None,
"responseSchema": None,
}
service_id = await server.add_service(service)

Expand Down
Loading