Skip to main content

quilt_rs/workflow/
error.rs

1//! Errors produced by the workflow gate and the workflows-config model.
2
3use std::fmt;
4use std::ops::Deref;
5
6use thiserror::Error;
7
8/// The only `$schema` meta-schema quilt3 accepts (its `SUPPORTED_META_SCHEMAS`
9/// maps exactly this URI to `Draft7Validator`).
10pub(super) const SUPPORTED_META_SCHEMA: &str = "http://json-schema.org/draft-07/schema#";
11
12/// Which schema in a workflow a configuration problem refers to.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum SchemaKind {
15    Metadata,
16    Entries,
17}
18
19impl fmt::Display for SchemaKind {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            SchemaKind::Metadata => f.write_str("metadata_schema"),
23            SchemaKind::Entries => f.write_str("entries_schema"),
24        }
25    }
26}
27
28/// A single reason a candidate package fails its workflow gate. Several may
29/// apply to one package; they are reported together in
30/// [`WorkflowValidationError::Rejected`].
31#[derive(Debug, Clone, PartialEq, Eq, Error)]
32pub enum RuleViolation {
33    #[error("a workflow is required by this bucket, but none was selected")]
34    WorkflowRequired,
35
36    #[error("a commit message is required by this workflow, but none was provided")]
37    MessageRequired,
38
39    #[error("package name {name:?} does not match the required handle_pattern {pattern:?}")]
40    HandleMismatch { name: String, pattern: String },
41
42    #[error("package metadata does not satisfy the workflow's metadata_schema: {0}")]
43    MetadataInvalid(String),
44
45    #[error("package entries do not satisfy the workflow's entries_schema: {0}")]
46    EntriesInvalid(String),
47}
48
49/// The reasons a candidate package failed its workflow gate — always at least
50/// one. The inner list is private, so a `Violations` can only be built non-empty
51/// (via [`Violations::from_nonempty`] or `From<RuleViolation>`); read access is
52/// through the slice `Deref` (`.iter()`, `.contains()`, `&v[..]`), and the
53/// multi-line rendering lives here as `Display`.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct Violations(Vec<RuleViolation>);
56
57impl Violations {
58    /// Build from a list, or `None` when it is empty — a rejection must carry a
59    /// reason, so an empty `Violations` is unrepresentable.
60    #[must_use]
61    pub fn from_nonempty(list: Vec<RuleViolation>) -> Option<Self> {
62        (!list.is_empty()).then_some(Self(list))
63    }
64}
65
66impl Deref for Violations {
67    type Target = [RuleViolation];
68
69    fn deref(&self) -> &Self::Target {
70        &self.0
71    }
72}
73
74impl fmt::Display for Violations {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        for violation in self.iter() {
77            write!(f, "\n  - {violation}")?;
78        }
79        Ok(())
80    }
81}
82
83impl From<RuleViolation> for Violations {
84    fn from(violation: RuleViolation) -> Self {
85        Violations(vec![violation])
86    }
87}
88
89/// The outcome of running the gate against a candidate package.
90///
91/// A [`WorkflowValidationError::Rejected`] means the package is well-formed
92/// but breaks one or more rules — the caller should surface the violations to
93/// the user. The other variants mean the *gate itself* is misconfigured (a
94/// schema is not valid Draft-7, uses `$ref`, or `handle_pattern` is not a
95/// valid regex) and are hard errors distinct from a rule failure.
96#[derive(Debug, Error)]
97pub enum WorkflowValidationError {
98    #[error("workflow {kind} is not a valid Draft-7 JSON Schema: {reason}")]
99    InvalidSchema { kind: SchemaKind, reason: String },
100
101    #[error("workflow {kind} uses `$ref`, which is not supported")]
102    UnsupportedRef { kind: SchemaKind },
103
104    #[error(
105        "workflow {kind} declares `$schema`: {value}, which is not supported \
106         (only the Draft-7 meta-schema {SUPPORTED_META_SCHEMA:?} is supported)"
107    )]
108    UnsupportedMetaSchema { kind: SchemaKind, value: String },
109
110    #[error("workflow handle_pattern {pattern:?} is not a valid regular expression: {reason}")]
111    InvalidHandlePattern { pattern: String, reason: String },
112
113    #[error("package does not satisfy the workflow:{0}")]
114    Rejected(Violations),
115}
116
117/// Errors from parsing / validating a workflows config, or resolving the
118/// declared (unfetched) schema URLs within it.
119///
120/// The variants mirror the `quilt_rs::RemoteCatalogError` variants they map
121/// onto, so a config error surfaced through quilt-rs keeps its exact `Display`.
122#[derive(Debug, Error)]
123pub enum ConfigError {
124    #[error("Workflow error: {0}")]
125    Workflow(String),
126
127    /// The `.quilt/workflows/config.yml` is malformed — it violates the vendored
128    /// quilt3 config schema, or its YAML could not be converted for validation.
129    #[error("Invalid workflows config: {0}")]
130    InvalidWorkflowsConfig(String),
131
132    #[error(transparent)]
133    Uri(#[from] quilt_uri::UriError),
134}