-
Notifications
You must be signed in to change notification settings - Fork 115
rpcserver: Support named and null parameters, Review optionals on rpc internal methods, Stronger Response and Error codes #831
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
base: master
Are you sure you want to change the base?
Changes from all commits
4411380
acae167
bb52e4b
8ecf173
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ pub struct RpcRequest { | |
| pub method: String, | ||
|
|
||
| /// The parameters for the method, as an array of json values. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think the comment should be updated, maybe "an optional array of json values" ?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, nice catch, regarding the new doc; the whole parameter should be a Json value by itself because we expect both an array and an object.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that then make the whole statement of it being only array of json values wrong. Maybe sonething around "JSON value (array or object)" |
||
| pub params: Vec<Value>, | ||
| pub params: Option<Value>, | ||
|
|
||
| /// An optional identifier for the request, which can be used to match responses. | ||
| pub id: Value, | ||
|
|
@@ -29,130 +29,61 @@ pub struct RpcRequest { | |
| /// methods already handle the case where the parameter is missing or has an | ||
| /// unexpected type, returning an error if so. | ||
| pub mod arg_parser { | ||
| use core::str::FromStr; | ||
|
|
||
| use serde::Deserialize; | ||
| use serde_json::Value; | ||
|
|
||
| use crate::json_rpc::res::JsonRpcError; | ||
| use crate::json_rpc::res::jsonrpc_interface::JsonRpcError; | ||
|
|
||
| /// Extracts a u64 parameter from the request parameters at the specified index. | ||
| /// | ||
| /// This function checks if the parameter exists, is of type u64 and can be converted to `T`. | ||
| /// Returns an error otherwise. | ||
| pub fn get_numeric<T: TryFrom<u64>>( | ||
| params: &[Value], | ||
| index: usize, | ||
| opt_name: &str, | ||
| ) -> Result<T, JsonRpcError> { | ||
| let v = params | ||
| .get(index) | ||
| .ok_or_else(|| JsonRpcError::MissingParameter(opt_name.to_string()))?; | ||
|
|
||
| let n = v.as_u64().ok_or_else(|| { | ||
| JsonRpcError::InvalidParameterType(format!("{opt_name} must be a number")) | ||
| })?; | ||
|
|
||
| T::try_from(n) | ||
| .map_err(|_| JsonRpcError::InvalidParameterType(format!("{opt_name} is out-of-range"))) | ||
| } | ||
|
|
||
| /// Extracts a string parameter from the request parameters at the specified index. | ||
| /// | ||
| /// This function checks if the parameter exists and is of type string. Returns an error | ||
| /// otherwise. | ||
| pub fn get_string( | ||
| params: &[Value], | ||
| index: usize, | ||
| opt_name: &str, | ||
| ) -> Result<String, JsonRpcError> { | ||
| let v = params | ||
| .get(index) | ||
| .ok_or_else(|| JsonRpcError::MissingParameter(opt_name.to_string()))?; | ||
|
|
||
| let str = v.as_str().ok_or_else(|| { | ||
| JsonRpcError::InvalidParameterType(format!("{opt_name} must be a string")) | ||
| })?; | ||
|
|
||
| Ok(str.to_string()) | ||
| } | ||
|
|
||
| /// Extracts a boolean parameter from the request parameters at the specified index. | ||
| /// | ||
| /// This function checks if the parameter exists and is of type boolean. Returns an error | ||
| /// otherwise. | ||
| pub fn get_bool(params: &[Value], index: usize, opt_name: &str) -> Result<bool, JsonRpcError> { | ||
| let v = params | ||
| .get(index) | ||
| .ok_or_else(|| JsonRpcError::MissingParameter(opt_name.to_string()))?; | ||
|
|
||
| v.as_bool().ok_or_else(|| { | ||
| JsonRpcError::InvalidParameterType(format!("{opt_name} must be a boolean")) | ||
| }) | ||
| } | ||
|
|
||
| /// Extracts a hash parameter from the request parameters at the specified index. | ||
| /// Extracts a parameter from the request parameters at the specified index. | ||
| /// | ||
| /// This function can extract any type that implements `FromStr`, such as `BlockHash` or | ||
| /// `Txid`. It checks if the parameter exists and is a valid string representation of the type. | ||
| /// Returns an error otherwise. | ||
| pub fn get_hash<T: FromStr>( | ||
| params: &[Value], | ||
| pub fn get_at<'de, T: Deserialize<'de>>( | ||
| params: &'de Value, | ||
| index: usize, | ||
| opt_name: &str, | ||
| field_name: &str, | ||
| ) -> Result<T, JsonRpcError> { | ||
| let v = params | ||
| .get(index) | ||
| .ok_or_else(|| JsonRpcError::MissingParameter(opt_name.to_string()))?; | ||
|
|
||
| v.as_str().and_then(|s| s.parse().ok()).ok_or_else(|| { | ||
| JsonRpcError::InvalidParameterType(format!("{opt_name} must be a valid hash")) | ||
| }) | ||
| let v = match (params.is_array(), params.is_object()) { | ||
| (true, false) => params.get(index), | ||
| (false, true) => params.get(field_name), | ||
| _ => None, | ||
| }; | ||
|
jaoleal marked this conversation as resolved.
|
||
|
|
||
| let unwrap = v.ok_or(JsonRpcError::MissingParameter(field_name.to_string()))?; | ||
|
|
||
| T::deserialize(unwrap) | ||
| .ok() | ||
| .ok_or(JsonRpcError::InvalidParameterType(format!( | ||
| "{field_name} has an invalid type" | ||
| ))) | ||
| } | ||
|
|
||
| /// Extracts an array of hashes from the request parameters at the specified index. | ||
| /// | ||
| /// This function can extract an array of any type that implements `FromStr`, such as | ||
| /// `BlockHash` or `Txid`. It checks if the parameter exists and is an array of valid string | ||
| /// representations of the type. Returns an error otherwise. | ||
| pub fn get_hashes_array<T: FromStr>( | ||
| params: &[Value], | ||
| index: usize, | ||
| opt_name: &str, | ||
| ) -> Result<Vec<T>, JsonRpcError> { | ||
| let v = params | ||
| .get(index) | ||
| .ok_or_else(|| JsonRpcError::MissingParameter(opt_name.to_string()))?; | ||
|
|
||
| let array = v.as_array().ok_or_else(|| { | ||
| JsonRpcError::InvalidParameterType(format!("{opt_name} must be an array of hashes")) | ||
| })?; | ||
|
|
||
| array | ||
| .iter() | ||
| .map(|v| { | ||
| v.as_str().and_then(|s| s.parse().ok()).ok_or_else(|| { | ||
| JsonRpcError::InvalidParameterType(format!("{opt_name} must be a valid hash")) | ||
| }) | ||
| }) | ||
| .collect() | ||
| /// Wraps a parameter extraction result so that a missing parameter yields `Ok(None)` | ||
| /// instead of an error. Other errors are propagated unchanged. | ||
| pub fn try_into_optional<T>( | ||
| result: Result<T, JsonRpcError>, | ||
| ) -> Result<Option<T>, JsonRpcError> { | ||
| match result { | ||
| Ok(t) => Ok(Some(t)), | ||
| Err(JsonRpcError::MissingParameter(_)) => Ok(None), | ||
| Err(e) => Err(e), | ||
| } | ||
| } | ||
|
|
||
| /// Extracts an optional field from the request parameters at the specified index. | ||
| /// | ||
| /// This function checks if the parameter exists and is of the expected type. If the parameter | ||
| /// doesn't exist, it returns `None`. If it exists but is of an unexpected type, it returns an | ||
| /// error. | ||
| pub fn get_optional_field<T>( | ||
| params: &[Value], | ||
| /// Like [`get_at`], but returns `default` when the parameter is missing instead of | ||
| /// an error. Type mismatches are still propagated as errors. | ||
| pub fn get_with_default<'de, T: Deserialize<'de>>( | ||
| v: &'de Value, | ||
| index: usize, | ||
| opt_name: &str, | ||
| extractor_fn: impl Fn(&[Value], usize, &str) -> Result<T, JsonRpcError>, | ||
| ) -> Result<Option<T>, JsonRpcError> { | ||
| if params.len() <= index { | ||
| return Ok(None); | ||
| field_name: &str, | ||
| default: T, | ||
| ) -> Result<T, JsonRpcError> { | ||
| match get_at(v, index, field_name) { | ||
| Ok(t) => Ok(t), | ||
| Err(JsonRpcError::MissingParameter(_)) => Ok(default), | ||
| Err(e) => Err(e), | ||
| } | ||
|
|
||
| let value = extractor_fn(params, index, opt_name)?; | ||
| Ok(Some(value)) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Damn need a concrete error type for chain ASAP, this sucks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be in a follow-up, this PR is already pretty big. But sure, be my guest to take a shot on it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, follow up pr.
Do you have any approach in mind ?