1use serde::Deserialize;
37
38use crate::parse;
39use crate::registry::Snippet;
40
41#[derive(Debug, thiserror::Error)]
44pub enum LoadError {
45 #[error("JSON parse: {0}")]
46 Json(#[from] serde_json::Error),
47 #[error("snippet body parse for `{name}`: {error}")]
48 Body {
49 name: String,
50 #[source]
51 error: parse::ParseError,
52 },
53}
54
55pub fn load_pack(json: &serde_json::Value) -> Result<Vec<Snippet>, LoadError> {
60 let raw: std::collections::BTreeMap<String, RawSnippet> = serde_json::from_value(json.clone())?;
61 let mut out: Vec<Snippet> = Vec::new();
62 for (name, r) in raw {
63 let prefixes = match r.prefix {
64 StringOrArray::String(s) => vec![s],
65 StringOrArray::Array(v) => v,
66 };
67 let body_str = match r.body {
68 StringOrArray::String(s) => s,
69 StringOrArray::Array(v) => v.join("\n"),
70 };
71 let body = parse::parse(&body_str).map_err(|error| LoadError::Body {
72 name: name.clone(),
73 error,
74 })?;
75 out.push(Snippet {
76 name,
77 prefixes,
78 body,
79 description: r.description,
80 scope: r.scope.unwrap_or_default(),
81 });
82 }
83 Ok(out)
84}
85
86pub fn load_pack_from_str(json: &str) -> Result<Vec<Snippet>, LoadError> {
89 let v: serde_json::Value = serde_json::from_str(json)?;
90 load_pack(&v)
91}
92
93#[derive(Debug, Deserialize)]
94struct RawSnippet {
95 prefix: StringOrArray,
96 body: StringOrArray,
97 #[serde(default)]
98 description: Option<String>,
99 #[serde(default)]
100 scope: Option<String>,
101}
102
103#[derive(Debug, Deserialize)]
104#[serde(untagged)]
105enum StringOrArray {
106 String(String),
107 Array(Vec<String>),
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn parses_simple_string_prefix_and_body() {
116 let json = r#"{
117 "for loop": {
118 "prefix": "for",
119 "body": "for ${1:i} in ${2:iter} {}",
120 "description": "for-in loop"
121 }
122 }"#;
123 let snips = load_pack_from_str(json).unwrap();
124 assert_eq!(snips.len(), 1);
125 let s = &snips[0];
126 assert_eq!(s.name, "for loop");
127 assert_eq!(s.prefixes, vec!["for"]);
128 assert_eq!(s.description.as_deref(), Some("for-in loop"));
129 }
130
131 #[test]
132 fn parses_array_prefix_and_body() {
133 let json = r#"{
134 "impl Display": {
135 "prefix": ["impl_display", "displ"],
136 "body": [
137 "impl Display for ${1:Ty} {",
138 "\tfn fmt(&self, f: &mut Formatter) -> Result {",
139 "\t\twrite!(f, \"${2}\")",
140 "\t}",
141 "}"
142 ]
143 }
144 }"#;
145 let snips = load_pack_from_str(json).unwrap();
146 let s = &snips[0];
147 assert_eq!(s.prefixes, vec!["impl_display", "displ"]);
148 assert!(!s.body.is_empty());
151 }
152
153 #[test]
154 fn unknown_top_level_fields_are_ignored() {
155 let json = r#"{
156 "x": {
157 "prefix": "x",
158 "body": "x",
159 "future_field": 42
160 }
161 }"#;
162 let snips = load_pack_from_str(json).unwrap();
163 assert_eq!(snips.len(), 1);
164 }
165
166 #[test]
167 fn missing_required_field_errors() {
168 let json = r#"{ "broken": { "prefix": "br" } }"#;
169 let result = load_pack_from_str(json);
170 assert!(matches!(result, Err(LoadError::Json(_))));
171 }
172
173 #[test]
174 fn scope_field_is_carried() {
175 let json = r#"{
176 "for": {
177 "prefix": "for",
178 "body": "for $1",
179 "scope": "source.rust,source.markdown.injection.rust"
180 }
181 }"#;
182 let snips = load_pack_from_str(json).unwrap();
183 assert_eq!(snips[0].scope, "source.rust,source.markdown.injection.rust");
184 }
185
186 #[test]
187 fn empty_pack_loads_to_empty_vec() {
188 let snips = load_pack_from_str("{}").unwrap();
189 assert!(snips.is_empty());
190 }
191
192 #[test]
193 fn friendly_snippets_for_loop_shape_round_trips() {
194 let json = r#"{
196 "For Range Loop": {
197 "prefix": ["for", "for-range"],
198 "body": [
199 "for ${1:i} in ${2:iter} {",
200 "\t$0",
201 "}"
202 ],
203 "description": "Iterate over a range using a for loop."
204 }
205 }"#;
206 let snips = load_pack_from_str(json).unwrap();
207 let s = &snips[0];
208 assert_eq!(s.prefixes.len(), 2);
209 let r = crate::render::render(&s.body, &crate::variables::VariableContext::default());
211 assert_eq!(r.text, "for i in iter {\n\t\n}");
212 assert_eq!(r.tabstops.len(), 3);
213 }
214}