Add support for JSON RPC protocol - #101
Conversation
b321999 to
943c2c2
Compare
be3bc10 to
f5e5fa6
Compare
|
I've been playing with this for a bit this week, I still have to get to terms with how to build a "correct" schema that is modular and not overly complex, but this does already look pretty promising. The part that I didn't consider is that, for reading, the json objects / properties can come in any order and if the "method" comes after the "params" section, that makes linear parsing impossible - I still have to look at your two pass solution for this. also, I notice that my json-rpc implementation is probably quite wrong, as I have not properly implemented the response format in the past. |
|
so I created a sample that creates a few of the messages I use in my protocol, including requests, responses and notifications. It took a bit of getting my head around as my schema has a oneOf as the root element - which I think is necessary to not get a named envelope? I wonder if there is a (sensible) way of abstracting the message generation, technically, I should be able to write a function that takes the message type (eg colorRequest, colorResponse or colorEvent, or maybe one for Request, Response and Event taking a paramter for the method) along with the parameters union (or actually a pointer to it) that the calling code would pass the fitting parameter structure. [edit] within ConfigDB, as far as I understand, there is a structure defined as containing r,g,b,ww and cw.
Maybe, the manual construction of messages is the right way, as passing parameters to a function would also mean additional stack use. [edit 2] |
|
another thing: "error": {
"type": "object",
"properties": {
"code": {
"$ref": "value-types/$defs/error-code"
},
"message": {
"$ref": "value-types/$defs/string-value"
},
"data": {
"$ref": "value-types/$defs/string-value"
}
},
"required": [
"code",
"message"
],
"additionalProperties": false
}that get's wrapped into an error response envelope: {
"type":"object",
"title":"error-response",
"properties": {
"jsonrpc": { "$ref": "value-types/$defs/jsonrpc-version" },
"id": { "$ref": "value-types/$defs/jsonrpc-id" },
"error": { "$ref": "params/$defs/error" }
},
"required": ["jsonrpc", "id", "error"],
"additionalProperties": false
}I use this function to generate any sort of error response: [[maybe_unused]] bool generateErrorResponse(Jsonrpc& db, int id, const ErrorType error, String data=NULL)
{
{
Jsonrpc::Root root(db);
if(auto update = root.update()) {
auto message = update.toErrorResponse();
message.setId(id);
auto result = message.error;
switch(error) {
case ParseError:
result.setCode(-32700);
result.setMessage("Parse error");
break;
case InvalidRequest:
result.setCode(-32600);
result.setMessage("Invalid Request");
break;
case MethodNotFound:
result.setCode(-32601);
result.setMessage("Method not found");
break;
case InvalidParams:
result.setCode(-32602);
result.setMessage("Invalid params");
break;
case InternalError:
result.setCode(-32603);
result.setMessage("Internal error");
break;
case ApplicationError1:
result.setCode(-32000);
result.setMessage("Application error 1");
if(data!=NULL)
result.setData(data);
break;
case ApplicationError2:
result.setCode(-32001);
result.setMessage("Application error 2");
if(data!=NULL)
result.setData(data);
break;
case ApplicationError3:
result.setCode(-32002);
result.setMessage("Application error 3");
if(data!=NULL)
result.setData(data);
break;
}
printMessage(message);
root.clearDirty();
} else {
return false;
}
}
return true;
}if I call the function with a {
"error": {
"code": -32001,
"message": "Application error 2",
"data": "something went horribly wrong"
},
"jsonrpc": "2.0",
"id": 2
}but when I omit the {
"error": {
"code": -32601,
"message": "Method not found",
"data": null
},
"jsonrpc": "2.0",
"id": 2
}I guess while that's syntactically okay and would make sense when using the api to unset a value, in this case, it would be preferrable to just not generate the |
That would require implementing change tracking in ConfigDB. That's currently work in progress, should have a draft PR for that in a while. My feeling is that we shouldn't be attempting to address any of the protocol elements of JsonRPC in ConfigDB. NB. In the This approach would help to simplify the RGBWWJson schema considerably. |
|
Note: ConfigDB doesn't recognise or do anything with |
noted, although, I think it would be nice to just code it in flash rather than hold a ram value for it. |
…notifications for color and info
So the Stuff like colour information is just numbers so there's no issue with using the generated Structs if it's convenient. But of course what's missing is a way to update an object using the struct instead of individual fields. That would be pretty easy to do since internally it's just a Example for struct __attribute__((packed)) Struct {
uint16_t r{0};
uint16_t g{0};
uint16_t b{0};
uint16_t ww{0};
uint16_t cw{0};
};We could generate a method within const Struct getStruct() const;and within void setStruct(const Struct& value); |
|
this morning, I was trying to implement receive messages, which turns out to be more difficult than I initially thought, as it isn't immediately obvious to me how I would select a schema. {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"info": {
"oneOf": [
{
"ctype": "InfoRequest",
"type": "object",
"title": "info-request",
"properties": {
"jsonrpc": { "$ref": "value-types/$defs/jsonrpc-version" },
"id": { "$ref": "value-types/$defs/jsonrpc-id" },
"method": { "$ref": "value-types/$defs/info-method" }
},
"required": ["jsonrpc", "id", "method"],
"additionalProperties": false
},
{
"ctype": "InfoResponse",
"type": "object",
"title": "info-response",
"properties": {
"jsonrpc": { "$ref": "value-types/$defs/jsonrpc-version" },
"id": { "$ref": "value-types/$defs/jsonrpc-id" },
"result": {
"oneOf": [
{ "$ref": "params/$defs/info-v1-params" },
{ "$ref": "params/$defs/info-v2-params" }
]
}
},
"required": ["jsonrpc", "id", "result"],
"additionalProperties": false
},
{
"ctype": "InfoEvent",
"type": "object",
"title": "info-event",
"properties": {
"jsonrpc": { "$ref": "value-types/$defs/jsonrpc-version" },
"method": { "$ref": "value-types/$defs/info-method" },
"params": {
"oneOf": [
{ "$ref": "params/$defs/info-v1-params" },
{ "$ref": "params/$defs/info-v2-params" }
]
}
},
"required": ["jsonrpc", "method", "params"],
"additionalProperties": false
}
]
}
}
}ConfigDB does not use anchor today, right? |
|
I've moved the JsonRPC module into the library and reworked your sample code. Does any of that make sense? |
This PR takes a look at how ConfigDB might be used to support processing and generation of JSON RPC messages.
All messages require a standard
"jsonrpc": "2.0"andidproperty (except notifications).A request requires
methodandparamsproperties.A Response requires
resulton success, anderroron failure.The general idea is that the application defines a ConfigDB schema defining the structure for
params,resultanderrorobjects. A union (oneOf) is appropriate since any message may contain only one of these items.For parsing, it is necessary to first scan the message to extract the standard fields and establish what kind of message it is.
Request
The
paramsis itself a union, and the tag corresponds to the method.If
paramsappears beforemethodthen a second pass is required.Response
Processing responses is tricker as there is nothing in the message to indicate how
resultis structured. Presumably the application would keep a note of outgoing request{id: method}mappings and use that to determine the expected result. That mapping might include a callback for handling the response.Error
The
codeandmessagefields are standard, butdatais variable and so is likely to be application-specific.TODO: