Skip to main content

systemprompt_loader/
error.rs

1//! Error types raised by the loader infrastructure.
2//!
3//! Three thiserror-derived enums cover the public surface:
4//!
5//! - [`ConfigLoadError`] — services-config reading, YAML parsing, include
6//!   resolution, and validation failures.
7//! - [`ConfigWriteError`] — file creation, agent CRUD, and config-file editing
8//!   failures.
9//! - [`ExtensionLoadError`] — manifest discovery and registry lookups.
10//!
11//! All three implement `std::error::Error` and compose with upstream
12//! errors (`std::io::Error`, `serde_yaml::Error`,
13//! `systemprompt_config::ProfileBootstrapError`,
14//! `systemprompt_models::errors::ServicesValidationError`,
15//! `systemprompt_manifest::profile::ProfileError`) via `#[from]`.
16//!
17//! Copyright (c) systemprompt.io — Business Source License 1.1.
18//! See <https://systemprompt.io> for licensing details.
19
20use std::path::PathBuf;
21use systemprompt_identifiers::AgentName;
22use thiserror::Error;
23
24#[derive(Debug, Error)]
25pub enum ConfigLoadError {
26    #[error("profile bootstrap unavailable: {0}")]
27    ProfileBootstrap(#[from] systemprompt_config::ProfileBootstrapError),
28
29    #[error("io error at {path}: {source}")]
30    Io {
31        path: PathBuf,
32        #[source]
33        source: std::io::Error,
34    },
35
36    #[error("yaml parse failure at {path}: {source}")]
37    Yaml {
38        path: PathBuf,
39        #[source]
40        source: serde_yaml::Error,
41    },
42
43    #[error(
44        "include file not found: {include}\nReferenced in: {referrer}\nEither create the file or \
45         remove it from the includes list."
46    )]
47    IncludeNotFound { include: PathBuf, referrer: PathBuf },
48
49    #[error("invalid identifier at {path}: {source}")]
50    InvalidId {
51        path: PathBuf,
52        #[source]
53        source: systemprompt_identifiers::error::IdValidationError,
54    },
55
56    #[error("include cycle detected: {chain}")]
57    IncludeCycle { chain: String },
58
59    #[error("duplicate agent definition: {0}")]
60    DuplicateAgent(String),
61
62    #[error("duplicate MCP server definition: {0}")]
63    DuplicateMcpServer(String),
64
65    #[error("duplicate plugin definition: {0}")]
66    DuplicatePlugin(String),
67
68    #[error("duplicate marketplace definition: {0}")]
69    DuplicateMarketplace(String),
70
71    #[error("duplicate skill definition: {0}")]
72    DuplicateSkill(String),
73
74    #[error("duplicate external agent definition: {0}")]
75    DuplicateExternalAgent(String),
76
77    #[error("duplicate Slack app definition: {0}")]
78    DuplicateSlackApp(String),
79
80    #[error("duplicate Teams app definition: {0}")]
81    DuplicateTeamsApp(String),
82
83    #[error("{path}: {what} is the pre-0.73 catalog shape; {hint}", hint = systemprompt_manifest::services::catalog::MIGRATE_HINT)]
84    LegacyCatalog { path: PathBuf, what: &'static str },
85
86    #[error("catalog file unreadable: {0}")]
87    CatalogDocument(Box<systemprompt_manifest::services::catalog::CatalogDocumentError>),
88
89    #[error(
90        "{path} declares `{section}:`; the model catalog is read only from services/ai/{{{files}}}, \
91         so remove the section (and any `includes:` entry naming a catalog file); {hint}",
92        files = systemprompt_manifest::services::catalog::CATALOG_FILES.join(","),
93        hint = systemprompt_manifest::services::catalog::MIGRATE_HINT
94    )]
95    CatalogOutsideConvention {
96        path: PathBuf,
97        section: &'static str,
98    },
99
100    #[error("the services catalog does not resolve: {0}")]
101    Catalog(Box<systemprompt_manifest::services::catalog::CatalogError>),
102
103    #[error("services config validation failed: {0}")]
104    Validation(#[from] systemprompt_models::errors::ServicesValidationError),
105
106    #[error(
107        "include {path} sets `settings:` — settings are only valid in the root config file. Move \
108         the values to the root or remove them from the include."
109    )]
110    IncludeMustNotSetGlobalSettings { path: PathBuf },
111}
112
113impl From<systemprompt_manifest::services::catalog::CatalogDocumentError> for ConfigLoadError {
114    fn from(error: systemprompt_manifest::services::catalog::CatalogDocumentError) -> Self {
115        Self::CatalogDocument(Box::new(error))
116    }
117}
118
119impl From<systemprompt_manifest::services::catalog::CatalogError> for ConfigLoadError {
120    fn from(error: systemprompt_manifest::services::catalog::CatalogError) -> Self {
121        Self::Catalog(Box::new(error))
122    }
123}
124
125#[derive(Debug, Error)]
126pub enum ConfigWriteError {
127    #[error("io error at {path}: {source}")]
128    Io {
129        path: PathBuf,
130        #[source]
131        source: std::io::Error,
132    },
133
134    #[error("yaml serialisation failed: {0}")]
135    YamlEncode(#[from] serde_yaml::Error),
136
137    #[error("Agent file already exists: {0}. Use 'agents edit' to modify.")]
138    AgentFileExists(PathBuf),
139
140    #[error("Agent '{0}' not found in any configuration file")]
141    AgentNotFound(AgentName),
142}
143
144#[derive(Debug, Error)]
145pub enum ExtensionLoadError {
146    #[error("Binary '{name}' not found at {path}")]
147    BinaryNotFound { name: String, path: PathBuf },
148
149    #[error("No manifest.yaml found for extension '{0}' in extensions/")]
150    ManifestMissing(String),
151}
152
153#[derive(Debug, Error)]
154pub enum ProfileLoadError {
155    #[error("io error at {path}: {source}")]
156    Io {
157        path: PathBuf,
158        #[source]
159        source: std::io::Error,
160    },
161
162    #[error(transparent)]
163    Profile(#[from] systemprompt_manifest::profile::ProfileError),
164}
165
166pub type ConfigLoadResult<T> = Result<T, ConfigLoadError>;
167
168pub type ConfigWriteResult<T> = Result<T, ConfigWriteError>;
169
170pub type ExtensionLoadResult<T> = Result<T, ExtensionLoadError>;
171
172pub type ProfileLoadResult<T> = Result<T, ProfileLoadError>;