properly handling patch updates
Some checks failed
CI / Clippy (push) Failing after 3m6s
CI / Rustfmt (push) Failing after 3m9s
CI / Cargo Audit & Deny (push) Successful in 5m2s
CI / Tests (push) Successful in 8m15s
CI / Security Blocking Checks (push) Successful in 10s
CI / Web Advisory Checks (push) Successful in 1m4s
CI / Web Blocking Checks (push) Failing after 4m52s
Publish Images And Chart / Resolve Publish Metadata (push) Successful in 2s
CI / Security Advisory Checks (push) Successful in 1m31s
Publish Images And Chart / Publish init-user (push) Failing after 30s
Publish Images And Chart / Publish init-packs (push) Failing after 1m41s
Publish Images And Chart / Publish migrations (push) Failing after 10s
Publish Images And Chart / Publish web (push) Failing after 11s
Publish Images And Chart / Publish sensor (push) Failing after 32s
Publish Images And Chart / Publish worker (push) Failing after 11s
Publish Images And Chart / Publish executor (push) Failing after 11s
Publish Images And Chart / Publish notifier (push) Failing after 9s
Publish Images And Chart / Publish api (push) Failing after 31s
Publish Images And Chart / Publish Helm Chart (push) Has been skipped

This commit is contained in:
2026-03-17 12:17:58 -05:00
parent 643023b6d5
commit f96861d417
136 changed files with 3782 additions and 1553 deletions

View File

