taplo_plugin_crates/
lib.rs1use 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
19static 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 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 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}