1use std::collections::{BTreeMap, HashMap};
9
10use serde::Serialize;
11use serde_json::Value;
12
13use crate::protocol::{
14 ClientCapabilities, MetaValidationError, ServerCapabilities, validate_extension_identifier,
15};
16
17#[derive(Debug, Clone, PartialEq)]
24pub struct ExtensionDeclaration {
25 identifier: String,
26 settings: Value,
27}
28
29impl ExtensionDeclaration {
30 pub fn empty(identifier: impl Into<String>) -> Result<Self, MetaValidationError> {
32 Self::new(identifier, serde_json::json!({}))
33 }
34
35 pub fn new(
37 identifier: impl Into<String>,
38 settings: impl Serialize,
39 ) -> Result<Self, MetaValidationError> {
40 let identifier = identifier.into();
41 validate_extension_identifier(&identifier)?;
42 let settings = serde_json::to_value(settings)
43 .map_err(|_| MetaValidationError::InvalidExtensionSettings(identifier.clone()))?;
44 if !settings.is_object() {
45 return Err(MetaValidationError::InvalidExtensionSettings(identifier));
46 }
47 Ok(Self {
48 identifier,
49 settings,
50 })
51 }
52
53 pub fn identifier(&self) -> &str {
55 &self.identifier
56 }
57
58 pub fn settings(&self) -> &Value {
60 &self.settings
61 }
62
63 pub(crate) fn into_parts(self) -> (String, Value) {
64 (self.identifier, self.settings)
65 }
66}
67
68#[derive(Debug, Clone, PartialEq)]
70pub struct NegotiatedExtension {
71 client_settings: Value,
72 server_settings: Value,
73}
74
75impl NegotiatedExtension {
76 pub fn client_settings(&self) -> &Value {
78 &self.client_settings
79 }
80
81 pub fn server_settings(&self) -> &Value {
83 &self.server_settings
84 }
85}
86
87#[derive(Debug, Clone, Default, PartialEq)]
93pub struct NegotiatedExtensions {
94 extensions: BTreeMap<String, NegotiatedExtension>,
95}
96
97impl NegotiatedExtensions {
98 pub fn from_capabilities(client: &ClientCapabilities, server: &ServerCapabilities) -> Self {
100 Self::from_maps(client.extensions.as_ref(), server.extensions.as_ref())
101 }
102
103 pub(crate) fn from_maps(
104 client: Option<&HashMap<String, Value>>,
105 server: Option<&HashMap<String, Value>>,
106 ) -> Self {
107 let mut extensions = BTreeMap::new();
108 let (Some(client), Some(server)) = (client, server) else {
109 return Self { extensions };
110 };
111
112 for (identifier, client_settings) in client {
113 let Some(server_settings) = server.get(identifier) else {
114 continue;
115 };
116 if validate_extension_identifier(identifier).is_err()
117 || !client_settings.is_object()
118 || !server_settings.is_object()
119 {
120 continue;
121 }
122 extensions.insert(
123 identifier.clone(),
124 NegotiatedExtension {
125 client_settings: client_settings.clone(),
126 server_settings: server_settings.clone(),
127 },
128 );
129 }
130 Self { extensions }
131 }
132
133 pub fn get(&self, identifier: &str) -> Option<&NegotiatedExtension> {
135 self.extensions.get(identifier)
136 }
137
138 pub fn contains(&self, identifier: &str) -> bool {
140 self.extensions.contains_key(identifier)
141 }
142
143 pub fn iter(&self) -> impl Iterator<Item = (&str, &NegotiatedExtension)> {
145 self.extensions
146 .iter()
147 .map(|(identifier, extension)| (identifier.as_str(), extension))
148 }
149
150 pub fn len(&self) -> usize {
152 self.extensions.len()
153 }
154
155 pub fn is_empty(&self) -> bool {
157 self.extensions.is_empty()
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn declaration_requires_vendor_prefix_and_object_settings() {
167 assert!(ExtensionDeclaration::empty("com.example/feature").is_ok());
168 assert!(matches!(
169 ExtensionDeclaration::empty("feature"),
170 Err(MetaValidationError::MissingExtensionPrefix(_))
171 ));
172 assert!(matches!(
173 ExtensionDeclaration::new("com.example/feature", true),
174 Err(MetaValidationError::InvalidExtensionSettings(_))
175 ));
176 }
177
178 #[test]
179 fn negotiation_is_the_exact_identifier_intersection() {
180 let client = ClientCapabilities {
181 extensions: Some(HashMap::from([
182 (
183 "com.example/shared".to_string(),
184 serde_json::json!({"client": true}),
185 ),
186 ("com.example/client-only".to_string(), serde_json::json!({})),
187 ])),
188 ..ClientCapabilities::default()
189 };
190 let server = ServerCapabilities {
191 extensions: Some(HashMap::from([
192 (
193 "com.example/shared".to_string(),
194 serde_json::json!({"server": true}),
195 ),
196 ("com.example/server-only".to_string(), serde_json::json!({})),
197 ])),
198 ..ServerCapabilities::default()
199 };
200
201 let negotiated = NegotiatedExtensions::from_capabilities(&client, &server);
202
203 assert_eq!(negotiated.len(), 1);
204 let shared = negotiated.get("com.example/shared").unwrap();
205 assert_eq!(shared.client_settings()["client"], true);
206 assert_eq!(shared.server_settings()["server"], true);
207 assert!(!negotiated.contains("com.example/client-only"));
208 assert!(!negotiated.contains("com.example/server-only"));
209 }
210}