@@ -42,7 +42,6 @@ use crate::repositories::action::{ActionRepository, UpdateActionInput};
use crate::repositories::identity::{
CreatePermissionSetInput, PermissionSetRepository, UpdatePermissionSetInput,
};
use crate::repositories::runtime::{CreateRuntimeInput, RuntimeRepository, UpdateRuntimeInput};
use crate::repositories::runtime_version::{
CreateRuntimeVersionInput, RuntimeVersionRepository, UpdateRuntimeVersionInput,
};
@@ -53,7 +52,10 @@ use crate::repositories::trigger::{
use crate::repositories::workflow::{
CreateWorkflowDefinitionInput, UpdateWorkflowDefinitionInput, WorkflowDefinitionRepository,
};
use crate::repositories::{Create, Delete, FindById, FindByRef, Update};
use crate::repositories::{
runtime::{CreateRuntimeInput, RuntimeRepository, UpdateRuntimeInput},
Create, Delete, FindById, FindByRef, Patch, Update,
};
use crate::version_matching::extract_version_components;
use crate::workflow::parser::parse_workflow_yaml;
@@ -405,10 +407,16 @@ impl<'a> PackComponentLoader<'a> {
// Check if runtime already exists — update in place if so
if let Some(existing) = RuntimeRepository::find_by_ref(self.pool, &runtime_ref).await? {
let update_input = UpdateRuntimeInput {
description,
description: Some(match description {
Some(description) => Patch::Set(description),
None => Patch::Clear,
}),
name: Some(name),
distributions: Some(distributions),
installation,
installation: Some(match installation {
Some(installation) => Patch::Set(installation),
None => Patch::Clear,
}),
execution_config: Some(execution_config),
};
@@ -547,9 +555,18 @@ impl<'a> PackComponentLoader<'a> {
{
let update_input = UpdateRuntimeVersionInput {
version: None, // version string doesn't change
version_major: Some(version_major),
version_minor: Some(version_minor),
version_patch: Some(version_patch),
version_major: Some(match version_major {
Some(value) => Patch::Set(value),
None => Patch::Clear,
}),
version_minor: Some(match version_minor {
Some(value) => Patch::Set(value),
None => Patch::Clear,
}),
version_patch: Some(match version_patch {
Some(value) => Patch::Set(value),
None => Patch::Clear,
}),
execution_config: Some(execution_config),
distributions: Some(distributions),
is_default: Some(is_default),
@@ -713,10 +730,16 @@ impl<'a> PackComponentLoader<'a> {
if let Some(existing) = TriggerRepository::find_by_ref(self.pool, &trigger_ref).await? {
let update_input = UpdateTriggerInput {
label: Some(label),
description: Some(description),
description: Some(Patch::Set(description)),
enabled: Some(enabled),
param_schema,
out_schema,
param_schema: Some(match param_schema {
Some(value) => Patch::Set(value),
None => Patch::Clear,
}),
out_schema: Some(match out_schema {
Some(value) => Patch::Set(value),
None => Patch::Clear,
}),
};
match TriggerRepository::update(self.pool, existing.id, update_input).await {
@@ -921,7 +944,10 @@ impl<'a> PackComponentLoader<'a> {
description: Some(description),
entrypoint: Some(entrypoint),
runtime: runtime_id,
runtime_version_constraint: Some(runtime_version_constraint),
runtime_version_constraint: Some(match runtime_version_constraint {
Some(value) => Patch::Set(value),
None => Patch::Clear,
}),
param_schema,
out_schema,
parameter_delivery: Some(parameter_delivery),
@@ -1310,11 +1336,17 @@ impl<'a> PackComponentLoader<'a> {
entrypoint: Some(entrypoint),
runtime: Some(sensor_runtime_id),
runtime_ref: Some(sensor_runtime_ref.clone()),
runtime_version_constraint: Some(runtime_version_constraint.clone()),
runtime_version_constraint: Some(match runtime_version_constraint.clone() {
Some(value) => Patch::Set(value),
None => Patch::Clear,
}),
trigger: Some(trigger_id.unwrap_or(existing.trigger)),
trigger_ref: Some(trigger_ref.unwrap_or(existing.trigger_ref.clone())),
enabled: Some(enabled),
param_schema,
param_schema: Some(match param_schema {
Some(value) => Patch::Set(value),
None => Patch::Clear,
}),
config: Some(config),
};

View File

@@ -6,7 +6,7 @@ use crate::models::{action::*, enums::PolicyMethod, Id, JsonSchema};
use crate::{Error, Result};
use sqlx::{Executor, Postgres, QueryBuilder};
use super::{Create, Delete, FindById, FindByRef, List, Repository, Update};
use super::{Create, Delete, FindById, FindByRef, List, Patch, Repository, Update};
/// Columns selected in all Action queries. Must match the `Action` model's `FromRow` fields.
pub const ACTION_COLUMNS: &str = "id, ref, pack, pack_ref, label, description, entrypoint, \
@@ -67,7 +67,7 @@ pub struct UpdateActionInput {
pub description: Option<String>,
pub entrypoint: Option<String>,
pub runtime: Option<Id>,
pub runtime_version_constraint: Option<Option<String>>,
pub runtime_version_constraint: Option<Patch<String>>,
pub param_schema: Option<JsonSchema>,
pub out_schema: Option<JsonSchema>,
pub parameter_delivery: Option<String>,
@@ -237,7 +237,10 @@ impl Update for ActionRepository {
query.push(", ");
}
query.push("runtime_version_constraint = ");
query.push_bind(runtime_version_constraint);
match runtime_version_constraint {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<String>::None),
};
has_updates = true;
}

View File

@@ -8,7 +8,7 @@ use crate::models::{
use crate::Result;
use sqlx::{Executor, Postgres, QueryBuilder};
use super::{Create, Delete, FindById, FindByRef, List, Repository, Update};
use super::{Create, Delete, FindById, FindByRef, List, Patch, Repository, Update};
// ============================================================================
// ArtifactRepository
@@ -48,12 +48,12 @@ pub struct UpdateArtifactInput {
pub visibility: Option<ArtifactVisibility>,
pub retention_policy: Option<RetentionPolicyType>,
pub retention_limit: Option<i32>,
pub name: Option<String>,
pub description: Option<String>,
pub content_type: Option<String>,
pub name: Option<Patch<String>>,
pub description: Option<Patch<String>>,
pub content_type: Option<Patch<String>>,
pub size_bytes: Option<i64>,
pub execution: Option<Option<i64>>,
pub data: Option<serde_json::Value>,
pub execution: Option<Patch<i64>>,
pub data: Option<Patch<serde_json::Value>>,
}
/// Filters for searching artifacts
@@ -186,20 +186,62 @@ impl Update for ArtifactRepository {
push_field!(input.visibility, "visibility");
push_field!(input.retention_policy, "retention_policy");
push_field!(input.retention_limit, "retention_limit");
push_field!(&input.name, "name");
push_field!(&input.description, "description");
push_field!(&input.content_type, "content_type");
if let Some(name) = &input.name {
if has_updates {
query.push(", ");
}
query.push("name = ");
match name {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<String>::None),
};
has_updates = true;
}
if let Some(description) = &input.description {
if has_updates {
query.push(", ");
}
query.push("description = ");
match description {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<String>::None),
};
has_updates = true;
}
if let Some(content_type) = &input.content_type {
if has_updates {
query.push(", ");
}
query.push("content_type = ");
match content_type {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<String>::None),
};
has_updates = true;
}
push_field!(input.size_bytes, "size_bytes");
// execution is Option<Option<i64>> — outer Option = "was field provided?",
// inner Option = nullable column value
if let Some(exec_val) = input.execution {
if has_updates {
query.push(", ");
}
query.push("execution = ").push_bind(exec_val);
query.push("execution = ");
match exec_val {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<i64>::None),
};
has_updates = true;
}
if let Some(data) = &input.data {
if has_updates {
query.push(", ");
}
query.push("data = ");
match data {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<serde_json::Value>::None),
};
has_updates = true;
}
push_field!(&input.data, "data");
if !has_updates {
return Self::get_by_id(executor, id).await;

View File

@@ -66,6 +66,14 @@ pub use runtime_version::RuntimeVersionRepository;
pub use trigger::{SensorRepository, TriggerRepository};
pub use workflow::{WorkflowDefinitionRepository, WorkflowExecutionRepository};
/// Explicit patch operation for update inputs where callers must distinguish
/// between "leave unchanged", "set value", and "clear to NULL".
#[derive(Debug, Clone, PartialEq)]
pub enum Patch<T> {
Set(T),
Clear,
}
/// Type alias for database connection/transaction
pub type DbConnection<'c> = &'c mut Transaction<'c, Postgres>;

View File

@@ -6,7 +6,7 @@ use crate::models::{pack::Pack, JsonDict, JsonSchema};
use crate::{Error, Result};
use sqlx::{Executor, Postgres, QueryBuilder};
use super::{Create, Delete, FindById, FindByRef, List, Pagination, Repository, Update};
use super::{Create, Delete, FindById, FindByRef, List, Pagination, Patch, Repository, Update};
/// Repository for Pack operations
pub struct PackRepository;
@@ -40,7 +40,7 @@ pub struct CreatePackInput {
#[derive(Debug, Clone, Default)]
pub struct UpdatePackInput {
pub label: Option<String>,
pub description: Option<String>,
pub description: Option<Patch<String>>,
pub version: Option<String>,
pub conf_schema: Option<JsonSchema>,
pub config: Option<JsonDict>,
@@ -186,7 +186,10 @@ impl Update for PackRepository {
query.push(", ");
}
query.push("description = ");
query.push_bind(description);
match description {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<String>::None),
};
has_updates = true;
}

View File

@@ -10,7 +10,7 @@ use crate::models::{
use crate::Result;
use sqlx::{Executor, Postgres, QueryBuilder};
use super::{Create, Delete, FindById, FindByRef, List, Repository, Update};
use super::{Create, Delete, FindById, FindByRef, List, Patch, Repository, Update};
/// Repository for Runtime operations
pub struct RuntimeRepository;
@@ -39,10 +39,10 @@ pub struct CreateRuntimeInput {
/// Input for updating a runtime
#[derive(Debug, Clone, Default)]
pub struct UpdateRuntimeInput {
pub description: Option<String>,
pub description: Option<Patch<String>>,
pub name: Option<String>,
pub distributions: Option<JsonDict>,
pub installation: Option<JsonDict>,
pub installation: Option<Patch<JsonDict>>,
pub execution_config: Option<JsonDict>,
}
@@ -163,7 +163,10 @@ impl Update for RuntimeRepository {
if let Some(description) = &input.description {
query.push("description = ");
query.push_bind(description);
match description {
Patch::Set(description) => query.push_bind(description),
Patch::Clear => query.push_bind(Option::<String>::None),
};
has_updates = true;
}
@@ -190,7 +193,10 @@ impl Update for RuntimeRepository {
query.push(", ");
}
query.push("installation = ");
query.push_bind(installation);
match installation {
Patch::Set(installation) => query.push_bind(installation),
Patch::Clear => query.push_bind(Option::<JsonDict>::None),
};
has_updates = true;
}

View File

@@ -5,7 +5,7 @@
use crate::error::Result;
use crate::models::{Id, RuntimeVersion};
use crate::repositories::{Create, Delete, FindById, List, Repository, Update};
use crate::repositories::{Create, Delete, FindById, List, Patch, Repository, Update};
use sqlx::{Executor, Postgres, QueryBuilder};
/// Repository for runtime version database operations
@@ -39,14 +39,14 @@ pub struct CreateRuntimeVersionInput {
#[derive(Debug, Clone, Default)]
pub struct UpdateRuntimeVersionInput {
pub version: Option<String>,
pub version_major: Option<Option<i32>>,
pub version_minor: Option<Option<i32>>,
pub version_patch: Option<Option<i32>>,
pub version_major: Option<Patch<i32>>,
pub version_minor: Option<Patch<i32>>,
pub version_patch: Option<Patch<i32>>,
pub execution_config: Option<serde_json::Value>,
pub distributions: Option<serde_json::Value>,
pub is_default: Option<bool>,
pub available: Option<bool>,
pub verified_at: Option<Option<chrono::DateTime<chrono::Utc>>>,
pub verified_at: Option<Patch<chrono::DateTime<chrono::Utc>>>,
pub meta: Option<serde_json::Value>,
}
@@ -154,7 +154,10 @@ impl Update for RuntimeVersionRepository {
query.push(", ");
}
query.push("version_major = ");
query.push_bind(*version_major);
match version_major {
Patch::Set(value) => query.push_bind(*value),
Patch::Clear => query.push_bind(Option::<i32>::None),
};
has_updates = true;
}
@@ -163,7 +166,10 @@ impl Update for RuntimeVersionRepository {
query.push(", ");
}
query.push("version_minor = ");
query.push_bind(*version_minor);
match version_minor {
Patch::Set(value) => query.push_bind(*value),
Patch::Clear => query.push_bind(Option::<i32>::None),
};
has_updates = true;
}
@@ -172,7 +178,10 @@ impl Update for RuntimeVersionRepository {
query.push(", ");
}
query.push("version_patch = ");
query.push_bind(*version_patch);
match version_patch {
Patch::Set(value) => query.push_bind(*value),
Patch::Clear => query.push_bind(Option::<i32>::None),
};
has_updates = true;
}
@@ -217,7 +226,10 @@ impl Update for RuntimeVersionRepository {
query.push(", ");
}
query.push("verified_at = ");
query.push_bind(*verified_at);
match verified_at {
Patch::Set(value) => query.push_bind(*value),
Patch::Clear => query.push_bind(Option::<chrono::DateTime<chrono::Utc>>::None),
};
has_updates = true;
}

View File

@@ -7,7 +7,7 @@ use crate::Result;
use serde_json::Value as JsonValue;
use sqlx::{Executor, Postgres, QueryBuilder};
use super::{Create, Delete, FindById, FindByRef, List, Repository, Update};
use super::{Create, Delete, FindById, FindByRef, List, Patch, Repository, Update};
// ============================================================================
// Trigger Search
@@ -88,10 +88,10 @@ pub struct CreateTriggerInput {
#[derive(Debug, Clone, Default)]
pub struct UpdateTriggerInput {
pub label: Option<String>,
pub description: Option<String>,
pub description: Option<Patch<String>>,
pub enabled: Option<bool>,
pub param_schema: Option<JsonSchema>,
pub out_schema: Option<JsonSchema>,
pub param_schema: Option<Patch<JsonSchema>>,
pub out_schema: Option<Patch<JsonSchema>>,
}
#[async_trait::async_trait]
@@ -229,7 +229,10 @@ impl Update for TriggerRepository {
query.push(", ");
}
query.push("description = ");
query.push_bind(description);
match description {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<String>::None),
};
has_updates = true;
}
@@ -247,7 +250,10 @@ impl Update for TriggerRepository {
query.push(", ");
}
query.push("param_schema = ");
query.push_bind(param_schema);
match param_schema {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<JsonSchema>::None),
};
has_updates = true;
}
@@ -256,7 +262,10 @@ impl Update for TriggerRepository {
query.push(", ");
}
query.push("out_schema = ");
query.push_bind(out_schema);
match out_schema {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<JsonSchema>::None),
};
has_updates = true;
}
@@ -676,11 +685,11 @@ pub struct UpdateSensorInput {
pub entrypoint: Option<String>,
pub runtime: Option<Id>,
pub runtime_ref: Option<String>,
pub runtime_version_constraint: Option<Option<String>>,
pub runtime_version_constraint: Option<Patch<String>>,
pub trigger: Option<Id>,
pub trigger_ref: Option<String>,
pub enabled: Option<bool>,
pub param_schema: Option<JsonSchema>,
pub param_schema: Option<Patch<JsonSchema>>,
pub config: Option<JsonValue>,
}
@@ -866,7 +875,10 @@ impl Update for SensorRepository {
query.push(", ");
}
query.push("runtime_version_constraint = ");
query.push_bind(runtime_version_constraint);
match runtime_version_constraint {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<String>::None),
};
has_updates = true;
}
@@ -893,7 +905,10 @@ impl Update for SensorRepository {
query.push(", ");
}
query.push("param_schema = ");
query.push_bind(param_schema);
match param_schema {
Patch::Set(value) => query.push_bind(value),
Patch::Clear => query.push_bind(Option::<JsonSchema>::None),
};
has_updates = true;
}

