lsp_primitives/lsps0/
parameter_validation.rs

1//! Parameter validation for LSP-servers.
2//!
3//! Some functionality to deserialize requests and
4//! get custom error-types.
5//!
6//! These error-types can be easily converted to LSPS0-compliant
7//! error data.
8
9use crate::json_rpc::ErrorData;
10use serde::{Deserialize, Serialize};
11use serde_json::{Map, Value};
12
13///
14#[derive(Clone, Debug, Serialize, Deserialize)]
15#[serde(tag = "type")]
16#[serde(rename_all = "snake_case")]
17pub enum ParamValidationError {
18    Custom(Custom),
19    Unrecognized(Unrecognized),
20    InvalidParam(InvalidParam),
21}
22
23impl ParamValidationError {
24    pub fn custom(message: String) -> Self {
25        Self::Custom(Custom { message })
26    }
27
28    pub fn unrecognized(unrecognized: Vec<String>) -> Self {
29        Self::Unrecognized(Unrecognized { unrecognized })
30    }
31
32    pub fn invalid_params(property: String, message: String) -> Self {
33        Self::InvalidParam(InvalidParam { property, message })
34    }
35}
36
37impl From<ParamValidationError> for ErrorData<serde_json::Value> {
38    fn from(err: ParamValidationError) -> Self {
39        let result_ser = serde_json::to_value(err);
40        match result_ser {
41            Ok(data) => ErrorData::invalid_params(data),
42            Err(err) => ErrorData::internal_error(Value::String(format!(
43                "Deserialization failed: {:?}",
44                err
45            ))),
46        }
47    }
48}
49
50#[derive(Clone, Debug, Serialize, Deserialize)]
51pub struct Custom {
52    message: String,
53}
54
55#[derive(Clone, Debug, Serialize, Deserialize)]
56pub struct Unrecognized {
57    unrecognized: Vec<String>,
58}
59
60#[derive(Clone, Debug, Serialize, Deserialize)]
61pub struct InvalidParam {
62    property: String,
63    message: String,
64}
65
66pub trait ExpectedFields {
67    fn expected_fields() -> Vec<String>;
68}
69
70/// Parses a request from a json_value
71pub fn from_value<'de, T: Deserialize<'de> + ExpectedFields>(
72    value: serde_json::Value,
73) -> Result<T, ParamValidationError> {
74    let value = match value {
75        Value::Object(_) => value,
76        Value::Null => Value::Null,
77        _ => {
78            return Err(ParamValidationError::Custom(Custom {
79                message: "Arguments should be passed by name".to_string(),
80            }))
81        }
82    };
83
84    // Find unreconginsed arguments
85    let expected_fields = T::expected_fields();
86    let expected_fields: Vec<&str> = expected_fields.iter().map(|x| x.as_ref()).collect();
87    let unrecognized = list_unrecogninzed_fields(expected_fields.as_ref(), &value);
88    if !unrecognized.is_empty() {
89        return Err(ParamValidationError::Unrecognized(Unrecognized {
90            unrecognized,
91        }));
92    }
93
94    serde_path_to_error::deserialize::<'de, _, T>(value).map_err(|e| {
95        ParamValidationError::InvalidParam(InvalidParam {
96            property: e.path().to_string(),
97            message: e.to_string(),
98        })
99    })
100}
101
102/// Computes a list of unrecognized fields
103fn list_unrecogninzed_fields(
104    expected_arguments: &[&str],
105    json_value: &serde_json::Value,
106) -> Vec<String> {
107    match json_value {
108        Value::Object(map) => {
109            let mut current_prefix = Vec::new();
110            let mut current_result = Vec::new();
111            list_unrecognized_fields_impl(
112                expected_arguments,
113                &map,
114                &mut current_prefix,
115                &mut current_result,
116            )
117        }
118        _ => Vec::new(),
119    }
120}
121
122fn list_unrecognized_fields_impl(
123    expected_arguments: &[&str],
124    map: &Map<String, Value>,
125    current_prefix: &mut Vec<Box<str>>,
126    current_result: &mut Vec<String>,
127) -> Vec<String> {
128    for (name, value) in map {
129        match value {
130            Value::Object(map) => {
131                current_prefix.push(name.clone().into_boxed_str());
132                list_unrecognized_fields_impl(
133                    expected_arguments,
134                    map,
135                    current_prefix,
136                    current_result,
137                );
138                current_prefix.pop();
139            }
140            _ => {
141                current_prefix.push(name.clone().into_boxed_str());
142                let argument = current_prefix.join(".");
143                if !expected_arguments.contains(&argument.as_ref()) {
144                    current_result.push(argument.to_string());
145                }
146                current_prefix.pop();
147            }
148        };
149    }
150
151    current_result.clone()
152}
153
154#[cfg(test)]
155mod test {
156    use super::*;
157    use crate::lsps0::parameter_validation::ParamValidationError;
158    use serde_json::json;
159
160    #[test]
161    fn test_find_unrecognized_arguments() {
162        struct Case<'a> {
163            value: serde_json::Value,
164            expected_arguments: &'a [&'a str],
165            expected_result: Vec<String>,
166        }
167
168        let cases = vec![
169            Case {
170                value: serde_json::json!({}),
171                expected_arguments: &[],
172                expected_result: vec![],
173            },
174            Case {
175                value: serde_json::json!({"param_a": "a"}),
176                expected_arguments: &[],
177                expected_result: vec!["param_a".to_string()],
178            },
179            Case {
180                value: json!({"param_a" : "a"}),
181                expected_arguments: &["param_a"],
182                expected_result: vec![],
183            },
184            Case {
185                value: serde_json::json!({"param_a" : {"field_a" : "a"}}),
186                expected_arguments: &[],
187                expected_result: vec!["param_a.field_a".to_string()],
188            },
189            Case {
190                value: serde_json::json!({"param_a" : {"field_a" : "a"}}),
191                expected_arguments: &["param_a.field_a"],
192                expected_result: vec![],
193            },
194            Case {
195                value: serde_json::json!({"param_a" : "a", "param_b" : "b"}),
196                expected_arguments: &["f1", "f2"],
197                expected_result: vec!["param_a".to_string(), "param_b".to_string()],
198            },
199        ];
200
201        for case in cases {
202            let result = list_unrecogninzed_fields(case.expected_arguments, &case.value);
203            assert_eq!(result, case.expected_result);
204        }
205    }
206
207    #[test]
208    fn serialize_error_data() {
209        assert_eq!(
210            serde_json::to_value(ParamValidationError::Custom(Custom {
211                message: "arg by name".to_string()
212            }))
213            .unwrap(),
214            json!({"type" : "custom", "message" : "arg by name"})
215        );
216
217        assert_eq!(
218            serde_json::to_value(ParamValidationError::InvalidParam(InvalidParam {
219                property: "param_a".to_string(),
220                message: "Not an integer".to_string()
221            }))
222            .unwrap(),
223            json!({"type" : "invalid_param", "property" : "param_a", "message" : "Not an integer"})
224        );
225
226        assert_eq!(
227            serde_json::to_value(ParamValidationError::Unrecognized(Unrecognized {
228                unrecognized: vec!["param_a".to_string()]
229            }),)
230            .unwrap(),
231            json!({"type" : "unrecognized", "unrecognized" : ["param_a"] })
232        )
233    }
234
235    #[test]
236    fn convert_invalid_params_to_error_data() {
237        let error = ParamValidationError::unrecognized(vec!["param_a".to_string()]);
238        let error_data: ErrorData<serde_json::Value> = error.into();
239
240        assert_eq!(error_data.code, -32602);
241        assert_eq!(
242            error_data
243                .data
244                .expect("is not None")
245                .get("unrecognized")
246                .expect("unrecognized field exists")[0],
247            "param_a"
248        );
249    }
250}