1use serde::Deserialize;
2use serde::Serialize;
3
4#[cfg(feature = "async_runtime")]
5use crate::async_runtime::FutureExt;
6#[cfg(feature = "async_runtime")]
7use crate::async_runtime::LocalBoxFuture;
8
9use crate::configuration::ConfigKeyMap;
10use crate::configuration::ConfigKeyValue;
11use crate::configuration::ConfigurationDiagnostic;
12use crate::configuration::GlobalConfiguration;
13use crate::plugins::PluginInfo;
14
15use super::FileMatchingInfo;
16
17pub trait CancellationToken: Send + Sync + std::fmt::Debug {
18 fn is_cancelled(&self) -> bool;
19 #[cfg(feature = "async_runtime")]
20 fn wait_cancellation(&self) -> LocalBoxFuture<'static, ()>;
21}
22
23#[cfg(feature = "async_runtime")]
24impl CancellationToken for tokio_util::sync::CancellationToken {
25 fn is_cancelled(&self) -> bool {
26 self.is_cancelled()
27 }
28
29 fn wait_cancellation(&self) -> LocalBoxFuture<'static, ()> {
30 let token = self.clone();
31 async move { token.cancelled().await }.boxed_local()
32 }
33}
34
35#[derive(Debug)]
37pub struct NullCancellationToken;
38
39impl CancellationToken for NullCancellationToken {
40 fn is_cancelled(&self) -> bool {
41 false
42 }
43
44 #[cfg(feature = "async_runtime")]
45 fn wait_cancellation(&self) -> LocalBoxFuture<'static, ()> {
46 Box::pin(std::future::pending())
48 }
49}
50
51pub type FormatRange = Option<std::ops::Range<usize>>;
52
53#[derive(Debug, thiserror::Error)]
59#[error(transparent)]
60pub struct FormatError(Box<dyn std::error::Error + Send + Sync + 'static>);
61
62impl FormatError {
63 pub fn new(error: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>) -> Self {
66 FormatError(error.into())
67 }
68
69 pub fn downcast_ref<E: std::error::Error + 'static>(&self) -> Option<&E> {
72 self.0.downcast_ref::<E>()
73 }
74}
75
76pub fn error_to_string(err: &(dyn std::error::Error + 'static)) -> String {
80 const MAX_DEPTH: usize = 100;
83 let mut result = err.to_string();
84 let mut source = err.source();
85 for _ in 0..MAX_DEPTH {
86 let Some(err) = source else { break };
87 result.push_str(": ");
88 result.push_str(&err.to_string());
89 source = err.source();
90 }
91 result
92}
93
94macro_rules! impl_format_error_from {
95 ($($t:ty),* $(,)?) => {
96 $(
97 impl From<$t> for FormatError {
98 fn from(error: $t) -> Self {
99 FormatError(error.into())
100 }
101 }
102 )*
103 };
104}
105
106impl_format_error_from!(
107 String,
108 &str,
109 Box<dyn std::error::Error + Send + Sync + 'static>,
110 std::io::Error,
111 std::str::Utf8Error,
112 std::string::FromUtf8Error,
113 CriticalFormatError,
114);
115
116#[cfg(test)]
117mod tests {
118 use super::FormatError;
119
120 #[test]
121 fn should_convert_utf8_error_to_format_error() {
122 let bytes = [u8::MAX];
123 let utf8_error = std::str::from_utf8(&bytes).unwrap_err();
124 let format_error: FormatError = utf8_error.into();
125
126 assert!(format_error.downcast_ref::<std::str::Utf8Error>().is_some());
127 }
128}
129
130#[cfg(feature = "serde_json")]
131impl_format_error_from!(serde_json::Error);
132
133#[cfg(feature = "async_runtime")]
134impl_format_error_from!(tokio::task::JoinError, tokio::sync::oneshot::error::RecvError);
135
136#[derive(Debug, thiserror::Error)]
141#[error(transparent)]
142pub struct CriticalFormatError(pub FormatError);
143
144#[derive(Debug, Serialize, Deserialize)]
145#[serde(rename_all = "camelCase")]
146pub struct CheckConfigUpdatesMessage {
147 #[serde(default)]
149 pub old_version: Option<String>,
150 pub config: ConfigKeyMap,
151}
152
153#[cfg(feature = "process")]
154#[derive(Debug)]
155pub struct HostFormatRequest {
156 pub file_path: std::path::PathBuf,
157 pub file_bytes: Vec<u8>,
158 pub range: FormatRange,
160 pub override_config: ConfigKeyMap,
161 pub token: std::sync::Arc<dyn CancellationToken>,
162}
163
164#[cfg(feature = "wasm")]
165#[derive(Debug)]
166pub struct SyncHostFormatRequest<'a> {
167 pub file_path: &'a std::path::Path,
168 pub file_bytes: &'a [u8],
169 pub range: FormatRange,
171 pub override_config: &'a ConfigKeyMap,
172}
173
174pub type FormatResult = Result<Option<Vec<u8>>, FormatError>;
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct RawFormatConfig {
181 pub plugin: ConfigKeyMap,
182 pub global: GlobalConfiguration,
183}
184
185#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
187pub struct FormatConfigId(u32);
188
189impl std::fmt::Display for FormatConfigId {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "${}", self.0)
192 }
193}
194
195impl FormatConfigId {
196 pub fn from_raw(raw: u32) -> FormatConfigId {
197 FormatConfigId(raw)
198 }
199
200 pub fn uninitialized() -> FormatConfigId {
201 FormatConfigId(0)
202 }
203
204 pub fn as_raw(&self) -> u32 {
205 self.0
206 }
207}
208
209#[cfg(feature = "process")]
210pub struct FormatRequest<TConfiguration> {
211 pub file_path: std::path::PathBuf,
212 pub file_bytes: Vec<u8>,
213 pub config_id: FormatConfigId,
214 pub config: std::sync::Arc<TConfiguration>,
215 pub range: FormatRange,
217 pub token: std::sync::Arc<dyn CancellationToken>,
218}
219
220#[cfg(feature = "wasm")]
221pub struct SyncFormatRequest<'a, TConfiguration> {
222 pub file_path: &'a std::path::Path,
223 pub file_bytes: Vec<u8>,
224 pub config_id: FormatConfigId,
225 pub config: &'a TConfiguration,
226 pub range: FormatRange,
228 pub token: &'a dyn CancellationToken,
229}
230
231#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
232#[serde(untagged)]
233pub enum ConfigChangePathItem {
234 String(String),
236 Number(usize),
238}
239
240impl From<String> for ConfigChangePathItem {
241 fn from(value: String) -> Self {
242 Self::String(value)
243 }
244}
245
246impl From<usize> for ConfigChangePathItem {
247 fn from(value: usize) -> Self {
248 Self::Number(value)
249 }
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253#[serde(rename_all = "camelCase")]
254pub struct ConfigChange {
255 pub path: Vec<ConfigChangePathItem>,
257 #[serde(flatten)]
258 pub kind: ConfigChangeKind,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
262#[serde(tag = "kind", content = "value")]
263pub enum ConfigChangeKind {
264 Add(ConfigKeyValue),
266 Set(ConfigKeyValue),
268 Remove,
270}
271
272#[derive(Clone, Serialize)]
273#[serde(rename_all = "camelCase")]
274pub struct PluginResolveConfigurationResult<T>
275where
276 T: Clone + Serialize,
277{
278 pub file_matching: FileMatchingInfo,
280
281 pub diagnostics: Vec<ConfigurationDiagnostic>,
283
284 pub config: T,
287}
288
289#[cfg(feature = "process")]
291#[crate::async_runtime::async_trait(?Send)]
292pub trait AsyncPluginHandler: 'static {
293 type Configuration: Serialize + Clone + Send + Sync;
294
295 fn plugin_info(&self) -> PluginInfo;
297 fn license_text(&self) -> String;
299 async fn resolve_config(&self, config: ConfigKeyMap, global_config: GlobalConfiguration) -> PluginResolveConfigurationResult<Self::Configuration>;
301 async fn check_config_updates(&self, _message: CheckConfigUpdatesMessage) -> Result<Vec<ConfigChange>, FormatError> {
304 Ok(Vec::new())
305 }
306 async fn format(
308 &self,
309 request: FormatRequest<Self::Configuration>,
310 format_with_host: impl FnMut(HostFormatRequest) -> LocalBoxFuture<'static, FormatResult> + 'static,
311 ) -> FormatResult;
312}
313
314#[cfg(feature = "wasm")]
316pub trait SyncPluginHandler<TConfiguration: Clone + serde::Serialize> {
317 fn resolve_config(&mut self, config: ConfigKeyMap, global_config: &GlobalConfiguration) -> PluginResolveConfigurationResult<TConfiguration>;
319 fn plugin_info(&mut self) -> PluginInfo;
321 fn license_text(&mut self) -> String;
323 fn check_config_updates(&self, message: CheckConfigUpdatesMessage) -> Result<Vec<ConfigChange>, FormatError>;
326 fn format(&mut self, request: SyncFormatRequest<TConfiguration>, format_with_host: impl FnMut(SyncHostFormatRequest) -> FormatResult) -> FormatResult;
328}