Skip to main content

systemprompt_security/authz/
error.rs

1//! Typed error surface for the authz crate.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use systemprompt_models::domain_error;
7use thiserror::Error;
8
9domain_error! {
10    pub enum AuthzError {
11        common: [repository, validation],
12
13        #[error("invalid rule_type: {0}")]
14        InvalidRuleType(String),
15
16        #[error("invalid access value: {0}")]
17        InvalidAccess(String),
18
19        #[error("unknown entity_type: {0}")]
20        UnknownEntityKind(String),
21
22        #[error("unknown access scope: {0}")]
23        UnknownAccessScope(String),
24
25        #[error(
26            "refusing to reconcile the gateway_route catalog against an empty route set — this \
27             would delete every route entity and cascade away every route grant; check that the \
28             profile actually declares a gateway"
29        )]
30        EmptyGatewayRouteSet,
31
32        #[error("authz hook transport: {0}")]
33        Hook(#[from] reqwest::Error),
34
35        #[error("authz bootstrap: {0}")]
36        Bootstrap(#[from] AuthzBootstrapError),
37
38        #[error("failed to {action} {path}: {source}")]
39        File {
40            action: &'static str,
41            path: String,
42            #[source]
43            source: std::io::Error,
44        },
45
46        #[error("failed to parse {path} as AccessControlConfig: {source}")]
47        ConfigParse {
48            path: String,
49            #[source]
50            source: serde_yaml::Error,
51        },
52
53        #[error(
54            "marketplace '{marketplace}': access.rules rule_type '{rule_type}' is not a valid \
55             subject dimension: {source}"
56        )]
57        MarketplaceRuleType {
58            marketplace: String,
59            rule_type: String,
60            #[source]
61            source: Box<AuthzError>,
62        },
63
64        #[error("invalid entity id: {0}")]
65        InvalidEntityId(#[source] systemprompt_identifiers::error::IdValidationError),
66    }
67}
68
69impl From<sqlx::Error> for AuthzError {
70    fn from(err: sqlx::Error) -> Self {
71        Self::Repository(systemprompt_models::errors::RepositoryError::from(err))
72    }
73}
74
75pub type AuthzResult<T> = Result<T, AuthzError>;
76
77#[derive(Debug, Error)]
78pub enum AuthzBootstrapError {
79    #[error(
80        "governance.authz.hook.mode = webhook but `url` is missing or blank — refusing to start"
81    )]
82    MissingWebhookUrl,
83
84    #[error("governance.authz.hook.url is invalid or unsafe: {0} — refusing to start")]
85    InvalidWebhookUrl(#[source] systemprompt_models::net::OutboundUrlError),
86
87    #[error(
88        "governance.authz.hook.mode = unrestricted requires `acknowledgement` field equal to the \
89         literal: {expected:?}"
90    )]
91    MissingUnrestrictedAcknowledgement { expected: &'static str },
92
93    #[error(
94        "governance.authz.hook.mode = extension but no extension hook was supplied via \
95         AppContextBuilder::with_authz_hook(...) — refusing to start"
96    )]
97    ExtensionModeButNoHook,
98
99    #[error(
100        "an extension authz hook was supplied via AppContextBuilder::with_authz_hook(...) but \
101         governance.authz.hook.mode is `{mode}` (must be `extension`) — refusing to start"
102    )]
103    ExtensionHookButWrongMode { mode: &'static str },
104
105    #[error(
106        "an extension authz hook was supplied via AppContextBuilder::with_authz_hook(...) but the \
107         profile has no `governance.authz` block — set `governance.authz.hook.mode = extension` \
108         or drop the `with_authz_hook` call"
109    )]
110    NoGovernanceButExtensionHook,
111}