diff --git a/crates/api-types/src/v3/policy.rs b/crates/api-types/src/v3/policy.rs index ac0fbfd32..6d76879c8 100644 --- a/crates/api-types/src/v3/policy.rs +++ b/crates/api-types/src/v3/policy.rs @@ -19,8 +19,8 @@ //! * **Requests** accept a `String` only, per `keystone/policy/schema.py` //! (`{'blob': {'type': 'string'}}`). An object-valued `blob` is a 400. //! * **Responses** carry an arbitrary JSON value, because the stored column -//! round-trips whatever was written and rows created by older python -//! keystone releases may hold objects. +//! round-trips whatever was written and rows created by older python keystone +//! releases may hold objects. use std::collections::HashMap; diff --git a/crates/domain-config-driver-sql/src/get.rs b/crates/domain-config-driver-sql/src/get.rs index c885a16bf..5aa031f61 100644 --- a/crates/domain-config-driver-sql/src/get.rs +++ b/crates/domain-config-driver-sql/src/get.rs @@ -161,9 +161,9 @@ pub async fn get_config( /// - `group`: The group to read. /// /// # Returns -/// - `Result, DomainConfigProviderError>` - The -/// group without any sensitive option, or `None` when the domain has no -/// readable option stored in it. +/// - `Result, DomainConfigProviderError>` - The group +/// without any sensitive option, or `None` when the domain has no readable +/// option stored in it. pub async fn get_group( db: &C, domain_id: &str, diff --git a/crates/domain-config-driver-sql/src/option.rs b/crates/domain-config-driver-sql/src/option.rs index 1f427bd3d..11497abea 100644 --- a/crates/domain-config-driver-sql/src/option.rs +++ b/crates/domain-config-driver-sql/src/option.rs @@ -152,8 +152,9 @@ where /// - `options`: The options to persist. /// /// # Returns -/// - `Result<(Vec, Vec), DomainConfigProviderError>` - -/// The readable and the sensitive rows. +/// - `Result<(Vec, +/// Vec), DomainConfigProviderError>` - The +/// readable and the sensitive rows. #[allow(clippy::type_complexity)] pub(crate) fn to_rows<'a, I>( domain_id: &str, diff --git a/crates/trust-driver-sql/src/entity/trust.rs b/crates/trust-driver-sql/src/entity/trust.rs index e05da5087..62f8e1281 100644 --- a/crates/trust-driver-sql/src/entity/trust.rs +++ b/crates/trust-driver-sql/src/entity/trust.rs @@ -27,12 +27,20 @@ pub struct Model { pub impersonation: bool, pub deleted_at: Option, pub expires_at: Option, - pub remaining_uses: Option, + // Deployed column is a signed `INT` (see `redelegation_count` comment + // below for why the Rust type has to match). + pub remaining_uses: Option, #[sea_orm(column_type = "Text", nullable)] pub extra: Option, pub expires_at_int: Option, pub redelegated_trust_id: Option, - pub redelegation_count: Option, + // Deployed column is a signed `INT` (predates this field, and `db + // sync` never alters existing columns), not the `INT UNSIGNED` that + // sea-orm's default codegen maps `u32` to -- sqlx's typed decode then + // rejects every non-null value on existing installs. Keep the Rust + // field signed to match what's actually on disk; convert to the + // domain's `u32` at the `TryFrom`/`create` boundary instead. + pub redelegation_count: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/crates/trust-driver-sql/src/trust.rs b/crates/trust-driver-sql/src/trust.rs index 428a0edaa..26eec0381 100644 --- a/crates/trust-driver-sql/src/trust.rs +++ b/crates/trust-driver-sql/src/trust.rs @@ -65,20 +65,44 @@ impl TryFrom for Trust { } } + if let Some(val) = value.redelegation_count { + match u32::try_from(val) { + Ok(val) => { + builder.redelegation_count(val); + } + Err(_) => { + error!( + trust_id = %value.id, + redelegation_count = val, + "trust.redelegation_count is negative; treating as unset" + ); + } + } + } + + if let Some(val) = value.remaining_uses { + match u32::try_from(val) { + Ok(val) => { + builder.remaining_uses(val); + } + Err(_) => { + error!( + trust_id = %value.id, + remaining_uses = val, + "trust.remaining_uses is negative; treating as unset" + ); + } + } + } + builder.id(value.id); builder.impersonation(value.impersonation); if let Some(val) = &value.project_id { builder.project_id(val); } - if let Some(val) = value.remaining_uses { - builder.remaining_uses(val); - } if let Some(val) = &value.redelegated_trust_id { builder.redelegated_trust_id(val); } - if let Some(val) = value.redelegation_count { - builder.redelegation_count(val); - } builder.trustor_user_id(value.trustor_user_id); builder.trustee_user_id(value.trustee_user_id); Ok(builder.build()?) diff --git a/crates/trust-driver-sql/src/trust/create.rs b/crates/trust-driver-sql/src/trust/create.rs index 0eefb7b78..b3cc237bc 100644 --- a/crates/trust-driver-sql/src/trust/create.rs +++ b/crates/trust-driver-sql/src/trust/create.rs @@ -38,11 +38,16 @@ impl TryFrom for db_trust::ActiveModel { impersonation: Set(value.impersonation), deleted_at: NotSet, expires_at: NotSet, - remaining_uses: Set(value.remaining_uses), + // Column signed `INT` (see entity comment); remaining_uses is + // always small, cast never truncates in practice. + remaining_uses: Set(value.remaining_uses.map(|v| v as i32)), extra: Set(value.extra.map(|v| serde_json::to_string(&v)).transpose()?), expires_at_int: Set(value.expires_at.map(|v| v.timestamp_micros())), redelegated_trust_id: Set(value.redelegated_trust_id), - redelegation_count: Set(value.redelegation_count), + // Column is signed `INT` (see entity comment); `redelegation_count` + // is always small and bounded by `max_redelegation_count` + // config, so the cast never truncates in practice. + redelegation_count: Set(value.redelegation_count.map(|v| v as i32)), }) } } diff --git a/tests/api/src/macros.rs b/tests/api/src/macros.rs index 7dc200500..8fca7203c 100644 --- a/tests/api/src/macros.rs +++ b/tests/api/src/macros.rs @@ -93,8 +93,8 @@ //! - `update`: private request struct, `RestEndpoint` impl (PATCH `path/{id}`, //! JSON body under `body_key`), and `pub async fn (tc, id, //! ) -> Result`. `update_put` is the identical arm -//! emitting `PUT` instead — every v4 update handler declares `put` where -//! its v3 counterpart declares `patch`, so v4 helpers need it. +//! emitting `PUT` instead — every v4 update handler declares `put` where its +//! v3 counterpart declares `patch`, so v4 helpers need it. //! - `list`: **public** request struct with `Option` query fields, //! `RestEndpoint` impl (GET `path`), and `pub async fn (tc, params) -> //! Result>`.