taplo_plugin_crates/
lib.rs

1use arc_swap::ArcSwap;
2use async_trait::async_trait;
3use itertools::Itertools;
4use once_cell::sync::OnceCell;
5use rayon::iter::ParallelIterator;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use serde_json::{json, Value};
9use smartstring::alias::CompactString;
10use std::{borrow::Cow, sync::Arc};
11use taplo::dom::{node::Key, Keys};
12use taplo_common::{
13    environment::Environment,
14    plugin::{CollectSchemasAction, Plugin},
15    schema::Schemas,
16};
17use url::Url;
18
19/// All non-yanked crate names and versions.
20static ALL_CRATES: OnceCell<Vec<(CompactString, CompactString)>> = OnceCell::new();
21
22#[derive(Debug, Default, Serialize, Deserialize, JsonSchema)]
23#[serde(deny_unknown_fields)]
24struct CratesSettings {
25    all_crates: bool,
26}
27
28#[derive(Default)]
29pub struct CratesPlugin {
30    index: OnceCell<crates_index::Index>,
31    settings: ArcSwap<CratesSettings>,
32}
33
34#[async_trait(?Send)]
35impl<E: Environment> Plugin<E> for CratesPlugin {
36    fn name(&self) -> Cow<'static, str> {
37        "crates".into()
38    }
39
40    fn settings(&self, value: Value) {
41        match serde_json::from_value(value) {
42            Ok(s) => {
43                self.settings.store(Arc::new(s));
44            }
45            Err(err) => {
46                tracing::error!(error = %err, "invalid plugin settings");
47            }
48        };
49    }
50
51    async fn possible_schemas(
52        &self,
53        schemas: &Schemas<E>,
54        _root_url: &Url,
55        schema: &Value,
56        root_path: &Keys,
57        relative_path: &Keys,
58        child_schemas: &mut Vec<(Keys, Keys, Arc<Value>)>,
59    ) -> CollectSchemasAction {
60        let index = match self
61            .index
62            .get_or_try_init(crates_index::Index::new_cargo_default)
63        {
64            Ok(index) => index,
65            Err(err) => {
66                tracing::warn!(error = %err, "failed to retrieve crates index");
67                return CollectSchemasAction::Continue;
68            }
69        };
70
71        if self.settings.load().all_crates
72            && schema["x-taplo"]["crates"]["schemas"] == "dependencies"
73        {
74            if ALL_CRATES.get().is_none() {
75                ALL_CRATES.set(
76                        schemas
77                            .env()
78                            .spawn_blocking(|| {
79                                rayon::ThreadPoolBuilder::new()
80                                .build()
81                                .unwrap()
82                                .install(move || {
83                                    let index = match crates_index::Index::new_cargo_default() {
84                                        Ok(idx) => idx,
85                                        Err(err) => {
86                                            tracing::warn!(error = %err, "failed to retrieve crates index");
87                                            return Vec::new();
88                                        }
89                                    };
90                                    index
91                                        .crates_parallel()
92                                        .filter_map(Result::ok)
93                                        .filter_map(|c| {
94                                            c.versions().iter().filter(|v| !v.is_yanked()).last().map(|v| {
95                                                (
96                                                    CompactString::from(c.name()),
97                                                    CompactString::from(v.version()),
98                                                )
99                                            })
100                                        })
101                                        .collect()
102                                })
103                            })
104                            .await
105                    ).ok();
106            }
107
108            child_schemas.extend(ALL_CRATES.get().unwrap().iter().map(|(name, version)| {
109                (
110                    root_path.clone(),
111                    relative_path.join(Key::from(&**name)),
112                    Arc::new(json!({
113                        "required": ["version"],
114                        "properties": {
115                            "version": {
116                                "type": "string",
117                                "default": version
118                            }
119                        }
120                    })),
121                )
122            }));
123        } else if schema["x-taplo"]["crates"]["schemas"] == "version" {
124            // This will not work with the "version" crate, hopefully it's not a huge issue.
125            let crate_name = match root_path
126                .iter()
127                .rev()
128                .find(|k| !k.is_index() && *k != "version")
129                .and_then(|k| k.as_key())
130            {
131                Some(crate_name) => crate_name,
132                None => return CollectSchemasAction::Continue,
133            };
134
135            let c = match index.crate_(crate_name.value()) {
136                Some(c) => c,
137                None => return CollectSchemasAction::Continue,
138            };
139
140            let highest_version = c
141                .highest_stable_version()
142                .unwrap_or_else(|| c.highest_version());
143
144            child_schemas.push((
145                root_path.clone(),
146                relative_path.clone(),
147                Arc::new(json!({
148                    "type": "string",
149                    "enum": c.versions().iter().rev().filter(|v| !v.is_yanked()).map(|v| v.version()).collect::<Vec<_>>(),
150                    "default": highest_version.version()
151                })),
152            ));
153        } else if schema["x-taplo"]["crates"]["schemas"] == "feature" {
154            let crate_name = match root_path
155                .iter()
156                .rev()
157                .find(|k| !k.is_index() && *k != "features")
158                .and_then(|k| k.as_key())
159            {
160                Some(crate_name) => crate_name,
161                None => return CollectSchemasAction::Continue,
162            };
163
164            let c = match index.crate_(crate_name.value()) {
165                Some(c) => c,
166                None => return CollectSchemasAction::Continue,
167            };
168
169            // FIXME: currently there is no way to know the version of the crate,
170            // so we list features from all versions.
171            let c_feature_schemas = c
172                .versions()
173                .iter()
174                .filter(|v| !v.is_yanked())
175                .flat_map(|v| v.features().iter())
176                .filter(|(f, _)| *f != "default")
177                .unique_by(|f| f.0)
178                .map(|(f, enables)| {
179                    let desc = if enables.is_empty() {
180                        String::from("This feature does not enable additional features.")
181                    } else {
182                        let mut s = String::from("Enables additional features:\n\n");
183
184                        for enables in enables {
185                            s += "- `";
186                            s += enables;
187                            s += "`\n"
188                        }
189
190                        s
191                    };
192
193                    Arc::new(json!({
194                        "type": "string",
195                        "enum": [f],
196                        "description": desc
197                    }))
198                });
199
200            child_schemas.extend(
201                c_feature_schemas.map(|schema| (root_path.clone(), relative_path.clone(), schema)),
202            );
203        }
204
205        CollectSchemasAction::Continue
206    }
207}