Skip to main content

mcpls_core/config/
language_id.rs

1//! The LSP language identifier of a configured server.
2
3use std::borrow::Cow;
4
5use schemars::{JsonSchema, Schema, SchemaGenerator};
6use serde::{Deserialize, Serialize};
7
8use super::text_newtype::impl_text_newtype;
9
10/// A language identifier was empty or whitespace-only.
11#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
12#[error("language_id cannot be blank")]
13pub struct InvalidLanguageId;
14
15/// A non-empty LSP language identifier such as `"rust"` or `"typescript"`.
16///
17/// Deserializes from a TOML string and rejects an empty one at load time.
18///
19/// # Examples
20///
21/// ```
22/// use mcpls_core::config::LanguageId;
23///
24/// let id = LanguageId::new("rust").unwrap();
25/// assert_eq!(id, "rust");
26/// assert!(LanguageId::new("").is_err());
27/// ```
28#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
29#[serde(try_from = "String", into = "String")]
30pub struct LanguageId(Cow<'static, str>);
31
32impl LanguageId {
33    /// Language reported for a file whose extension has no mapping.
34    pub const PLAINTEXT: Self = Self::from_static("plaintext");
35}
36
37impl JsonSchema for LanguageId {
38    fn inline_schema() -> bool {
39        true
40    }
41
42    fn schema_name() -> Cow<'static, str> {
43        Cow::Borrowed("LanguageId")
44    }
45
46    fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
47        schemars::json_schema!({"type": "string"})
48    }
49}
50
51impl_text_newtype!(LanguageId, InvalidLanguageId, non_blank, "language_id");
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[derive(Debug, Serialize, Deserialize, PartialEq)]
58    struct Holder {
59        id: LanguageId,
60    }
61
62    #[test]
63    fn test_rejects_empty() {
64        assert_eq!(LanguageId::new(""), Err(InvalidLanguageId));
65        assert!("".parse::<LanguageId>().is_err());
66    }
67
68    #[test]
69    #[should_panic(expected = "language_id must be ASCII and not blank")]
70    fn test_from_static_panics_on_empty() {
71        let _ = LanguageId::from_static("");
72    }
73
74    #[test]
75    #[should_panic(expected = "language_id must be ASCII and not blank")]
76    fn test_from_static_panics_on_non_ascii_blank() {
77        let _ = LanguageId::from_static("\u{a0}");
78    }
79
80    #[test]
81    fn test_rejects_whitespace_only() {
82        assert_eq!(LanguageId::new("  \u{a0}"), Err(InvalidLanguageId));
83    }
84
85    #[test]
86    fn test_round_trip_and_comparisons() {
87        let holder: Holder = toml::from_str(r#"id = "python""#).unwrap();
88        assert_eq!(holder.id, "python");
89        assert_eq!(holder.id, *"python");
90        assert_eq!(holder.id.to_string(), "python");
91        assert_eq!(holder.id, LanguageId::from_static("python"));
92        let text = toml::to_string(&holder).unwrap();
93        assert_eq!(toml::from_str::<Holder>(&text).unwrap(), holder);
94    }
95
96    #[test]
97    fn test_schema_is_an_inline_string() {
98        let mut generator = SchemaGenerator::default();
99        let schema = LanguageId::json_schema(&mut generator);
100        assert_eq!(schema.as_value(), &serde_json::json!({"type": "string"}));
101    }
102
103    #[test]
104    fn test_hash_lookup_by_str() {
105        let mut map = std::collections::HashMap::new();
106        map.insert(LanguageId::from_static("go"), 1);
107        assert_eq!(map.get("go"), Some(&1));
108    }
109}