Unnamed repository; edit this file 'description' to name the repository.
LSP Client: Accept floats with trailing zeros as valid JSONRPC IDs (#12376)
| -rw-r--r-- | helix-lsp/src/jsonrpc.rs | 45 |
1 files changed, 44 insertions, 1 deletions
diff --git a/helix-lsp/src/jsonrpc.rs b/helix-lsp/src/jsonrpc.rs index 9ff57cde..0a5b2b4c 100644 --- a/helix-lsp/src/jsonrpc.rs +++ b/helix-lsp/src/jsonrpc.rs @@ -104,10 +104,37 @@ impl std::error::Error for Error {} #[serde(untagged)] pub enum Id { Null, - Num(u64), + Num(#[serde(deserialize_with = "deserialize_jsonrpc_id_num")] u64), Str(String), } +fn deserialize_jsonrpc_id_num<'de, D>(deserializer: D) -> Result<u64, D::Error> +where + D: serde::Deserializer<'de>, +{ + let num = serde_json::Number::deserialize(deserializer)?; + + if let Some(val) = num.as_u64() { + return Ok(val); + }; + + // Accept floats as long as they represent positive whole numbers. + // The JSONRPC spec says "Numbers SHOULD NOT contain fractional parts" so we should try to + // accept them if possible. The JavaScript type system lumps integers and floats together so + // some languages may serialize integer IDs as floats with a zeroed fractional part. + // See <https://github.com/helix-editor/helix/issues/12367>. + if let Some(val) = num + .as_f64() + .filter(|f| f.is_sign_positive() && f.fract() == 0.0) + { + return Ok(val as u64); + } + + Err(de::Error::custom( + "number must be integer or float representing a whole number in valid u64 range", + )) +} + impl std::fmt::Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -376,6 +403,22 @@ fn serialize_skip_none_params() { } #[test] +fn id_deserialize() { + use serde_json; + + let id = r#"8"#; + let deserialized: Id = serde_json::from_str(id).unwrap(); + assert_eq!(deserialized, Id::Num(8)); + + let id = r#"4.0"#; + let deserialized: Id = serde_json::from_str(id).unwrap(); + assert_eq!(deserialized, Id::Num(4)); + + let id = r#"0.01"#; + assert!(serde_json::from_str::<Id>(id).is_err()); +} + +#[test] fn success_output_deserialize() { use serde_json; |