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

Cleanup JsonRPC codec #51

Merged
merged 4 commits into from
Nov 14, 2024
Merged

Cleanup JsonRPC codec #51

merged 4 commits into from
Nov 14, 2024

Conversation

sylane
Copy link
Contributor

@sylane sylane commented Nov 13, 2024

  • Move the error formating out.
  • pre-process and post-process the message to handle null/undefined conversion.
  • Always return a list of message, no special case for single/batch.
  • Return decoding errors as proper jsonrpc errors that could be sent right away to the peer.

First bit of mi refactor, the simple one, the rest will get harder....

  • The decoding errors are not really handled yet as they could because this will be part of a later cleanup.
  • The error formatting is moved out because it will be delegated to the different capability handler of the API in a later cleanup. To properly modularise the code, only the part implementing the API should have to be aware of the possible errors.

 * Move the error formating out.
 * pre-process and post-process the message to handle null/undefined
   conversion.
 * Always return a list of message, no special case for single/batch.
 * Return decoding errors as proper jsonrpc errors that could be sent
   right away to the peer.
@sylane sylane requested a review from maehjam November 13, 2024 19:20
Copy link
Member

@maehjam maehjam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we distinguish between a single JSON-RPC request and a batch with only one JSON-RPC request. As I understand the JSON-RPC 2.0 specification we should always response an array when receiving a batch, so I would do that for a batch request with one element as well. I guess that was the reason the differentiation was implemented the way it was.

| {error, Code :: integer(), Message :: undefined | binary(),
Data :: undefined | term(), ReqRef :: undefined | binary() | integer()}
| {decoding_error, Code :: integer(), Message :: undefined | binary(),
Data :: undefined | term(), ReqRef :: undefined | binary()}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we ever have a ReqRef for a decoding error? It is supposed to be the ID of the JSON-PRC request, right? When we can't decode the JSON we don't get the ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that the decoding error is due to invalid JSON or anything else is an implementation detail of the module. If something when wrong, it respond with an error that can be encoded and sent to the peer right away without the need to know what happened. In a way this is the same as an error, but for the sender, not the receiver.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to JSON-RPC in the response object the ID is REQUIRED and MUST be the same as in the request. We can't send a response, when we don't know the ID, we can't know the ID when we can't decode the JSON.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, but you can always send back a generic error without an ID. if you don't have it you don't put one, this is why if can be undefined in the case, but we can still inform the client that there was some errors....

jsx:encode(preprocess(Term)).

postprocess(null) -> undefined;
postprocess(Atom) when is_atom(Atom) -> Atom;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will never be called. jsx does not convert keys to atoms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but post process is called recursively with the content of arrays and the values of the map, so there could be atoms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is called on decoded JSON, that does not have atoms as values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, I mixed up things, and somehow though the values could be atom too. I got bit before by this but it was a key, you are right on this.

postprocess(List) when is_list(List) ->
[postprocess(E) || E <- List];
postprocess(Map) when is_map(Map) ->
maps:from_list([{K, postprocess(V)} || {K, V} <- maps:to_list(Map)]).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use maps:map/2 instead of doing this back and forth conversion to lists.

as_id(Integer) when is_integer(Integer) -> Integer;
as_id(Binary) when is_binary(Binary) -> Binary;
as_id(List) when is_list(List) -> list_to_binary(List);
as_id(Atom) when is_atom(Atom) -> atom_to_binary(Atom).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the atom_to_binary conversion? This is done by the JSON encoder (jsx) and I don't want to mess around with it, if not necessary. Who knows what encoding issues this can produce.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only case I see where it can be called is in a response object, but why would we put in another ID there than what we got (we check that we always get binary or integer with the ?is_id macro).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The atom to binary is to enforce the id to be a binary or an integer without having to know if there is an atom that exists with this name. JSX will convert the id to atom if there is an existing atom and that could lead to strange bugs.

Copy link
Member

@maehjam maehjam Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it will not. It only converts labels to existing atoms not values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the ID somehow becomes an atom, we should crash.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I mixed things up

@sylane sylane merged commit f012db6 into main Nov 14, 2024
1 check passed
@sylane sylane deleted the sylane/cleanup-jsonrpc branch November 14, 2024 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants