1use crate::capability_slot::CapabilitySlot;
4use crate::revision::RevisionLifecycle;
5use thiserror::Error;
6
7use crate::ids::{BundleId, DeploymentId, MessagingEndpointId, RevisionId};
8use greentic_types::EnvId;
9
10#[derive(Debug, Error, PartialEq, Eq)]
11pub enum SpecError {
12 #[error("basis-points entries must sum to 10000, got {sum}")]
13 BasisPointsSum { sum: u64 },
14
15 #[error("basis-points entry exceeds 10000: {value}")]
16 BasisPointsEntryTooLarge { value: u32 },
17
18 #[error("revenue-policy version must be >= 1 (1-based monotonic)")]
19 RevenuePolicyVersionZero,
20
21 #[error("duplicate capability slot `{0}` in Environment.packs")]
22 DuplicateCapabilitySlot(CapabilitySlot),
23
24 #[error("revision lifecycle transition {from:?} → {to:?} is not permitted")]
25 InvalidLifecycleTransition {
26 from: RevisionLifecycle,
27 to: RevisionLifecycle,
28 },
29
30 #[error("schema discriminator mismatch: expected `{expected}`, got `{actual}`")]
31 SchemaMismatch {
32 expected: &'static str,
33 actual: String,
34 },
35
36 #[error("env_id mismatch in {context}: expected `{expected}`, got `{actual}`")]
37 EnvIdMismatch {
38 context: &'static str,
39 expected: EnvId,
40 actual: EnvId,
41 },
42
43 #[error("traffic split references unknown deployment `{0}`")]
44 UnknownDeployment(DeploymentId),
45
46 #[error("reference to unknown revision `{0}`")]
47 UnknownRevision(RevisionId),
48
49 #[error(
50 "split entry revision `{revision}` belongs to deployment `{actual_deployment}`, not split's `{expected_deployment}`"
51 )]
52 SplitRevisionWrongDeployment {
53 revision: RevisionId,
54 expected_deployment: DeploymentId,
55 actual_deployment: DeploymentId,
56 },
57
58 #[error(
59 "split entry revision `{revision}` belongs to bundle `{actual_bundle}`, not split's `{expected_bundle}`"
60 )]
61 SplitRevisionWrongBundle {
62 revision: RevisionId,
63 expected_bundle: BundleId,
64 actual_bundle: BundleId,
65 },
66
67 #[error(
68 "bundle `{deployment}` current_revision `{revision}` belongs to deployment `{actual_deployment}`"
69 )]
70 BundleRevisionWrongDeployment {
71 deployment: DeploymentId,
72 revision: RevisionId,
73 actual_deployment: DeploymentId,
74 },
75
76 #[error(
77 "bundle deployment `{deployment}` current_revision `{revision}` belongs to bundle `{actual_bundle}`, not deployment's `{expected_bundle}`"
78 )]
79 BundleRevisionWrongBundle {
80 deployment: DeploymentId,
81 revision: RevisionId,
82 expected_bundle: BundleId,
83 actual_bundle: BundleId,
84 },
85
86 #[error(
87 "traffic split for deployment `{deployment}` carries bundle `{split_bundle}`, but the BundleDeployment record holds bundle `{deployment_bundle}`"
88 )]
89 SplitDeploymentBundleMismatch {
90 deployment: DeploymentId,
91 split_bundle: BundleId,
92 deployment_bundle: BundleId,
93 },
94
95 #[error("{context} ref `{uri}` is scoped to env `{actual_env}`, expected `{expected_env}`")]
96 CrossEnvRef {
97 context: &'static str,
98 uri: String,
99 expected_env: EnvId,
100 actual_env: String,
101 },
102
103 #[error("duplicate messaging endpoint id `{0}` in Environment.messaging_endpoints")]
104 DuplicateMessagingEndpoint(MessagingEndpointId),
105
106 #[error(
107 "duplicate provider instance `{provider_type}` / `{provider_id}` in Environment.messaging_endpoints"
108 )]
109 DuplicateProviderInstance {
110 provider_type: String,
111 provider_id: String,
112 },
113
114 #[error(
115 "messaging endpoint `{endpoint}` links bundle `{bundle}` which is not deployed in this env"
116 )]
117 MessagingEndpointBundleNotLinked {
118 endpoint: MessagingEndpointId,
119 bundle: BundleId,
120 },
121
122 #[error(
123 "messaging endpoint `{endpoint}` welcome_flow references bundle `{bundle}` which is not in linked_bundles"
124 )]
125 WelcomeFlowBundleNotLinked {
126 endpoint: MessagingEndpointId,
127 bundle: BundleId,
128 },
129
130 #[error("messaging endpoint provider_id is empty")]
131 EmptyMessagingProviderId,
132
133 #[error("messaging endpoint provider_type is empty")]
134 EmptyMessagingProviderType,
135
136 #[error("messaging endpoint welcome_flow.flow_id is empty")]
137 EmptyWelcomeFlowId,
138
139 #[error(
140 "duplicate extension binding for path `{path}` / instance `{instance_id:?}` in Environment.extensions"
141 )]
142 DuplicateExtension {
143 path: String,
144 instance_id: Option<String>,
145 },
146
147 #[error("extension binding `{path}` has an invalid instance id: {reason}")]
148 InvalidExtensionInstanceId { path: String, reason: String },
149
150 #[error("bundle config_overrides carries {count} packs, exceeds cap of {max}")]
151 ConfigOverridesTooManyPacks { count: usize, max: usize },
152
153 #[error(
154 "bundle config_overrides for pack `{pack_id}` carries {count} keys, exceeds cap of {max}"
155 )]
156 ConfigOverridesTooManyKeysForPack {
157 pack_id: String,
158 count: usize,
159 max: usize,
160 },
161
162 #[error("bundle config_overrides serialized size is {bytes} bytes, exceeds cap of {max}")]
163 ConfigOverridesTooLarge { bytes: usize, max: usize },
164
165 #[error("bundle config_overrides has an empty pack id key")]
166 ConfigOverrideEmptyPackId,
167
168 #[error("bundle config_overrides for pack `{pack_id}` has an empty config key")]
169 ConfigOverrideEmptyKey { pack_id: String },
170
171 #[error(
172 "bundle deployment `{deployment}` config_overrides references pack `{pack_id}` which is not in any current revision's pack_list"
173 )]
174 ConfigOverridePackNotInRevisions {
175 deployment: DeploymentId,
176 pack_id: String,
177 },
178
179 #[error("public_base_url `{value}` is invalid: {reason}")]
180 InvalidPublicBaseUrl { value: String, reason: &'static str },
181}