stackless_core/def/
error.rs1use crate::fault::{Fault, codes};
5
6#[derive(Debug, thiserror::Error)]
7pub enum DefError {
8 #[error("stackless.toml is not valid TOML: {message}")]
9 Syntax { message: String },
10
11 #[error("stackless.toml does not match the schema: {message}")]
12 Schema { message: String },
13
14 #[error("{kind} name {name:?} is not DNS-safe")]
15 NameInvalid { kind: &'static str, name: String },
16
17 #[error("the stack declares no services")]
18 NoServices,
19
20 #[error("unknown key {key:?} under {location}")]
21 UnknownKey {
22 location: String,
23 key: String,
24 known_substrates: Vec<String>,
25 },
26
27 #[error("`depends_on` under {location} is not part of the schema")]
28 DependsOnRejected { location: String },
29
30 #[error("substrate block {location} must be a table, found {found}")]
31 SubstrateBlockInvalid { location: String, found: String },
32
33 #[error("service {service:?} has no [services.{service}.{substrate}] config")]
34 SubstrateConfigMissing { service: String, substrate: String },
35
36 #[error("multiple services declare root_origin: {services:?}")]
37 RootOriginConflict { services: Vec<String> },
38
39 #[error("invalid interpolation reference {reference:?} in {location}: {detail}")]
40 ReferenceSyntax {
41 location: String,
42 reference: String,
43 detail: String,
44 },
45
46 #[error("{location} references undeclared {kind} {name:?}")]
47 UndeclaredReference {
48 location: String,
49 kind: &'static str,
50 name: String,
51 },
52
53 #[error("{location} uses secret {key:?} which is not in [secrets].required")]
54 SecretNotRequired { location: String, key: String },
55
56 #[error("[integrations.{integration}] is invalid: {detail}")]
57 IntegrationInvalid { integration: String, detail: String },
58
59 #[error("the wiring graph has a dependency cycle through: {nodes}")]
60 WiringCycle { nodes: String },
61
62 #[error("env under {location} must be a table of string values")]
63 EnvNotStrings { location: String },
64}
65
66impl Fault for DefError {
67 fn code(&self) -> &'static str {
68 match self {
69 Self::Syntax { .. } => codes::DEF_PARSE_SYNTAX,
70 Self::Schema { .. } => codes::DEF_PARSE_SCHEMA,
71 Self::NameInvalid { .. } => codes::DEF_NAME_INVALID,
72 Self::NoServices => codes::DEF_NO_SERVICES,
73 Self::UnknownKey { .. } => codes::DEF_UNKNOWN_KEY,
74 Self::DependsOnRejected { .. } => codes::DEF_DEPENDS_ON_REJECTED,
75 Self::SubstrateBlockInvalid { .. } => codes::DEF_SUBSTRATE_BLOCK_INVALID,
76 Self::SubstrateConfigMissing { .. } => codes::DEF_SUBSTRATE_CONFIG_MISSING,
77 Self::RootOriginConflict { .. } => codes::DEF_ROOT_ORIGIN_CONFLICT,
78 Self::ReferenceSyntax { .. } => codes::DEF_REFERENCE_SYNTAX,
79 Self::UndeclaredReference { .. } => codes::DEF_UNDECLARED_REFERENCE,
80 Self::SecretNotRequired { .. } => codes::DEF_SECRET_NOT_REQUIRED,
81 Self::IntegrationInvalid { .. } => codes::DEF_INTEGRATION_INVALID,
82 Self::WiringCycle { .. } => codes::DEF_WIRING_CYCLE,
83 Self::EnvNotStrings { .. } => codes::DEF_ENV_NOT_STRINGS,
84 }
85 }
86
87 fn remediation(&self) -> String {
88 match self {
89 Self::Syntax { .. } => "fix the TOML syntax at the location shown, then re-run".into(),
90 Self::Schema { .. } => {
91 "compare the failing key against the schema reference in ARCHITECTURE.md §1; \
92 remove or rename keys the schema does not define"
93 .into()
94 }
95 Self::NameInvalid { kind, .. } => format!(
96 "rename the {kind} to lowercase letters, digits, and hyphens, starting with a \
97 letter (it becomes hostnames and cloud service names)"
98 ),
99 Self::NoServices => {
100 "declare at least one [services.<name>] table in stackless.toml".into()
101 }
102 Self::UnknownKey {
103 key,
104 known_substrates,
105 ..
106 } => format!(
107 "{key:?} is neither a schema field nor a registered substrate; known substrates \
108 are {known_substrates:?} — fix the typo or remove the key"
109 ),
110 Self::DependsOnRejected { .. } => {
111 "express the dependency in wiring instead: reference the dependency from this \
112 service's env (e.g. CORS_ALLOWED_ORIGINS = \"${services.web.origin}\"); the \
113 dependency graph is derived from wiring, never declared separately"
114 .into()
115 }
116 Self::SubstrateBlockInvalid { location, .. } => {
117 format!("make {location} a TOML table, e.g. [{location}]")
118 }
119 Self::SubstrateConfigMissing { service, substrate } => format!(
120 "add a [services.{service}.{substrate}] block with the config that substrate \
121 requires, or bring the instance up on a substrate this service supports"
122 ),
123 Self::RootOriginConflict { services } => format!(
124 "keep root_origin = true on exactly one of {services:?} and remove it from the \
125 others"
126 ),
127 Self::ReferenceSyntax { .. } => {
128 "valid references are ${stack.name}, ${instance.name}, \
129 ${services.<name>.origin}, ${secrets.<KEY>}, and \
130 ${integrations.<name>.<output>}"
131 .into()
132 }
133 Self::UndeclaredReference { kind, name, .. } => format!(
134 "declare [{kind}s.{name}] in stackless.toml, or fix the reference to name a \
135 declared {kind}"
136 ),
137 Self::SecretNotRequired { key, .. } => format!(
138 "add {key:?} to [secrets].required so it is resolved and validated before \
139 anything provisions"
140 ),
141 Self::IntegrationInvalid { integration, .. } => format!(
142 "fix [integrations.{integration}]; declare provider and provider-specific \
143 config (see `stackless check` / SCHEMA.md)"
144 ),
145 Self::WiringCycle { .. } => {
146 "break the cycle: at least one of these references must be removed or replaced \
147 with one that does not require its target to start first"
148 .into()
149 }
150 Self::EnvNotStrings { location } => {
151 format!("every value under {location} must be a TOML string")
152 }
153 }
154 }
155}