pub type Result<T> = std::result::Result<T, ToolkitError>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ToolkitError {
#[error("failed to parse config TOML: {0}")]
Parse(#[from] toml::de::Error),
#[error("missing required config field: {0}")]
MissingField(String),
#[error("tool synthesis failed: {0}")]
Synth(String),
#[error("code-mode wiring failed: {0}")]
CodeMode(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("secret '{name}' not resolvable: {cause}")]
Secret {
name: String,
cause: String,
},
#[error("config validation failed: {0}")]
Validation(#[from] ConfigValidationError),
#[error(
"[backend].base_url references environment variable '{var}', which is \
unset or empty (set it to the REST API root URL)"
)]
UnresolvedBaseUrlRef {
var: String,
},
#[cfg(feature = "workbook")]
#[error("workbook bundle load failed: {0}")]
Workbook(#[from] pmcp_workbook_runtime::BundleLoadError),
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ConfigValidationError {
#[error("server.name must be non-empty")]
EmptyServerName,
#[error("server.version must be non-empty")]
EmptyServerVersion,
#[error("[[tools]] entry at index {0} has empty name")]
EmptyToolName(usize),
#[error("[[database.tables]] entry at index {0} has empty name")]
EmptyTableName(usize),
#[error(
"[code_mode].token_secret is an inline literal; use 'env:VAR_NAME' \
or set allow_inline_token_secret_for_dev=true (NEVER in production)"
)]
InlineSecretRejected,
#[error(
"[[tools]] entry at index {0} declares ambiguous tool kind: set exactly \
one of `sql`, `path`/`method`, or `script` (not a mixture)"
)]
AmbiguousToolKind(usize),
#[error(
"[backend].base_url must be non-empty (set the REST API root URL, \
e.g. \"https://api.example.com\")"
)]
EmptyBackendBaseUrl,
#[error(
"[backend].base_url is a malformed environment reference; a reference must be \
exactly one `${{VAR}}` or `env:VAR` naming a single variable — inline \
compositions like \"${{SCHEME}}://${{HOST}}\" cannot be resolved by any \
environment, so compose the full URL in ONE variable instead"
)]
MalformedBackendBaseUrlRef,
#[error(
"[backend.auth].{0} is a malformed environment reference; a reference must be \
exactly one `${{VAR}}` (name matching [A-Za-z0-9_]+) or `env:VAR` naming a single \
variable — no environment can satisfy this value, so the credential would be \
silently omitted and every backend request sent unauthenticated"
)]
MalformedBackendAuthRef(String),
#[error("[[config_slots]] entry at index {0} has an empty key or name")]
EmptyConfigSlotField(usize),
#[error(
"[[config_slots]] entry at index {0} is kind = \"secret\" but carries a tested_value; \
identity-bearing slots record no value — remove it (a credential must never sit in \
the config file)"
)]
SecretSlotCarriesTestedValue(usize),
}