fix(manager-core): F-Q-011 blanket Arc<T: ScriptRepository> impl; delete PostgresScriptRepoHandle

A 70-line newtype lived in picloud/src/lib.rs wrapping
Arc<PostgresScriptRepository> and re-implementing every ScriptRepository
method as `self.0.method(...).await`. Hand-delegation invited silent
skew when the trait gained a method, and forced 8 call sites to know
about the wrapper.

Add a blanket
  impl<T: ScriptRepository + ?Sized> ScriptRepository for Arc<T>
in manager-core::repo. The implementations forward via (**self).method(...)
so any owner of an Arc<dyn ScriptRepository> (or Arc<ConcreteImpl>) can
be passed where the trait is expected.

Migrate the 7 picloud-binary call sites:
  Arc::new(PostgresScriptRepoHandle(script_repo.clone())) → script_repo.clone()

Delete the newtype + its hand-delegated impl. Leaves a 6-line comment
documenting why the boilerplate is gone.

AUDIT.md anchor: F-Q-011.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 21:10:46 +02:00
parent 6d35eaad92
commit 1911691420
2 changed files with 76 additions and 88 deletions

View File

@@ -76,6 +76,68 @@ pub trait ScriptRepository: Send + Sync {
-> Result<Vec<Script>, ScriptRepositoryError>;
}
/// F-Q-011: blanket impl for `Arc<T: ScriptRepository>`. Lets callers
/// pass a shared `Arc<PostgresScriptRepository>` directly anywhere a
/// `dyn ScriptRepository` is wanted — replacing the hand-delegated
/// `PostgresScriptRepoHandle` newtype that previously had to be
/// rewritten in lockstep every time the trait gained a method.
#[async_trait::async_trait]
impl<T: ScriptRepository + ?Sized> ScriptRepository for std::sync::Arc<T> {
async fn get(&self, id: ScriptId) -> Result<Option<Script>, ScriptRepositoryError> {
(**self).get(id).await
}
async fn get_by_name(
&self,
app_id: AppId,
name: &str,
) -> Result<Option<Script>, ScriptRepositoryError> {
(**self).get_by_name(app_id, name).await
}
async fn list(&self) -> Result<Vec<Script>, ScriptRepositoryError> {
(**self).list().await
}
async fn list_for_app(&self, app_id: AppId) -> Result<Vec<Script>, ScriptRepositoryError> {
(**self).list_for_app(app_id).await
}
async fn list_for_user(
&self,
user_id: AdminUserId,
) -> Result<Vec<Script>, ScriptRepositoryError> {
(**self).list_for_user(user_id).await
}
async fn create(&self, input: NewScript) -> Result<Script, ScriptRepositoryError> {
(**self).create(input).await
}
async fn update(
&self,
id: ScriptId,
patch: ScriptPatch,
) -> Result<Script, ScriptRepositoryError> {
(**self).update(id, patch).await
}
async fn delete(&self, id: ScriptId) -> Result<(), ScriptRepositoryError> {
(**self).delete(id).await
}
async fn count_routes_for_script(
&self,
script_id: ScriptId,
) -> Result<i64, ScriptRepositoryError> {
(**self).count_routes_for_script(script_id).await
}
async fn count_triggers_for_script(
&self,
script_id: ScriptId,
) -> Result<i64, ScriptRepositoryError> {
(**self).count_triggers_for_script(script_id).await
}
async fn list_imports(
&self,
script_id: ScriptId,
) -> Result<Vec<Script>, ScriptRepositoryError> {
(**self).list_imports(script_id).await
}
}
/// Inbound shape for create. Defaults match the migration's CHECK
/// constraints; the repo enforces them in the DB regardless.
#[derive(Debug, Clone)]