Skip to main content

systemprompt_config/
error.rs

1//! Public error type for `systemprompt-config`.
2//!
3//! All public APIs of this crate return [`ConfigError`] (or
4//! [`ConfigResult<T>`]) instead of `anyhow::Error`. The enum is
5//! `#[non_exhaustive]` so additional variants can be added in patch
6//! releases without breaking downstream code that performs exhaustive
7//! matching only on the documented variants.
8//!
9//! Upstream errors are composed via `#[from]` so callers can use `?`
10//! transparently from `std::io`, `serde_json` and `serde_yaml`
11//! operations performed inside the bootstrap pipeline.
12//!
13//! Copyright (c) systemprompt.io — Business Source License 1.1.
14//! See <https://systemprompt.io> for licensing details.
15
16use std::path::PathBuf;
17
18use systemprompt_identifiers::{ModelId, ProviderId};
19use systemprompt_manifest::profile::ProfileError;
20use systemprompt_models::errors::{GlobalConfigError, SecretsError};
21
22use crate::bootstrap::{ProfileBootstrapError, SecretsBootstrapError};
23use crate::services::ConfigValidationError;
24
25pub type ConfigResult<T> = Result<T, ConfigError>;
26
27#[derive(Debug, thiserror::Error)]
28#[non_exhaustive]
29pub enum ConfigError {
30    #[error("Config already initialized")]
31    AlreadyInitialized,
32
33    #[error(
34        "cannot resolve a stable instance id for a cloud profile: set server.instance_id or \
35         export HOSTNAME so this replica keeps one identity across restarts"
36    )]
37    InstanceIdUnresolved,
38
39    #[error(transparent)]
40    Profile(#[from] ProfileBootstrapError),
41
42    #[error(transparent)]
43    Secrets(#[from] SecretsBootstrapError),
44
45    #[error(transparent)]
46    ProfileParse(#[from] ProfileError),
47
48    #[error(transparent)]
49    SchemaValidation(#[from] ConfigValidationError),
50
51    #[error(transparent)]
52    SecretsParse(#[from] SecretsError),
53
54    #[error(transparent)]
55    Io(#[from] std::io::Error),
56
57    #[error(transparent)]
58    Json(#[from] serde_json::Error),
59
60    #[error(transparent)]
61    Yaml(#[from] serde_yaml::Error),
62
63    #[error("Missing required path: paths.{field}")]
64    MissingProfilePath { field: String },
65
66    #[error("Failed to canonicalize {name} path: {source}")]
67    CanonicalizePath {
68        name: String,
69        #[source]
70        source: std::io::Error,
71    },
72
73    #[error("Profile path '{field}' cannot be read: {path}")]
74    ReadProfilePath {
75        field: String,
76        path: PathBuf,
77        #[source]
78        source: std::io::Error,
79    },
80
81    #[error("Profile path '{field}' has invalid YAML: {path}")]
82    InvalidProfileYaml {
83        field: String,
84        path: PathBuf,
85        #[source]
86        source: serde_yaml::Error,
87    },
88
89    #[error("Profile path validation failed: {message}")]
90    ProfilePathReport { message: String },
91
92    #[error("Unsupported database type '{db_type}'. Only 'postgres' is supported.")]
93    UnsupportedDatabaseType { db_type: String },
94
95    #[error("Invalid database URL: {0}")]
96    InvalidDatabaseUrl(#[source] GlobalConfigError),
97
98    #[error(
99        "Profile is missing required `system_admin.username` and `SYSTEMPROMPT_SYSTEM_ADMIN` is \
100         not set. The platform refuses to start without an explicit system-admin identity."
101    )]
102    MissingSystemAdmin,
103
104    #[error("No provider named {name}")]
105    ProviderNotFound { name: ProviderId },
106
107    #[error("No model with id {id} under provider {provider}")]
108    ModelNotFound { id: ModelId, provider: ProviderId },
109
110    #[error("Access token expiry must be positive")]
111    NonPositiveAccessTokenExpiry,
112
113    #[error("Refresh token expiry must be positive")]
114    NonPositiveRefreshTokenExpiry,
115
116    #[error("No trusted issuer found with issuer {issuer}")]
117    TrustedIssuerNotFound { issuer: String },
118
119    #[error("Profile path has no parent directory: {path}")]
120    ProfilePathWithoutParent { path: PathBuf },
121
122    #[error("Secrets file root is not a JSON object: {path}")]
123    SecretsFileNotObject { path: PathBuf },
124}