Skip to main content

gritpack_searchlib/
models.rs

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) {}