View File

@@ -6,7 +6,9 @@
mod helpers;
use attune_common::repositories::pack::{self, PackRepository};
use attune_common::repositories::{Create, Delete, FindById, FindByRef, List, Pagination, Update};
use attune_common::repositories::{
Create, Delete, FindById, FindByRef, List, Pagination, Patch, Update,
};
use attune_common::Error;
use helpers::*;
use serde_json::json;
@@ -214,7 +216,7 @@ async fn test_update_pack() {
let update_input = pack::UpdatePackInput {
label: Some("Updated Label".to_string()),
version: Some("2.0.0".to_string()),
description: Some("Updated description".to_string()),
description: Some(Patch::Set("Updated description".to_string())),
..Default::default()
};

View File

@@ -9,7 +9,7 @@ use attune_common::models::enums::{
use attune_common::repositories::artifact::{
ArtifactRepository, CreateArtifactInput, UpdateArtifactInput,
};
use attune_common::repositories::{Create, Delete, FindById, FindByRef, List, Update};
use attune_common::repositories::{Create, Delete, FindById, FindByRef, List, Patch, Update};
use attune_common::Error;
use sqlx::PgPool;
use std::collections::hash_map::DefaultHasher;
@@ -267,11 +267,11 @@ async fn test_update_artifact_all_fields() {
visibility: Some(ArtifactVisibility::Public),
retention_policy: Some(RetentionPolicyType::Days),
retention_limit: Some(30),
name: Some("Updated Name".to_string()),
description: Some("Updated description".to_string()),
content_type: Some("image/png".to_string()),
name: Some(Patch::Set("Updated Name".to_string())),
description: Some(Patch::Set("Updated description".to_string())),
content_type: Some(Patch::Set("image/png".to_string())),
size_bytes: Some(12345),
data: Some(serde_json::json!({"key": "value"})),
data: Some(Patch::Set(serde_json::json!({"key": "value"}))),
execution: None,
};

View File

@@ -6,7 +6,7 @@
use attune_common::repositories::runtime::{
CreateRuntimeInput, RuntimeRepository, UpdateRuntimeInput,
};
use attune_common::repositories::{Create, Delete, FindById, FindByRef, List, Update};
use attune_common::repositories::{Create, Delete, FindById, FindByRef, List, Patch, Update};
use serde_json::json;
use sqlx::PgPool;
use std::collections::hash_map::DefaultHasher;
@@ -259,14 +259,14 @@ async fn test_update_runtime() {
.expect("Failed to create runtime");
let update_input = UpdateRuntimeInput {
description: Some("Updated description".to_string()),
description: Some(Patch::Set("Updated description".to_string())),
name: Some("updated_name".to_string()),
distributions: Some(json!({
"linux": { "supported": false }
})),
installation: Some(json!({
installation: Some(Patch::Set(json!({
"method": "npm"
})),
}))),
execution_config: None,
};
@@ -275,10 +275,10 @@ async fn test_update_runtime() {
.expect("Failed to update runtime");
assert_eq!(updated.id, created.id);
assert_eq!(updated.description, update_input.description);
assert_eq!(updated.description, Some("Updated description".to_string()));
assert_eq!(updated.name, update_input.name.unwrap());
assert_eq!(updated.distributions, update_input.distributions.unwrap());
assert_eq!(updated.installation, update_input.installation);
assert_eq!(updated.installation, Some(json!({ "method": "npm" })));
assert!(updated.updated > created.updated);
}
@@ -294,7 +294,7 @@ async fn test_update_runtime_partial() {
.expect("Failed to create runtime");
let update_input = UpdateRuntimeInput {
description: Some("Only description changed".to_string()),
description: Some(Patch::Set("Only description changed".to_string())),
name: None,
distributions: None,
installation: None,
@@ -305,7 +305,10 @@ async fn test_update_runtime_partial() {
.await
.expect("Failed to update runtime");
assert_eq!(updated.description, update_input.description);
assert_eq!(
updated.description,
Some("Only description changed".to_string())
);
assert_eq!(updated.name, created.name);
assert_eq!(updated.distributions, created.distributions);
assert_eq!(updated.installation, created.installation);
@@ -610,7 +613,7 @@ async fn test_update_changes_timestamp() {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let update_input = UpdateRuntimeInput {
description: Some("Updated".to_string()),
description: Some(Patch::Set("Updated".to_string())),
..Default::default()
};

View File

@@ -8,7 +8,7 @@ mod helpers;
use attune_common::{
repositories::{
trigger::{CreateSensorInput, SensorRepository, UpdateSensorInput},
Create, Delete, FindById, FindByRef, List, Update,
Create, Delete, FindById, FindByRef, List, Patch, Update,
},
Error,
};
@@ -888,7 +888,7 @@ async fn test_update_param_schema() {
});
let input = UpdateSensorInput {
param_schema: Some(new_schema.clone()),
param_schema: Some(Patch::Set(new_schema.clone())),
..Default::default()
};
@@ -937,7 +937,7 @@ async fn test_update_multiple_fields() {
description: Some("Updated multiple fields".to_string()),
entrypoint: Some("sensors/multi.py".to_string()),
enabled: Some(false),
param_schema: Some(json!({"type": "object"})),
param_schema: Some(Patch::Set(json!({"type": "object"}))),
..Default::default()
};
@@ -1766,7 +1766,7 @@ async fn test_param_schema_can_be_null() {
// Update to add schema
let schema = json!({"type": "object"});
let input = UpdateSensorInput {
param_schema: Some(schema.clone()),
param_schema: Some(Patch::Set(schema.clone())),
..Default::default()
};

View File

@@ -8,7 +8,7 @@ mod helpers;
use attune_common::{
repositories::{
trigger::{CreateTriggerInput, TriggerRepository, UpdateTriggerInput},
Create, Delete, FindById, FindByRef, List, Update,
Create, Delete, FindById, FindByRef, List, Patch, Update,
},
Error,
};
@@ -477,7 +477,7 @@ async fn test_update_trigger() {
let update_input = UpdateTriggerInput {
label: Some("Updated Label".to_string()),
description: Some("Updated description".to_string()),
description: Some(Patch::Set("Updated description".to_string())),
enabled: Some(false),
param_schema: None,
out_schema: None,
@@ -571,8 +571,8 @@ async fn test_update_trigger_schemas() {
label: None,
description: None,
enabled: None,
param_schema: Some(new_param_schema.clone()),
out_schema: Some(new_out_schema.clone()),
param_schema: Some(Patch::Set(new_param_schema.clone())),
out_schema: Some(Patch::Set(new_out_schema.clone())),
};
let updated = TriggerRepository::update(&pool, trigger.id, update_input)