1use std::collections::BTreeMap;
2
3use reqwest::Url;
4use serde::{Deserialize, Serialize};
5
6pub(crate) const GRITPACK_MANIFEST_FILE: &str = "gritpack.toml";
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub(crate) struct ReleaseResponse {
10 pub(crate) uuid: String,
11 pub(crate) name: String,
12 pub(crate) version: String,
13 pub(crate) dialect: Option<String>,
14 pub(crate) encoding: Option<String>,
15 pub(crate) target: Option<String>,
16 pub(crate) source_root: Option<String>,
17 pub(crate) dependencies: Option<BTreeMap<String, ReleaseDependencySpec>>,
18 pub(crate) environment: Option<ReleaseEnvironmentResponse>,
19 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
20 pub(crate) stage: BTreeMap<String, StageSpec>,
21 #[serde(default, skip_serializing)]
22 pub(crate) handoff: Option<ReleaseHandoffResponse>,
23 pub(crate) advisory: Option<ReleaseAdvisoryResponse>,
24 #[serde(default)]
25 pub(crate) artifacts: ReleaseArtifactsResponse,
26 pub(crate) checksum: Option<String>,
27 pub(crate) file_id: Option<String>,
28 pub(crate) presigned_url: Option<String>,
29 #[serde(default)]
30 pub(crate) trust: Option<ReleaseTrustSummary>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub(crate) struct ReleaseTrustSummary {
35 pub(crate) artifact_profile: Option<String>,
36 pub(crate) trust_tier: Option<String>,
37 pub(crate) content_class: Option<String>,
38 pub(crate) publisher_signature: Option<ReleaseTrustStatus>,
39 pub(crate) hub_attestation: Option<ReleaseTrustStatus>,
40 pub(crate) artifact_digest_algo: Option<String>,
41 pub(crate) artifact_digest: Option<String>,
42 pub(crate) payload_digest_algo: Option<String>,
43 pub(crate) payload_digest: Option<String>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub(crate) struct ReleaseTrustStatus {
48 pub(crate) status: Option<String>,
49 pub(crate) issuer: Option<String>,
50 pub(crate) issuer_key_id: Option<String>,
51 pub(crate) policy: Option<String>,
52}
53
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
55pub(crate) struct ReleaseEnvironmentResponse {
56 pub(crate) layout: Option<String>,
57 pub(crate) entry: Option<String>,
58}
59
60#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
61pub(crate) struct StageSpec {
62 #[serde(default)]
63 pub(crate) command: Vec<String>,
64 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
65 pub(crate) env: BTreeMap<String, String>,
66}
67
68#[derive(Debug, Clone, Default, Serialize, Deserialize)]
69pub(crate) struct ReleaseHandoffResponse {
70 #[serde(default)]
71 pub(crate) build: Vec<String>,
72 #[serde(default)]
73 pub(crate) test: Vec<String>,
74 #[serde(default)]
75 pub(crate) run: Vec<String>,
76}
77
78impl ReleaseHandoffResponse {
79 pub(crate) fn stage_named(&self, name: &str) -> Option<StageSpec> {
80 let command = match name {
81 "build" => &self.build,
82 "test" => &self.test,
83 "run" => &self.run,
84 _ => return None,
85 };
86
87 if command.is_empty() {
88 return None;
89 }
90
91 Some(StageSpec {
92 command: command.clone(),
93 env: BTreeMap::new(),
94 })
95 }
96
97 pub(crate) fn merge_into_stage_map(&self, stages: &mut BTreeMap<String, StageSpec>) {
98 for stage_name in ["build", "test", "run"] {
99 if let Some(stage) = self.stage_named(stage_name) {
100 stages.entry(stage_name.to_string()).or_insert(stage);
101 }
102 }
103 }
104}
105
106impl ReleaseResponse {
107 pub(crate) fn normalized_stages(&self) -> BTreeMap<String, StageSpec> {
108 let mut stages = self.stage.clone();
109 if let Some(handoff) = self.handoff.as_ref() {
110 handoff.merge_into_stage_map(&mut stages);
111 }
112 stages
113 }
114
115 pub(crate) fn content_class(&self) -> &str {
116 self.trust
117 .as_ref()
118 .and_then(|trust| trust.content_class.as_deref())
119 .unwrap_or_else(|| {
120 if self.artifacts.executables.is_empty() && self.artifacts.libraries.is_empty() {
121 "source_only"
122 } else {
123 "contains_binaries"
124 }
125 })
126 }
127
128 pub(crate) fn trust_tier(&self) -> &str {
129 if let Some(tier) = self
130 .trust
131 .as_ref()
132 .and_then(|trust| trust.trust_tier.as_deref())
133 {
134 return tier;
135 }
136
137 match self
138 .trust
139 .as_ref()
140 .and_then(|trust| trust.publisher_signature.as_ref())
141 .and_then(|status| status.status.as_deref())
142 {
143 Some("verified") => "full",
144 Some("invalid") => "blocked",
145 _ => "low",
146 }
147 }
148
149 pub(crate) fn has_invalid_signature(&self) -> bool {
150 matches!(
151 self.trust
152 .as_ref()
153 .and_then(|trust| trust.publisher_signature.as_ref())
154 .and_then(|status| status.status.as_deref()),
155 Some("invalid")
156 )
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub(crate) struct ReleaseAdvisoryResponse {
162 #[serde(default)]
163 pub(crate) deprecated: bool,
164 pub(crate) message: Option<String>,
165 pub(crate) replacement: Option<String>,
166 #[serde(default)]
167 pub(crate) compatibility: Vec<ReleaseCompatibilityAdvisoryResponse>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub(crate) struct ReleaseCompatibilityAdvisoryResponse {
172 pub(crate) severity: ReleaseCompatibilitySeverity,
173 pub(crate) message: String,
174 pub(crate) package: Option<String>,
175 pub(crate) version: Option<String>,
176}
177
178#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub(crate) enum ReleaseCompatibilitySeverity {
181 Warning,
182 Blocker,
183}
184
185#[derive(Debug, Clone, Default, Serialize, Deserialize)]
186pub(crate) struct ReleaseArtifactsResponse {
187 #[serde(default)]
188 pub(crate) libraries: Vec<ReleaseLibraryArtifactResponse>,
189 #[serde(default)]
190 pub(crate) executables: Vec<ExecutableArtifactResponse>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub(crate) struct ReleaseLibraryArtifactResponse {
195 pub(crate) name: String,
196 pub(crate) target: String,
197 pub(crate) files: Vec<String>,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub(crate) struct ExecutableArtifactResponse {
202 pub(crate) name: String,
203 pub(crate) path: String,
204}
205
206#[allow(dead_code)]
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub(crate) struct ReleaseDependencySpec {
209 pub(crate) version: Option<String>,
210 pub(crate) dialect: Option<String>,
211 pub(crate) target: Option<String>,
212}
213
214#[derive(Debug, Deserialize)]
215pub(crate) struct PublishResponse {
216 pub(crate) id: String,
217 pub(crate) file_id: String,
218 pub(crate) presigned_url: String,
219}
220
221#[derive(Debug, Clone, Serialize)]
222pub(crate) struct RegisterPublisherKeyRequest {
223 pub(crate) publisher_kind: String,
224 pub(crate) publisher_id: String,
225 pub(crate) key_id: String,
226 pub(crate) public_key: String,
227 pub(crate) public_key_format: String,
228 #[serde(default)]
229 pub(crate) allowed_scopes: Vec<String>,
230 #[serde(default)]
231 pub(crate) allowed_packages: Vec<String>,
232 pub(crate) activation_mode: Option<String>,
233}
234
235#[derive(Debug, Clone, Deserialize)]
236pub(crate) struct PublisherKeyRecordResponse {
237 pub(crate) publisher_kind: String,
238 pub(crate) publisher_id: String,
239 pub(crate) key_id: String,
240 pub(crate) status: String,
241 pub(crate) public_key: String,
242 pub(crate) public_key_format: String,
243 #[serde(default)]
244 pub(crate) allowed_scopes: Vec<String>,
245 #[serde(default)]
246 pub(crate) allowed_packages: Vec<String>,
247 pub(crate) created_at: String,
248 pub(crate) activates_at: Option<String>,
249 pub(crate) expires_at: Option<String>,
250 pub(crate) revoked_at: Option<String>,
251}
252
253#[derive(Debug, Serialize, Deserialize)]
254pub(crate) struct PackageManifestFile {
255 pub(crate) package: LocalPackageSection,
256 #[serde(default)]
257 pub(crate) source: LocalSourceSection,
258 #[serde(default, rename = "lib")]
259 pub(crate) libraries: Vec<ReleaseLibraryArtifactResponse>,
260 #[serde(default, rename = "bin")]
261 pub(crate) executables: Vec<ExecutableArtifactResponse>,
262 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
263 pub(crate) stage: BTreeMap<String, StageSpec>,
264 #[serde(default, skip_serializing)]
265 pub(crate) handoff: Option<ReleaseHandoffResponse>,
266 pub(crate) dependencies: Option<BTreeMap<String, LocalDependencySpec>>,
267}
268
269impl PackageManifestFile {
270 pub(crate) fn normalize_stages(&mut self) {
271 if let Some(handoff) = self.handoff.as_ref() {
272 handoff.merge_into_stage_map(&mut self.stage);
273 }
274 self.handoff = None;
275 }
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub(crate) struct LocalPackageSection {
280 pub(crate) name: String,
281 pub(crate) version: String,
282 pub(crate) dialect: String,
283 pub(crate) target: Option<String>,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub(crate) struct LocalSourceSection {
288 #[serde(default = "default_source_root")]
289 pub(crate) root: String,
290 #[serde(default)]
291 pub(crate) include: Vec<String>,
292 #[serde(default)]
293 pub(crate) include_paths: Vec<String>,
294 #[serde(default)]
295 pub(crate) library_paths: Vec<String>,
296 #[serde(default)]
297 pub(crate) tool_paths: Vec<String>,
298}
299
300impl Default for LocalSourceSection {
301 fn default() -> Self {
302 Self {
303 root: default_source_root(),
304 include: Vec::new(),
305 include_paths: Vec::new(),
306 library_paths: Vec::new(),
307 tool_paths: Vec::new(),
308 }
309 }
310}
311
312fn default_source_root() -> String {
313 ".".to_string()
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, Default)]
317pub(crate) struct LocalDependencySpec {
318 pub(crate) version: Option<String>,
319 pub(crate) dialect: Option<String>,
320 pub(crate) target: Option<String>,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize, Default)]
324pub(crate) struct PackagesState {
325 pub(crate) version: u32,
326 #[serde(default)]
327 pub(crate) installs: Vec<InstalledPackage>,
328}
329
330#[derive(Debug, Serialize, Deserialize, Default)]
331pub(crate) struct ToolState {
332 pub(crate) version: u32,
333 #[serde(default)]
334 pub(crate) installs: Vec<InstalledTool>,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
338pub(crate) struct InstalledTool {
339 pub(crate) package_name: String,
340 pub(crate) version: String,
341 pub(crate) dialect: String,
342 pub(crate) target: Option<String>,
343 pub(crate) package_uuid: String,
344 pub(crate) install_path: String,
345 #[serde(default)]
346 pub(crate) commands: Vec<InstalledToolCommand>,
347 pub(crate) checksum: Option<String>,
348 pub(crate) file_id: Option<String>,
349 pub(crate) installed_at_unix: u64,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
353pub(crate) struct InstalledToolCommand {
354 pub(crate) name: String,
355 pub(crate) path: String,
356}
357
358#[derive(Debug, Clone)]
359pub(crate) struct ToolPackageRequest {
360 pub(crate) name: String,
361 pub(crate) version_constraint: Option<String>,
362}
363
364#[derive(Debug, Clone)]
365pub(crate) struct ToolUpgradeImpact {
366 pub(crate) request: String,
367 pub(crate) package_name: String,
368 pub(crate) current_version: String,
369 pub(crate) target_version: Option<String>,
370 pub(crate) target_platform: Option<String>,
371 pub(crate) command_names: Vec<String>,
372 pub(crate) package_count: usize,
373 pub(crate) max_depth: usize,
374 pub(crate) deepest_packages: Vec<String>,
375 pub(crate) findings: Vec<ImpactFinding>,
376 pub(crate) gaps: Vec<ImpactGap>,
377}
378
379#[derive(Debug, Clone, Serialize)]
380pub(crate) struct ImpactFinding {
381 pub(crate) severity: ImpactSeverity,
382 pub(crate) category: &'static str,
383 pub(crate) subject: String,
384 pub(crate) detail: String,
385}
386
387#[derive(Debug, Clone, Serialize)]
388pub(crate) struct ImpactGap {
389 pub(crate) category: &'static str,
390 pub(crate) detail: String,
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
394#[serde(rename_all = "snake_case")]
395pub(crate) enum ImpactSeverity {
396 Info,
397 Warning,
398 Risk,
399 Blocker,
400}
401
402#[derive(Debug, Clone)]
403pub(crate) struct ResolvedImpactNode {
404 pub(crate) name: String,
405 pub(crate) version: String,
406 pub(crate) dialect: String,
407 pub(crate) target: Option<String>,
408 pub(crate) depth: usize,
409}
410
411#[derive(Debug, Serialize)]
412pub(crate) struct ToolUpgradeImpactJsonReport {
413 pub(crate) schema: &'static str,
414 pub(crate) schema_version: u32,
415 pub(crate) generated_at_unix: u64,
416 pub(crate) reports: Vec<ToolUpgradeImpactJsonEntry>,
417}
418
419#[derive(Debug, Serialize)]
420pub(crate) struct ToolUpgradeImpactJsonEntry {
421 pub(crate) request: String,
422 pub(crate) summary: ToolUpgradeImpactSummary,
423 pub(crate) graph: ToolUpgradeImpactGraph,
424 pub(crate) findings: Vec<ImpactFinding>,
425 pub(crate) gaps: Vec<ImpactGap>,
426}
427
428#[derive(Debug, Serialize)]
429pub(crate) struct ToolUpgradeImpactSummary {
430 pub(crate) package: String,
431 pub(crate) status: &'static str,
432 pub(crate) current_version: String,
433 pub(crate) target_version: Option<String>,
434 pub(crate) target_platform: Option<String>,
435 pub(crate) commands: Vec<String>,
436}
437
438#[derive(Debug, Serialize)]
439pub(crate) struct ToolUpgradeImpactGraph {
440 pub(crate) package_count: usize,
441 pub(crate) max_depth: usize,
442 pub(crate) deepest_packages: Vec<String>,
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize)]
446pub(crate) struct InstalledPackage {
447 pub(crate) name: String,
448 pub(crate) version: String,
449 pub(crate) dialect: String,
450 pub(crate) target: Option<String>,
451 pub(crate) encoding: String,
452 pub(crate) package_uuid: String,
453 pub(crate) install_path: String,
454 pub(crate) source_root: Option<String>,
455 #[serde(default)]
456 pub(crate) direct: bool,
457 #[serde(default)]
458 pub(crate) dependencies: Vec<InstalledDependency>,
459 #[serde(default)]
460 pub(crate) libraries: Vec<ReleaseLibraryArtifactResponse>,
461 #[serde(default)]
462 pub(crate) executables: Vec<ExecutableArtifactResponse>,
463 pub(crate) checksum: Option<String>,
464 pub(crate) file_id: Option<String>,
465 pub(crate) installed_at_unix: u64,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub(crate) struct InstalledDependency {
470 pub(crate) name: String,
471 pub(crate) version: String,
472 pub(crate) dialect: Option<String>,
473 pub(crate) target: Option<String>,
474}
475
476#[derive(Debug, Serialize, Deserialize)]
477pub(crate) struct CachedReleaseMetadata {
478 pub(crate) package_uuid: String,
479 pub(crate) name: String,
480 pub(crate) version: String,
481 pub(crate) dialect: Option<String>,
482 pub(crate) target: Option<String>,
483 pub(crate) checksum: String,
484 pub(crate) file_id: Option<String>,
485 pub(crate) cached_at_unix: u64,
486}
487
488#[derive(Debug, Serialize)]
489pub(crate) struct EnvironmentManifest {
490 pub(crate) version: u32,
491 pub(crate) root: EnvironmentManifestRoot,
492 pub(crate) paths: EnvironmentManifestPaths,
493 pub(crate) packages: Vec<InstalledPackage>,
494 pub(crate) bins: Vec<EnvironmentManifestCommand>,
495 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
496 pub(crate) stage: BTreeMap<String, StageSpec>,
497 #[allow(dead_code)]
498 #[serde(skip_serializing)]
499 pub(crate) handoff: ReleaseHandoffResponse,
500}
501
502#[derive(Debug, Serialize)]
503pub(crate) struct EnvironmentManifestRoot {
504 pub(crate) name: String,
505 pub(crate) version: String,
506 pub(crate) dialect: String,
507 pub(crate) target: Option<String>,
508 pub(crate) package_uuid: String,
509 pub(crate) layout: Option<String>,
510 pub(crate) entry: Option<String>,
511}
512
513#[derive(Debug, Serialize)]
514pub(crate) struct EnvironmentManifestPaths {
515 pub(crate) root: String,
516 pub(crate) packages: String,
517 pub(crate) bin: String,
518 pub(crate) work: String,
519}
520
521#[derive(Debug, Serialize)]
522pub(crate) struct EnvironmentManifestCommand {
523 pub(crate) name: String,
524 pub(crate) path: String,
525 pub(crate) package: String,
526 pub(crate) version: String,
527}
528
529#[derive(Debug, thiserror::Error)]
530pub enum CliError {
531 #[error("http error: {0}")]
532 Http(#[from] reqwest::Error),
533 #[error("binary state error: {0}")]
534 BinaryState(#[from] bincode::Error),
535 #[error("io error: {0}")]
536 Io(#[from] std::io::Error),
537 #[error("json error: {0}")]
538 Json(#[from] serde_json::Error),
539 #[error("toml error: {0}")]
540 Toml(#[from] toml::de::Error),
541 #[error("toml error: {0}")]
542 TomlSerialize(#[from] toml::ser::Error),
543 #[error("{0}")]
544 Message(String),
545}
546
547#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
548pub(crate) enum ReconcileMode {
549 Sync,
550 Upgrade,
551}
552
553impl ReconcileMode {
554 pub(crate) fn completed_label(self) -> &'static str {
555 match self {
556 Self::Sync => "synced",
557 Self::Upgrade => "upgraded",
558 }
559 }
560
561 pub(crate) fn keeps_satisfying_existing_versions(self) -> bool {
562 matches!(self, Self::Sync)
563 }
564}
565
566#[allow(dead_code)]
567fn _keep_reqwest_url_imported_for_future_model_moves(_: &Url) {}