Skip to content

Commit

Permalink
apply spec updates to c++ server implementation (#709)
Browse files Browse the repository at this point in the history
### Public-Facing Changes
Apply changes wrt. service definition to example server implementations

### Description

- [X] C++
- [X] Python
- [X] ~Typescript~ (done in #579)
  • Loading branch information
achim-k authored Mar 21, 2024
1 parent b715c77 commit 862b2ca
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 41 deletions.
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

0 comments on commit 862b2ca

Please sign in to comment.