From e5cbf86baa1597a776bb1c22bf5ab8a3607f8606 Mon Sep 17 00:00:00 2001 From: Bilal Mahmoud Date: Sat, 21 Sep 2024 04:28:21 +0200 Subject: [PATCH] feat: move to v0.6 --- Cargo.lock | 57 ++---- Cargo.toml | 2 +- .../hash-graph/libs/api/src/rest/data_type.rs | 8 +- apps/hash-graph/libs/api/src/rest/entity.rs | 24 ++- .../libs/api/src/rest/entity_type.rs | 8 +- .../libs/api/src/rest/property_type.rs | 8 +- apps/hash-graph/libs/api/src/rest/status.rs | 2 +- apps/hash-graph/libs/graph/Cargo.toml | 2 +- .../store/postgres/knowledge/entity/mod.rs | 33 +-- .../libs/graph/src/store/postgres/mod.rs | 20 +- .../src/store/postgres/ontology/data_type.rs | 20 +- .../store/postgres/ontology/entity_type.rs | 20 +- .../store/postgres/ontology/property_type.rs | 20 +- .../type-system/rust/Cargo.toml | 2 +- .../src/schema/data_type/constraint/array.rs | 17 +- .../schema/data_type/constraint/boolean.rs | 17 +- .../src/schema/data_type/constraint/mod.rs | 12 -- .../src/schema/data_type/constraint/null.rs | 20 +- .../src/schema/data_type/constraint/number.rs | 33 ++- .../src/schema/data_type/constraint/object.rs | 17 +- .../src/schema/data_type/constraint/string.rs | 61 +++--- .../rust/src/schema/data_type/mod.rs | 53 ++--- libs/@local/hash-authorization/Cargo.toml | 2 +- .../hash-authorization/src/zanzibar/api.rs | 57 +++--- .../rust/src/knowledge/property/visitor.rs | 190 ++++++++--------- .../hash-graph-types/rust/src/ontology/mod.rs | 4 +- .../@local/hash-validation/src/entity_type.rs | 191 ++++++++---------- libs/@local/hash-validation/src/lib.rs | 10 +- libs/@local/hash-validation/src/property.rs | 8 +- libs/@local/hql/diagnostics/src/diagnostic.rs | 22 +- libs/deer/Cargo.toml | 2 +- libs/deer/json/Cargo.toml | 2 +- libs/deer/json/src/array.rs | 12 +- libs/deer/json/src/deserializer.rs | 28 +-- libs/deer/json/src/error.rs | 31 --- libs/deer/json/src/object.rs | 38 ++-- libs/deer/src/bound.rs | 16 +- libs/deer/src/error/mod.rs | 23 +-- libs/deer/src/error/serialize.rs | 37 ++-- libs/deer/src/error/tuple.rs | 8 +- libs/deer/src/ext.rs | 69 ------- libs/deer/src/helpers.rs | 5 +- libs/deer/src/impls/core/array.rs | 21 +- libs/deer/src/impls/core/ops.rs | 18 +- libs/deer/src/impls/core/tuples.rs | 7 +- libs/deer/src/lib.rs | 1 - libs/deer/src/value/mod.rs | 6 +- libs/deer/src/value/object.rs | 5 +- libs/deer/tests/common.rs | 68 ------- libs/deer/tests/test_bound.rs | 40 ++-- libs/deer/tests/test_enum_visitor.rs | 23 +-- libs/deer/tests/test_struct_visitor.rs | 18 +- 52 files changed, 546 insertions(+), 872 deletions(-) delete mode 100644 libs/deer/src/ext.rs diff --git a/Cargo.lock b/Cargo.lock index d6fc48f9f3b..9bda6c81c3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,7 +368,7 @@ version = "0.0.0" dependencies = [ "codec", "derive-where", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "futures-core", "graph-types", @@ -1224,7 +1224,7 @@ version = "0.0.0" dependencies = [ "bytes", "derive-where", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "harpc-wire-protocol", "regex", "serde", @@ -1619,7 +1619,7 @@ dependencies = [ "approx", "deer-desert", "erased-serde", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "num-traits", "paste", "proptest", @@ -1636,7 +1636,7 @@ version = "0.0.0" dependencies = [ "bitvec", "deer", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "num-traits", "serde", "serde_json", @@ -1648,7 +1648,7 @@ name = "deer-json" version = "0.0.0-reserved" dependencies = [ "deer", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "justjson", "lexical 7.0.1", "memchr", @@ -1964,24 +1964,11 @@ dependencies = [ "trybuild", ] -[[package]] -name = "error-stack" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe413319145d1063f080f27556fd30b1d70b01e2ba10c2a6e40d4be982ffc5d1" -dependencies = [ - "anyhow", - "rustc_version", - "serde", - "spin 0.9.8", - "tracing-error", -] - [[package]] name = "error-stack-macros" version = "0.0.0-reserved" dependencies = [ - "error-stack 0.5.0", + "error-stack", ] [[package]] @@ -2326,7 +2313,7 @@ dependencies = [ "deadpool-postgres", "derive-where", "dotenv-flow", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "futures-sink", "graph-types", @@ -2361,7 +2348,7 @@ dependencies = [ "axum 0.7.5", "axum-core 0.4.3", "bytes", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "graph", "graph-type-defs", "graph-types", @@ -2417,7 +2404,7 @@ name = "graph-integration" version = "0.0.0" dependencies = [ "authorization", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "graph", "graph-test-data", "graph-types", @@ -2453,7 +2440,7 @@ version = "0.0.0" dependencies = [ "bytes", "codec", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "graph-test-data", "postgres-types", @@ -2505,7 +2492,7 @@ dependencies = [ "bytes", "bytes-utils", "codec", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "futures-core", "futures-io", @@ -2540,7 +2527,7 @@ name = "harpc-tower" version = "0.0.0" dependencies = [ "bytes", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "futures-core", "harpc-net", @@ -2572,7 +2559,7 @@ version = "0.0.0" dependencies = [ "bytes", "enumflags2", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "expect-test", "harpc-types", "proptest", @@ -2590,7 +2577,7 @@ dependencies = [ "clap", "clap_complete", "codec", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "graph", "graph-api", @@ -2617,7 +2604,7 @@ dependencies = [ "authorization", "bytes", "derive-where", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "graph-types", "postgres-types", "serde", @@ -2643,7 +2630,7 @@ version = "0.0.0" dependencies = [ "clap", "clap_builder", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "opentelemetry 0.23.0", "opentelemetry-otlp", "opentelemetry_sdk 0.23.0", @@ -2824,7 +2811,7 @@ dependencies = [ "anstyle", "anstyle-yansi", "ariadne", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "hql-span", "jsonptr", "serde", @@ -5436,7 +5423,7 @@ dependencies = [ "clap", "clap_complete", "criterion", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "inferno", "serde", "serde_json", @@ -6448,7 +6435,7 @@ dependencies = [ name = "temporal-client" version = "0.0.0" dependencies = [ - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "graph-types", "serde", "serde_json", @@ -6640,7 +6627,7 @@ dependencies = [ "authorization", "axum 0.7.5", "codec", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "graph", "graph-api", @@ -7287,7 +7274,7 @@ dependencies = [ "codec", "console_error_panic_hook", "email_address", - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "graph-test-data", "iso8601-duration", @@ -7515,7 +7502,7 @@ dependencies = [ name = "validation" version = "0.0.0" dependencies = [ - "error-stack 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-stack", "futures", "graph-test-data", "graph-types", diff --git a/Cargo.toml b/Cargo.toml index e200dc834f4..3b0c2b1cab7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ type-system.path = "libs/@blockprotocol/type-system/rust" validation.path = "libs/@local/hash-validation" # Pinned workspace members -error-stack = { version = "=0.5.0", default-features = false } +error-stack = { path = "./libs/error-stack", default-features = false } # Public dependencies ahash = { version = "=0.8.11", default-features = false } diff --git a/apps/hash-graph/libs/api/src/rest/data_type.rs b/apps/hash-graph/libs/api/src/rest/data_type.rs index 5cfdc4e873c..06b278658db 100644 --- a/apps/hash-graph/libs/api/src/rest/data_type.rs +++ b/apps/hash-graph/libs/api/src/rest/data_type.rs @@ -444,7 +444,9 @@ where actor_id, // Manually deserialize the query from a JSON value to allow borrowed deserialization // and better error reporting. - GetDataTypesParams::deserialize(&request).map_err(report_to_response)?, + GetDataTypesParams::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?, ) .await .map_err(report_to_response) @@ -508,7 +510,9 @@ where actor_id, // Manually deserialize the query from a JSON value to allow borrowed deserialization // and better error reporting. - GetDataTypeSubgraphParams::deserialize(&request).map_err(report_to_response)?, + GetDataTypeSubgraphParams::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?, ) .await .map_err(report_to_response) diff --git a/apps/hash-graph/libs/api/src/rest/entity.rs b/apps/hash-graph/libs/api/src/rest/entity.rs index dafe9b6452e..e12c341b9ee 100644 --- a/apps/hash-graph/libs/api/src/rest/entity.rs +++ b/apps/hash-graph/libs/api/src/rest/entity.rs @@ -259,7 +259,9 @@ where S: StorePool + Send + Sync, A: AuthorizationApiPool + Send + Sync, { - let params = CreateEntityRequest::deserialize(&body).map_err(report_to_response)?; + let params = CreateEntityRequest::deserialize(&body) + .map_err(Report::from) + .map_err(report_to_response)?; let authorization_api = authorization_api_pool .acquire() @@ -309,7 +311,9 @@ where S: StorePool + Send + Sync, A: AuthorizationApiPool + Send + Sync, { - let params = Vec::::deserialize(&body).map_err(report_to_response)?; + let params = Vec::::deserialize(&body) + .map_err(Report::from) + .map_err(report_to_response)?; let authorization_api = authorization_api_pool .acquire() @@ -359,7 +363,9 @@ where S: StorePool + Send + Sync, A: AuthorizationApiPool + Send + Sync, { - let params = ValidateEntityParams::deserialize(&body).map_err(report_to_response)?; + let params = ValidateEntityParams::deserialize(&body) + .map_err(Report::from) + .map_err(report_to_response)?; let authorization_api = authorization_api_pool .acquire() @@ -587,7 +593,9 @@ where .await .map_err(report_to_response)?; - let request = GetEntitiesRequest::deserialize(&request).map_err(report_to_response)?; + let request = GetEntitiesRequest::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?; store .get_entities( actor_id, @@ -723,7 +731,9 @@ where .await .map_err(report_to_response)?; - let request = GetEntitySubgraphRequest::deserialize(&request).map_err(report_to_response)?; + let request = GetEntitySubgraphRequest::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?; store .get_entity_subgraph( actor_id, @@ -809,7 +819,9 @@ where store .count_entities( actor_id, - CountEntitiesParams::deserialize(&request).map_err(report_to_response)?, + CountEntitiesParams::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?, ) .await .map(Json) diff --git a/apps/hash-graph/libs/api/src/rest/entity_type.rs b/apps/hash-graph/libs/api/src/rest/entity_type.rs index b6787952e27..0067d8e29a2 100644 --- a/apps/hash-graph/libs/api/src/rest/entity_type.rs +++ b/apps/hash-graph/libs/api/src/rest/entity_type.rs @@ -736,7 +736,9 @@ where actor_id, // Manually deserialize the query from a JSON value to allow borrowed deserialization // and better error reporting. - GetEntityTypesParams::deserialize(&request).map_err(report_to_response)?, + GetEntityTypesParams::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?, ) .await .map_err(report_to_response) @@ -804,7 +806,9 @@ where store .get_entity_type_subgraph( actor_id, - GetEntityTypeSubgraphParams::deserialize(&request).map_err(report_to_response)?, + GetEntityTypeSubgraphParams::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?, ) .await .map_err(report_to_response) diff --git a/apps/hash-graph/libs/api/src/rest/property_type.rs b/apps/hash-graph/libs/api/src/rest/property_type.rs index cee6104d47e..3bbf1cd2adb 100644 --- a/apps/hash-graph/libs/api/src/rest/property_type.rs +++ b/apps/hash-graph/libs/api/src/rest/property_type.rs @@ -433,7 +433,9 @@ where actor_id, // Manually deserialize the query from a JSON value to allow borrowed deserialization // and better error reporting. - GetPropertyTypesParams::deserialize(&request).map_err(report_to_response)?, + GetPropertyTypesParams::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?, ) .await .map_err(report_to_response) @@ -499,7 +501,9 @@ where store .get_property_type_subgraph( actor_id, - GetPropertyTypeSubgraphParams::deserialize(&request).map_err(report_to_response)?, + GetPropertyTypeSubgraphParams::deserialize(&request) + .map_err(Report::from) + .map_err(report_to_response)?, ) .await .map_err(report_to_response) diff --git a/apps/hash-graph/libs/api/src/rest/status.rs b/apps/hash-graph/libs/api/src/rest/status.rs index 125db22287e..9a0d5644990 100644 --- a/apps/hash-graph/libs/api/src/rest/status.rs +++ b/apps/hash-graph/libs/api/src/rest/status.rs @@ -25,7 +25,7 @@ where response } -pub(crate) fn report_to_response(report: impl Into>) -> Response +pub(crate) fn report_to_response(report: impl Into>) -> Response where C: Context, { diff --git a/apps/hash-graph/libs/graph/Cargo.toml b/apps/hash-graph/libs/graph/Cargo.toml index f8aca88c8b5..f8a85154a64 100644 --- a/apps/hash-graph/libs/graph/Cargo.toml +++ b/apps/hash-graph/libs/graph/Cargo.toml @@ -26,7 +26,7 @@ tokio-postgres = { workspace = true, public = true } # Private workspace dependencies codec = { workspace = true } -error-stack = { workspace = true, features = ["std", "serde"] } +error-stack = { workspace = true, features = ["std", "serde", "unstable"] } graph-types = { workspace = true, features = ["postgres"] } hash-status = { workspace = true } temporal-versioning = { workspace = true, features = ["postgres"] } diff --git a/apps/hash-graph/libs/graph/src/store/postgres/knowledge/entity/mod.rs b/apps/hash-graph/libs/graph/src/store/postgres/knowledge/entity/mod.rs index 1a547f07808..97ab340694d 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/knowledge/entity/mod.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/knowledge/entity/mod.rs @@ -13,7 +13,7 @@ use authorization::{ zanzibar::{Consistency, Zookie}, AuthorizationApi, }; -use error_stack::{bail, Report, Result, ResultExt}; +use error_stack::{bail, Report, ReportSink, Result, ResultExt}; use futures::TryStreamExt; use graph_types::{ account::{AccountId, CreatedById, EditionArchivedById, EditionCreatedById}, @@ -933,7 +933,9 @@ where } let commit_result = transaction.commit().await.change_context(InsertionError); - if let Err(mut error) = commit_result { + if let Err(error) = commit_result { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_entity_relations(relationships.into_iter().map( @@ -948,12 +950,10 @@ where .await .change_context(InsertionError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(InsertionError)) } else { if let Some(temporal_client) = &self.temporal_client { temporal_client @@ -977,7 +977,7 @@ where consistency: Consistency<'_>, params: Vec>, ) -> Result<(), ValidateEntityError> { - let mut status: Result<(), validation::EntityValidationError> = Ok(()); + let mut status = ReportSink::new(); let validator_provider = StoreProvider { store: self, @@ -999,11 +999,7 @@ where if schema.schemas.is_empty() { let error = Report::new(validation::EntityValidationError::EmptyEntityTypes); - if let Err(ref mut report) = status { - report.extend_one(error); - } else { - status = Err(error); - } + status.add(error); }; let pre_process_result = EntityPreprocessor { @@ -1017,11 +1013,7 @@ where .await .change_context(validation::EntityValidationError::InvalidProperties); if let Err(error) = pre_process_result { - if let Err(ref mut report) = status { - report.extend_one(error); - } else { - status = Err(error); - } + status.add(error); } if let Err(error) = params @@ -1030,15 +1022,12 @@ where .validate(&schema, params.components, &validator_provider) .await { - if let Err(ref mut report) = status { - report.extend_one(error); - } else { - status = Err(error); - } + status.add(error); } } status + .finish() .change_context(ValidateEntityError) .attach(StatusCode::InvalidArgument) } diff --git a/apps/hash-graph/libs/graph/src/store/postgres/mod.rs b/apps/hash-graph/libs/graph/src/store/postgres/mod.rs index 72e2bfc937a..f8a641ac126 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/mod.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/mod.rs @@ -1082,11 +1082,13 @@ impl AccountStore for PostgresStore { .await .change_context(AccountGroupInsertionError)?; - if let Err(mut error) = transaction + if let Err(error) = transaction .commit() .await .change_context(AccountGroupInsertionError) { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_account_group_relations([( @@ -1100,12 +1102,10 @@ impl AccountStore for PostgresStore { .await .change_context(AccountGroupInsertionError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(AccountGroupInsertionError)) } else { Ok(()) } @@ -1184,7 +1184,9 @@ impl AccountStore for PostgresStore { .await .change_context(WebInsertionError)?; - if let Err(mut error) = transaction.commit().await.change_context(WebInsertionError) { + if let Err(error) = transaction.commit().await.change_context(WebInsertionError) { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_web_relations(relationships.into_iter().map(|relation_and_subject| { @@ -1197,12 +1199,10 @@ impl AccountStore for PostgresStore { .await .change_context(WebInsertionError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(WebInsertionError)) } else { Ok(()) } diff --git a/apps/hash-graph/libs/graph/src/store/postgres/ontology/data_type.rs b/apps/hash-graph/libs/graph/src/store/postgres/ontology/data_type.rs index 50c3b3e823d..eb1e4e13317 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/ontology/data_type.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/ontology/data_type.rs @@ -547,7 +547,9 @@ where .await .change_context(InsertionError)?; - if let Err(mut error) = transaction.commit().await.change_context(InsertionError) { + if let Err(error) = transaction.commit().await.change_context(InsertionError) { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_data_type_relations(relationships.into_iter().map( @@ -562,12 +564,10 @@ where .await .change_context(InsertionError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(InsertionError)) } else { if let Some(temporal_client) = &self.temporal_client { temporal_client @@ -919,7 +919,9 @@ where .await .change_context(UpdateError)?; - if let Err(mut error) = transaction.commit().await.change_context(UpdateError) { + if let Err(error) = transaction.commit().await.change_context(UpdateError) { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_data_type_relations(relationships.into_iter().map(|relation_and_subject| { @@ -932,12 +934,10 @@ where .await .change_context(UpdateError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(UpdateError)) } else { let metadata = DataTypeMetadata { record_id: OntologyTypeRecordId::from(params.schema.id.clone()), diff --git a/apps/hash-graph/libs/graph/src/store/postgres/ontology/entity_type.rs b/apps/hash-graph/libs/graph/src/store/postgres/ontology/entity_type.rs index 6bc28b960a0..74f86b43b8f 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/ontology/entity_type.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/ontology/entity_type.rs @@ -723,7 +723,9 @@ where .await .change_context(InsertionError)?; - if let Err(mut error) = transaction.commit().await.change_context(InsertionError) { + if let Err(error) = transaction.commit().await.change_context(InsertionError) { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_entity_type_relations(relationships.into_iter().map( @@ -738,12 +740,10 @@ where .await .change_context(InsertionError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(InsertionError)) } else { if let Some(temporal_client) = &self.temporal_client { temporal_client @@ -1029,7 +1029,9 @@ where .await .change_context(UpdateError)?; - if let Err(mut error) = transaction.commit().await.change_context(UpdateError) { + if let Err(error) = transaction.commit().await.change_context(UpdateError) { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_entity_type_relations(relationships.into_iter().map( @@ -1044,12 +1046,10 @@ where .await .change_context(UpdateError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(UpdateError)) } else { let metadata = EntityTypeMetadata { record_id: metadata.record_id, diff --git a/apps/hash-graph/libs/graph/src/store/postgres/ontology/property_type.rs b/apps/hash-graph/libs/graph/src/store/postgres/ontology/property_type.rs index 2330feac599..78151fc182a 100644 --- a/apps/hash-graph/libs/graph/src/store/postgres/ontology/property_type.rs +++ b/apps/hash-graph/libs/graph/src/store/postgres/ontology/property_type.rs @@ -490,7 +490,9 @@ where .await .change_context(InsertionError)?; - if let Err(mut error) = transaction.commit().await.change_context(InsertionError) { + if let Err(error) = transaction.commit().await.change_context(InsertionError) { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_property_type_relations(relationships.into_iter().map( @@ -505,12 +507,10 @@ where .await .change_context(InsertionError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(InsertionError)) } else { if let Some(temporal_client) = &self.temporal_client { temporal_client @@ -767,7 +767,9 @@ where .await .change_context(UpdateError)?; - if let Err(mut error) = transaction.commit().await.change_context(UpdateError) { + if let Err(error) = transaction.commit().await.change_context(UpdateError) { + let mut error = error.expand(); + if let Err(auth_error) = self .authorization_api .modify_property_type_relations(relationships.into_iter().map( @@ -782,12 +784,10 @@ where .await .change_context(UpdateError) { - // TODO: Use `add_child` - // see https://linear.app/hash/issue/GEN-105/add-ability-to-add-child-errors - error.extend_one(auth_error); + error.push(auth_error); } - Err(error) + Err(error.change_context(UpdateError)) } else { let metadata = PropertyTypeMetadata { record_id: OntologyTypeRecordId::from(params.schema.id.clone()), diff --git a/libs/@blockprotocol/type-system/rust/Cargo.toml b/libs/@blockprotocol/type-system/rust/Cargo.toml index 9391f3c12bf..26a2dddd457 100644 --- a/libs/@blockprotocol/type-system/rust/Cargo.toml +++ b/libs/@blockprotocol/type-system/rust/Cargo.toml @@ -16,7 +16,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] # Public workspace dependencies -error-stack = { workspace = true, public = true } +error-stack = { workspace = true, public = true, features = ["unstable"]} # Public third-party dependencies bytes = { workspace = true, public = true } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/array.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/array.rs index 0d26e045ba4..ba3da4a7c46 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/array.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/array.rs @@ -1,21 +1,18 @@ -use error_stack::Report; +use error_stack::ReportSink; use serde_json::Value as JsonValue; -use super::{extend_report, ConstraintError}; +use super::ConstraintError; use crate::schema::{DataType, JsonSchemaValueType}; pub(crate) fn check_array_constraints( _actual: &[JsonValue], data_type: &DataType, - result: &mut Result<(), Report>, + result: &mut ReportSink, ) { if !data_type.json_type.contains(&JsonSchemaValueType::Array) { - extend_report!( - *result, - ConstraintError::InvalidType { - actual: JsonSchemaValueType::Array, - expected: data_type.json_type.clone() - } - ); + result.capture(ConstraintError::InvalidType { + actual: JsonSchemaValueType::Array, + expected: data_type.json_type.clone(), + }); } } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/boolean.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/boolean.rs index 5131565332e..e118e7ed606 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/boolean.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/boolean.rs @@ -1,20 +1,17 @@ -use error_stack::Report; +use error_stack::ReportSink; -use super::{extend_report, ConstraintError}; +use super::ConstraintError; use crate::schema::{DataType, JsonSchemaValueType}; pub(crate) fn check_boolean_constraints( _actual: bool, data_type: &DataType, - result: &mut Result<(), Report>, + result: &mut ReportSink, ) { if !data_type.json_type.contains(&JsonSchemaValueType::Boolean) { - extend_report!( - *result, - ConstraintError::InvalidType { - actual: JsonSchemaValueType::Boolean, - expected: data_type.json_type.clone() - } - ); + result.capture(ConstraintError::InvalidType { + actual: JsonSchemaValueType::Boolean, + expected: data_type.json_type.clone(), + }); } } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/mod.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/mod.rs index c06bfe39365..4d6331e5d3c 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/mod.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/mod.rs @@ -1,15 +1,3 @@ -macro_rules! extend_report { - ($status:expr, $error:expr $(,)?) => { - if let Err(ref mut report) = $status { - report.extend_one(error_stack::report!($error)) - } else { - $status = Err(error_stack::report!($error)) - } - }; -} - -pub(crate) use extend_report; - mod array; mod boolean; mod error; diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/null.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/null.rs index ca400530992..5a34f4e471f 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/null.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/null.rs @@ -1,19 +1,13 @@ -use error_stack::Report; +use error_stack::ReportSink; -use super::{extend_report, ConstraintError}; +use super::ConstraintError; use crate::schema::{DataType, JsonSchemaValueType}; -pub(crate) fn check_null_constraints( - data_type: &DataType, - result: &mut Result<(), Report>, -) { +pub(crate) fn check_null_constraints(data_type: &DataType, sink: &mut ReportSink) { if !data_type.json_type.contains(&JsonSchemaValueType::Null) { - extend_report!( - *result, - ConstraintError::InvalidType { - actual: JsonSchemaValueType::Null, - expected: data_type.json_type.clone() - } - ); + sink.capture(ConstraintError::InvalidType { + actual: JsonSchemaValueType::Null, + expected: data_type.json_type.clone(), + }); } } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/number.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/number.rs index da92bba737c..204ac9bad0f 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/number.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/number.rs @@ -1,47 +1,38 @@ -use error_stack::Report; +use error_stack::ReportSink; -use super::{extend_report, ConstraintError}; +use super::ConstraintError; use crate::schema::{DataType, JsonSchemaValueType}; pub(crate) fn check_numeric_constraints( actual: f64, data_type: &DataType, - result: &mut Result<(), Report>, + sink: &mut ReportSink, ) { if !data_type.json_type.contains(&JsonSchemaValueType::Number) && !data_type.json_type.contains(&JsonSchemaValueType::Integer) { - extend_report!( - *result, - ConstraintError::InvalidType { - actual: JsonSchemaValueType::Number, - expected: data_type.json_type.clone() - } - ); + sink.capture(ConstraintError::InvalidType { + actual: JsonSchemaValueType::Number, + expected: data_type.json_type.clone(), + }); } if let Some(expected) = data_type.minimum { if data_type.exclusive_minimum { if actual <= expected { - extend_report!( - *result, - ConstraintError::ExclusiveMinimum { actual, expected } - ); + sink.capture(ConstraintError::ExclusiveMinimum { actual, expected }); } } else if actual < expected { - extend_report!(*result, ConstraintError::Minimum { actual, expected }); + sink.capture(ConstraintError::Minimum { actual, expected }); } } if let Some(expected) = data_type.maximum { if data_type.exclusive_maximum { if actual >= expected { - extend_report!( - *result, - ConstraintError::ExclusiveMaximum { actual, expected } - ); + sink.capture(ConstraintError::ExclusiveMaximum { actual, expected }); } } else if actual > expected { - extend_report!(*result, ConstraintError::Maximum { actual, expected }); + sink.capture(ConstraintError::Maximum { actual, expected }); } } @@ -57,7 +48,7 @@ pub(crate) fn check_numeric_constraints( reason = "Validation requires floating point arithmetic" )] if actual % expected >= f64::EPSILON { - extend_report!(*result, ConstraintError::MultipleOf { actual, expected }); + sink.capture(ConstraintError::MultipleOf { actual, expected }); } } } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/object.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/object.rs index e60fd454cfd..44756d9e566 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/object.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/object.rs @@ -1,6 +1,6 @@ -use error_stack::Report; +use error_stack::ReportSink; -use super::{extend_report, ConstraintError}; +use super::ConstraintError; use crate::schema::{DataType, JsonSchemaValueType}; type JsonObject = serde_json::Map; @@ -8,15 +8,12 @@ type JsonObject = serde_json::Map; pub(crate) fn check_object_constraints( _actual: &JsonObject, data_type: &DataType, - result: &mut Result<(), Report>, + sink: &mut ReportSink, ) { if !data_type.json_type.contains(&JsonSchemaValueType::Object) { - extend_report!( - *result, - ConstraintError::InvalidType { - actual: JsonSchemaValueType::Object, - expected: data_type.json_type.clone() - } - ); + sink.capture(ConstraintError::InvalidType { + actual: JsonSchemaValueType::Object, + expected: data_type.json_type.clone(), + }); } } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/string.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/string.rs index b198a04910d..909372a84d2 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/string.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/constraint/string.rs @@ -5,14 +5,14 @@ use core::{ use std::sync::OnceLock; use email_address::EmailAddress; -use error_stack::Report; +use error_stack::{Report, ReportSink}; use iso8601_duration::Duration; use regex::Regex; use serde::{Deserialize, Serialize}; use url::{Host, Url}; use uuid::Uuid; -use super::{extend_report, ConstraintError}; +use super::ConstraintError; use crate::schema::{ data_type::constraint::error::StringFormatError, DataType, JsonSchemaValueType, }; @@ -128,60 +128,45 @@ impl StringFormat { pub(crate) fn check_string_constraints( actual: &str, data_type: &DataType, - result: &mut Result<(), Report>, + sink: &mut ReportSink, ) { if !data_type.json_type.contains(&JsonSchemaValueType::String) { - extend_report!( - *result, - ConstraintError::InvalidType { - actual: JsonSchemaValueType::String, - expected: data_type.json_type.clone() - } - ); + sink.capture(ConstraintError::InvalidType { + actual: JsonSchemaValueType::String, + expected: data_type.json_type.clone(), + }); } if let Some(expected) = data_type.min_length { if actual.len() < expected { - extend_report!( - *result, - ConstraintError::MinLength { - actual: actual.to_owned(), - expected - } - ); + sink.capture(ConstraintError::MinLength { + actual: actual.to_owned(), + expected, + }); } } if let Some(expected) = data_type.max_length { if actual.len() > expected { - extend_report!( - *result, - ConstraintError::MaxLength { - actual: actual.to_owned(), - expected - } - ); + sink.capture(ConstraintError::MaxLength { + actual: actual.to_owned(), + expected, + }); } } if let Some(expected) = &data_type.pattern { if !expected.is_match(actual) { - extend_report!( - *result, - ConstraintError::Pattern { - actual: actual.to_owned(), - expected: expected.clone() - } - ); + sink.capture(ConstraintError::Pattern { + actual: actual.to_owned(), + expected: expected.clone(), + }); } } if let Some(expected) = data_type.format { if let Err(error) = expected.validate(actual) { - extend_report!( - *result, - error.change_context(ConstraintError::Format { - actual: actual.to_owned(), - expected - }) - ); + sink.add(error.change_context(ConstraintError::Format { + actual: actual.to_owned(), + expected, + })); } } } diff --git a/libs/@blockprotocol/type-system/rust/src/schema/data_type/mod.rs b/libs/@blockprotocol/type-system/rust/src/schema/data_type/mod.rs index 09d4257dd4e..f2979f65193 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/data_type/mod.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/data_type/mod.rs @@ -20,14 +20,14 @@ use alloc::sync::Arc; use core::{cmp, fmt, mem}; use std::collections::{hash_map::RawEntryMut, HashMap, HashSet}; -use error_stack::{bail, Report}; +use error_stack::{bail, Report, ReportSink}; use regex::Regex; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use thiserror::Error; use crate::{ - schema::data_type::constraint::{extend_report, ConstraintError, StringFormat}, + schema::data_type::constraint::{ConstraintError, StringFormat}, url::VersionedUrl, }; @@ -520,61 +520,52 @@ impl DataType { /// # Errors /// /// Returns an error if the JSON value is not a valid instance of the data type. - pub fn validate_constraints(&self, value: &JsonValue) -> Result<(), Report> { - let mut result = Ok::<(), Report>(()); + pub fn validate_constraints(&self, value: &JsonValue) -> Result<(), Report<[ConstraintError]>> { + let mut sink = ReportSink::new(); if let Some(const_value) = &self.const_value { if value != const_value { - extend_report!( - result, - ConstraintError::Const { - actual: value.clone(), - expected: const_value.clone() - } - ); + sink.capture(ConstraintError::Const { + actual: value.clone(), + expected: const_value.clone(), + }); } } if !self.enum_values.is_empty() && !self.enum_values.contains(value) { - extend_report!( - result, - ConstraintError::Enum { - actual: value.clone(), - expected: self.enum_values.clone() - } - ); + sink.capture(ConstraintError::Enum { + actual: value.clone(), + expected: self.enum_values.clone(), + }); } match value { JsonValue::Null => { - constraint::check_null_constraints(self, &mut result); + constraint::check_null_constraints(self, &mut sink); } JsonValue::Bool(boolean) => { - constraint::check_boolean_constraints(*boolean, self, &mut result); + constraint::check_boolean_constraints(*boolean, self, &mut sink); } JsonValue::Number(number) => { if let Some(number) = number.as_f64() { - constraint::check_numeric_constraints(number, self, &mut result); + constraint::check_numeric_constraints(number, self, &mut sink); } else { - extend_report!( - result, - ConstraintError::InsufficientPrecision { - actual: number.clone() - } - ); + sink.capture(ConstraintError::InsufficientPrecision { + actual: number.clone(), + }); } } JsonValue::String(string) => { - constraint::check_string_constraints(string, self, &mut result); + constraint::check_string_constraints(string, self, &mut sink); } JsonValue::Array(array) => { - constraint::check_array_constraints(array, self, &mut result); + constraint::check_array_constraints(array, self, &mut sink); } JsonValue::Object(object) => { - constraint::check_object_constraints(object, self, &mut result); + constraint::check_object_constraints(object, self, &mut sink); } } - result + sink.finish() } } diff --git a/libs/@local/hash-authorization/Cargo.toml b/libs/@local/hash-authorization/Cargo.toml index 59e18245f23..bf2007142c2 100644 --- a/libs/@local/hash-authorization/Cargo.toml +++ b/libs/@local/hash-authorization/Cargo.toml @@ -17,7 +17,7 @@ futures-core = { workspace = true, public = true } # Private workspace dependencies codec = { workspace = true } -error-stack = { workspace = true } +error-stack = { workspace = true, features = ["unstable"]} # Private third-party dependencies derive-where = { workspace = true } diff --git a/libs/@local/hash-authorization/src/zanzibar/api.rs b/libs/@local/hash-authorization/src/zanzibar/api.rs index dfa99689461..372a29a74e8 100644 --- a/libs/@local/hash-authorization/src/zanzibar/api.rs +++ b/libs/@local/hash-authorization/src/zanzibar/api.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{ReportSink, Result, ResultExt}; use futures::{Stream, TryStreamExt}; use graph_types::{ account::{AccountGroupId, AccountId}, @@ -15,8 +15,7 @@ use crate::{ BulkCheckItem, BulkCheckResponse, CheckError, CheckResponse, DeleteRelationshipError, DeleteRelationshipResponse, ExportSchemaError, ExportSchemaResponse, ImportSchemaError, ImportSchemaResponse, ModifyRelationError, ModifyRelationshipError, - ModifyRelationshipOperation, ModifyRelationshipResponse, ReadError, RpcError, - ZanzibarBackend, + ModifyRelationshipOperation, ModifyRelationshipResponse, ReadError, ZanzibarBackend, }, schema::{ AccountGroupPermission, AccountGroupRelationAndSubject, DataTypePermission, @@ -235,7 +234,9 @@ where consistency, ) .await?; - let mut status = Ok::<(), Report>(()); + + let mut status = ReportSink::new(); + let permissions = response .permissions .into_iter() @@ -243,11 +244,8 @@ where let permission = match item.has_permission { Ok(permissionship) => permissionship, Err(error) => { - if let Err(report) = &mut status { - report.extend_one(Report::new(error)); - } else { - status = Err(Report::new(error)); - } + status.capture(error); + return None; } }; @@ -256,8 +254,8 @@ where .collect(); status + .finish_with(|| (permissions, response.checked_at)) .change_context(CheckError) - .map(|()| (permissions, response.checked_at)) } #[tracing::instrument(level = "info", skip(self))] @@ -332,7 +330,9 @@ where consistency, ) .await?; - let mut status = Ok::<(), Report>(()); + + let mut status = ReportSink::new(); + let permissions = response .permissions .into_iter() @@ -340,11 +340,8 @@ where let permission = match item.has_permission { Ok(permissionship) => permissionship, Err(error) => { - if let Err(report) = &mut status { - report.extend_one(Report::new(error)); - } else { - status = Err(Report::new(error)); - } + status.capture(error); + return None; } }; @@ -353,8 +350,8 @@ where .collect(); status + .finish_with(|| (permissions, response.checked_at)) .change_context(CheckError) - .map(|()| (permissions, response.checked_at)) } #[tracing::instrument(level = "info", skip(self))] @@ -431,7 +428,9 @@ where consistency, ) .await?; - let mut status = Ok::<(), Report>(()); + + let mut status = ReportSink::new(); + let permissions = response .permissions .into_iter() @@ -439,11 +438,8 @@ where let permission = match item.has_permission { Ok(permissionship) => permissionship, Err(error) => { - if let Err(report) = &mut status { - report.extend_one(Report::new(error)); - } else { - status = Err(Report::new(error)); - } + status.capture(error); + return None; } }; @@ -452,8 +448,8 @@ where .collect(); status + .finish_with(|| (permissions, response.checked_at)) .change_context(CheckError) - .map(|()| (permissions, response.checked_at)) } #[tracing::instrument(level = "info", skip(self))] @@ -528,7 +524,9 @@ where consistency, ) .await?; - let mut status = Ok::<(), Report>(()); + + let mut status = ReportSink::new(); + let permissions = response .permissions .into_iter() @@ -536,11 +534,8 @@ where let permission = match item.has_permission { Ok(permissionship) => permissionship, Err(error) => { - if let Err(report) = &mut status { - report.extend_one(Report::new(error)); - } else { - status = Err(Report::new(error)); - } + status.capture(error); + return None; } }; @@ -549,8 +544,8 @@ where .collect(); status + .finish_with(|| (permissions, response.checked_at)) .change_context(CheckError) - .map(|()| (permissions, response.checked_at)) } #[tracing::instrument(level = "info", skip(self))] diff --git a/libs/@local/hash-graph-types/rust/src/knowledge/property/visitor.rs b/libs/@local/hash-graph-types/rust/src/knowledge/property/visitor.rs index 0fe4e594171..42cf4e5ec3a 100644 --- a/libs/@local/hash-graph-types/rust/src/knowledge/property/visitor.rs +++ b/libs/@local/hash-graph-types/rust/src/knowledge/property/visitor.rs @@ -1,6 +1,6 @@ use core::{borrow::Borrow, future::Future}; -use error_stack::{bail, Report, ResultExt}; +use error_stack::{bail, Report, ReportSink, ResultExt}; use serde_json::Value as JsonValue; use type_system::{ schema::{ @@ -94,7 +94,7 @@ pub trait EntityVisitor: Sized + Send + Sync { value: &mut JsonValue, metadata: &mut ValueMetadata, type_provider: &P, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where P: DataTypeProvider + Sync, { @@ -109,7 +109,7 @@ pub trait EntityVisitor: Sized + Send + Sync { schema: &PropertyType, property: &mut PropertyWithMetadata, type_provider: &P, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where P: DataTypeProvider + PropertyTypeProvider + Sync, { @@ -124,7 +124,7 @@ pub trait EntityVisitor: Sized + Send + Sync { schema: &ArraySchema, array: &mut PropertyWithMetadataArray, type_provider: &P, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where T: PropertyValueSchema + Sync, P: DataTypeProvider + PropertyTypeProvider + Sync, @@ -140,7 +140,7 @@ pub trait EntityVisitor: Sized + Send + Sync { schema: &T, object: &mut PropertyWithMetadataObject, type_provider: &P, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where T: PropertyObjectSchema> + Sync, P: DataTypeProvider + PropertyTypeProvider + Sync, @@ -156,7 +156,7 @@ pub trait EntityVisitor: Sized + Send + Sync { schema: &[PropertyValues], property: &mut PropertyWithMetadataValue, type_provider: &P, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where P: DataTypeProvider + Sync, { @@ -171,7 +171,7 @@ pub trait EntityVisitor: Sized + Send + Sync { schema: &[PropertyValues], array: &mut PropertyWithMetadataArray, type_provider: &P, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where P: DataTypeProvider + PropertyTypeProvider + Sync, { @@ -186,7 +186,7 @@ pub trait EntityVisitor: Sized + Send + Sync { schema: &[PropertyValues], object: &mut PropertyWithMetadataObject, type_provider: &P, - ) -> impl Future>> + Send + ) -> impl Future>> + Send where P: DataTypeProvider + PropertyTypeProvider + Sync, { @@ -194,16 +194,6 @@ pub trait EntityVisitor: Sized + Send + Sync { } } -macro_rules! extend_report { - ($status:ident, $error:expr $(,)?) => { - if let Err(ref mut report) = $status { - report.extend_one(error_stack::report!($error)) - } else { - $status = Err(error_stack::report!($error)) - } - }; -} - /// Walks through a JSON value using the provided schema. /// /// For all referenced data types [`EntityVisitor::visit_value`] is called. @@ -217,12 +207,13 @@ pub async fn walk_value( value: &mut JsonValue, metadata: &mut ValueMetadata, type_provider: &P, -) -> Result<(), Report> +) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, P: DataTypeProvider + Sync, { - let mut status = Ok::<_, Report>(()); + let mut status = ReportSink::new(); + for parent in &data_type.schema.all_of { match type_provider .provide_type(&parent.url) @@ -234,16 +225,18 @@ where .visit_value(parent.borrow(), value, metadata, type_provider) .await { - extend_report!(status, error); + status.add(error); } } Err(error) => { - extend_report!(status, error); + status.add(error); + continue; } } } - status + + status.finish() } /// Walks through a property using the provided schema. @@ -259,7 +252,7 @@ pub async fn walk_property( schema: &PropertyType, property: &mut PropertyWithMetadata, type_provider: &P, -) -> Result<(), Report> +) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, P: DataTypeProvider + PropertyTypeProvider + Sync, @@ -296,13 +289,14 @@ pub async fn walk_array( schema: &ArraySchema, array: &mut PropertyWithMetadataArray, type_provider: &P, -) -> Result<(), Report> +) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, S: PropertyValueSchema + Sync, P: DataTypeProvider + PropertyTypeProvider + Sync, { - let mut status = Ok::<_, Report>(()); + let mut status = ReportSink::new(); + for property in &mut array.value { match property { PropertyWithMetadata::Value(value) => { @@ -310,7 +304,7 @@ where .visit_one_of_property(schema.items.possibilities(), value, type_provider) .await { - extend_report!(status, error); + status.add(error); } } PropertyWithMetadata::Array(array) => { @@ -318,7 +312,7 @@ where .visit_one_of_array(schema.items.possibilities(), array, type_provider) .await { - extend_report!(status, error); + status.add(error); } } PropertyWithMetadata::Object(object) => { @@ -326,13 +320,13 @@ where .visit_one_of_object(schema.items.possibilities(), object, type_provider) .await { - extend_report!(status, error); + status.add(error); } } } } - status + status.finish() } /// Walks through a property object using the provided schema. @@ -359,22 +353,20 @@ pub async fn walk_object( schema: &S, object: &mut PropertyWithMetadataObject, type_provider: &P, -) -> Result<(), Report> +) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, S: PropertyObjectSchema> + Sync, P: DataTypeProvider + PropertyTypeProvider + Sync, { - let mut status = Ok::<_, Report>(()); + let mut status = ReportSink::new(); for (base_url, property) in &mut object.value { let Some(property_type_reference) = schema.properties().get(base_url) else { - extend_report!( - status, - TraversalError::UnexpectedProperty { - key: base_url.clone() - } - ); + status.capture(TraversalError::UnexpectedProperty { + key: base_url.clone(), + }); + continue; }; @@ -416,19 +408,20 @@ where ) .await; if let Err(error) = result { - extend_report!(status, error); + status.add(error); } } PropertyWithMetadata::Object { .. } | PropertyWithMetadata::Value(_) => { - bail!(TraversalError::InvalidType { + bail![TraversalError::InvalidType { actual: property.json_type(), expected: JsonSchemaValueType::Array, - }) + },] } }, }; } - status + + status.finish() } /// Walks through a property value using the provided schema list. @@ -448,12 +441,12 @@ pub async fn walk_one_of_property_value( schema: &[PropertyValues], property: &mut PropertyWithMetadataValue, type_provider: &P, -) -> Result<(), Report> +) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, P: DataTypeProvider + Sync, { - let mut status: Result<(), Report> = Ok(()); + let mut status = ReportSink::new(); let mut passed: usize = 0; for schema in schema { @@ -474,41 +467,32 @@ where ) .await { - extend_report!(status, error); + status.add(error); } else { passed += 1; } } PropertyValues::ArrayOfPropertyValues(_) => { - extend_report!( - status, - TraversalError::ExpectedValue { - actual: JsonSchemaValueType::Array, - } - ); + status.capture(TraversalError::ExpectedValue { + actual: JsonSchemaValueType::Array, + }); } PropertyValues::PropertyTypeObject(_) => { - extend_report!( - status, - TraversalError::ExpectedValue { - actual: JsonSchemaValueType::Object, - } - ); + status.capture(TraversalError::ExpectedValue { + actual: JsonSchemaValueType::Object, + }); } } } match passed { - 0 => status, + 0 => status.finish(), 1 => Ok(()), _ => { - extend_report!( - status, - TraversalError::AmbiguousProperty { - actual: PropertyWithMetadata::Value(property.clone()), - } - ); - status + status.capture(TraversalError::AmbiguousProperty { + actual: PropertyWithMetadata::Value(property.clone()), + }); + status.finish() } } } @@ -528,55 +512,47 @@ pub async fn walk_one_of_array( schema: &[PropertyValues], array: &mut PropertyWithMetadataArray, type_provider: &P, -) -> Result<(), Report> +) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, P: DataTypeProvider + PropertyTypeProvider + Sync, { - let mut status: Result<(), Report> = Ok(()); + let mut status = ReportSink::new(); let mut passed: usize = 0; for schema in schema { match schema { PropertyValues::DataTypeReference(_) => { - extend_report!( - status, - TraversalError::ExpectedValue { - actual: JsonSchemaValueType::Array, - } - ); + status.capture(TraversalError::ExpectedValue { + actual: JsonSchemaValueType::Array, + }); } PropertyValues::ArrayOfPropertyValues(array_schema) => { if let Err(error) = Box::pin(visitor.visit_array(array_schema, array, type_provider)).await { - extend_report!(status, error); + status.add(error); } else { passed += 1; } } PropertyValues::PropertyTypeObject(_) => { - extend_report!( - status, - TraversalError::ExpectedValue { - actual: JsonSchemaValueType::Object, - } - ); + status.capture(TraversalError::ExpectedValue { + actual: JsonSchemaValueType::Object, + }); } } } match passed { - 0 => status, + 0 => status.finish(), 1 => Ok(()), _ => { - extend_report!( - status, - TraversalError::AmbiguousProperty { - actual: PropertyWithMetadata::Array(array.clone()), - } - ); - status + status.capture(TraversalError::AmbiguousProperty { + actual: PropertyWithMetadata::Array(array.clone()), + }); + + status.finish() } } } @@ -596,37 +572,31 @@ pub async fn walk_one_of_object( schema: &[PropertyValues], object: &mut PropertyWithMetadataObject, type_provider: &P, -) -> Result<(), Report> +) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, P: DataTypeProvider + PropertyTypeProvider + Sync, { - let mut status: Result<(), Report> = Ok(()); + let mut status = ReportSink::new(); let mut passed: usize = 0; for schema in schema { match schema { PropertyValues::DataTypeReference(_) => { - extend_report!( - status, - TraversalError::ExpectedValue { - actual: JsonSchemaValueType::Array, - } - ); + status.capture(TraversalError::ExpectedValue { + actual: JsonSchemaValueType::Array, + }); } PropertyValues::ArrayOfPropertyValues(_) => { - extend_report!( - status, - TraversalError::ExpectedValue { - actual: JsonSchemaValueType::Object, - } - ); + status.capture(TraversalError::ExpectedValue { + actual: JsonSchemaValueType::Object, + }); } PropertyValues::PropertyTypeObject(object_schema) => { if let Err(error) = Box::pin(visitor.visit_object(object_schema, object, type_provider)).await { - extend_report!(status, error); + status.add(error); } else { passed += 1; } @@ -635,16 +605,14 @@ where } match passed { - 0 => status, + 0 => status.finish(), 1 => Ok(()), _ => { - extend_report!( - status, - TraversalError::AmbiguousProperty { - actual: PropertyWithMetadata::Object(object.clone()), - } - ); - status + status.capture(TraversalError::AmbiguousProperty { + actual: PropertyWithMetadata::Object(object.clone()), + }); + + status.finish() } } } diff --git a/libs/@local/hash-graph-types/rust/src/ontology/mod.rs b/libs/@local/hash-graph-types/rust/src/ontology/mod.rs index 191a113c811..51ba38414d4 100644 --- a/libs/@local/hash-graph-types/rust/src/ontology/mod.rs +++ b/libs/@local/hash-graph-types/rust/src/ontology/mod.rs @@ -213,7 +213,9 @@ pub trait EntityTypeProvider: OntologyTypeProvider { { stream::iter(type_ids) .then(|entity_type_url| async { - Ok(self.provide_type(entity_type_url).await?.borrow().clone()) + self.provide_type(entity_type_url) + .await + .map(|entity_type| entity_type.borrow().clone()) }) .try_collect::() } diff --git a/libs/@local/hash-validation/src/entity_type.rs b/libs/@local/hash-validation/src/entity_type.rs index 46a3ca9a816..ef95f063037 100644 --- a/libs/@local/hash-validation/src/entity_type.rs +++ b/libs/@local/hash-validation/src/entity_type.rs @@ -1,7 +1,7 @@ use core::borrow::Borrow; use std::collections::{hash_map::RawEntryMut, HashSet}; -use error_stack::{Report, ResultExt}; +use error_stack::{Report, ReportSink, ResultExt}; use futures::{stream, StreamExt, TryStreamExt}; use graph_types::{ knowledge::{ @@ -34,16 +34,6 @@ use type_system::{ use crate::{EntityProvider, Schema, Validate, ValidateEntityComponents}; -macro_rules! extend_report { - ($status:ident, $error:expr $(,)?) => { - if let Err(ref mut report) = $status { - report.extend_one(error_stack::report!($error)) - } else { - $status = Err(error_stack::report!($error)) - } - }; -} - #[derive(Debug, Error)] pub enum EntityValidationError { #[error("The properties of the entity do not match the schema")] @@ -81,12 +71,12 @@ where schema: &ClosedEntityType, components: ValidateEntityComponents, context: &P, - ) -> Result<(), Report> { + ) -> Result<(), Report<[Self::Error]>> { if !components.link_data { return Ok(()); } - let mut status: Result<(), Report> = Ok(()); + let mut status = ReportSink::new(); // TODO: The link type should be a const but the type system crate does not allow // to make this a `const` variable. @@ -102,17 +92,17 @@ where if let Some(link_data) = self { if !is_link { - extend_report!(status, EntityValidationError::UnexpectedLinkData); + status.capture(EntityValidationError::UnexpectedLinkData); } if let Err(error) = schema.validate_value(*link_data, components, context).await { - extend_report!(status, error); + status.add(error); } } else if is_link { - extend_report!(status, EntityValidationError::MissingLinkData); + status.capture(EntityValidationError::MissingLinkData); } - status + status.finish() } } @@ -131,11 +121,11 @@ where schema: &ClosedEntityType, components: ValidateEntityComponents, context: &P, - ) -> Result<(), Report> { - let mut status: Result<(), Report> = Ok(()); + ) -> Result<(), Report<[Self::Error]>> { + let mut status = ReportSink::new(); if self.metadata.entity_type_ids.is_empty() { - extend_report!(status, EntityValidationError::EmptyEntityTypes); + status.capture(EntityValidationError::EmptyEntityTypes); } if let Err(error) = self @@ -144,7 +134,7 @@ where .validate(schema, components, context) .await { - extend_report!(status, error); + status.add(error); } if let Err(error) = self .metadata @@ -152,10 +142,10 @@ where .validate(&self.properties, components, context) .await { - extend_report!(status, error); + status.add(error); } - status + status.finish() } } @@ -172,8 +162,8 @@ where value: &'a LinkData, _: ValidateEntityComponents, provider: &'a P, - ) -> Result<(), Report> { - let mut status: Result<(), Report> = Ok(()); + ) -> Result<(), Report<[EntityValidationError]>> { + let mut status = ReportSink::new(); let left_entity = provider .provide_entity(value.left_entity_id) @@ -250,25 +240,19 @@ where } if !found_match { - extend_report!( - status, - EntityValidationError::InvalidLinkTargetId { - target_types: right_entity_type.schemas.keys().cloned().collect(), - } - ); + status.capture(EntityValidationError::InvalidLinkTargetId { + target_types: right_entity_type.schemas.keys().cloned().collect(), + }); } } if !found_link_target { - extend_report!( - status, - EntityValidationError::InvalidLinkTypeId { - link_types: self.schemas.keys().cloned().collect(), - } - ); + status.capture(EntityValidationError::InvalidLinkTypeId { + link_types: self.schemas.keys().cloned().collect(), + }); } - status + status.finish() } } @@ -285,7 +269,7 @@ impl EntityVisitor for ValueValidator { value: &mut JsonValue, _: &mut ValueMetadata, _: &P, - ) -> Result<(), Report> + ) -> Result<(), Report<[TraversalError]>> where P: DataTypeProvider + Sync, { @@ -293,6 +277,7 @@ impl EntityVisitor for ValueValidator { .schema .validate_constraints(value) .change_context(TraversalError::ConstraintUnfulfilled) + .map_err(Report::expand) } } @@ -303,11 +288,11 @@ impl EntityVisitor for EntityPreprocessor { value: &mut JsonValue, metadata: &mut ValueMetadata, type_provider: &P, - ) -> Result<(), Report> + ) -> Result<(), Report<[TraversalError]>> where P: DataTypeProvider + Sync, { - let mut status: Result<(), Report> = Ok(()); + let mut status = ReportSink::new(); if let Some(data_type_url) = &metadata.data_type_id { if data_type.schema.id != *data_type_url { @@ -321,16 +306,13 @@ impl EntityVisitor for EntityPreprocessor { })?; if !is_compatible { - extend_report!( - status, - TraversalError::InvalidDataType { - actual: data_type_url.clone(), - expected: data_type.schema.id.clone(), - } - ); + status.capture(TraversalError::InvalidDataType { + actual: data_type_url.clone(), + expected: data_type.schema.id.clone(), + }); } - if let Err(err) = type_provider + if let Err(error) = type_provider .provide_type(data_type_url) .await .change_context_lazy(|| TraversalError::DataTypeRetrieval { @@ -343,19 +325,20 @@ impl EntityVisitor for EntityPreprocessor { .validate_constraints(value) .change_context(TraversalError::ConstraintUnfulfilled) { - extend_report!(status, err); + status.capture(error); } } } else { - extend_report!(status, TraversalError::AmbiguousDataType); + status.capture(TraversalError::AmbiguousDataType); } - if let Err(err) = ValueValidator + if let Err(error) = ValueValidator .visit_value(data_type, value, metadata, type_provider) .await { - extend_report!(status, err); + status.add(error); } + walk_value( &mut ValueValidator, data_type, @@ -365,7 +348,7 @@ impl EntityVisitor for EntityPreprocessor { ) .await?; - status + status.finish() } #[expect(clippy::too_many_lines, reason = "Need to refactor this function")] @@ -374,11 +357,11 @@ impl EntityVisitor for EntityPreprocessor { schema: &[PropertyValues], property: &mut PropertyWithMetadataValue, type_provider: &P, - ) -> Result<(), Report> + ) -> Result<(), Report<[TraversalError]>> where P: DataTypeProvider + Sync, { - let mut status = Ok::<_, Report>(()); + let mut status = ReportSink::new(); // We try to infer the data type ID if property.metadata.data_type_id.is_none() { @@ -393,7 +376,7 @@ impl EntityVisitor for EntityPreprocessor { id: data_type_ref.clone(), })?; if has_children { - extend_report!(status, TraversalError::AmbiguousDataType); + status.capture(TraversalError::AmbiguousDataType); possible_data_types.clear(); break; } @@ -406,7 +389,7 @@ impl EntityVisitor for EntityPreprocessor { })?; if !data_type.borrow().schema.all_of.is_empty() { - extend_report!(status, TraversalError::AmbiguousDataType); + status.capture(TraversalError::AmbiguousDataType); possible_data_types.clear(); break; } @@ -451,13 +434,10 @@ impl EntityVisitor for EntityPreprocessor { } property.value = JsonValue::from(value); } else { - extend_report!( - status, - TraversalError::InvalidType { - actual: JsonSchemaValueType::from(&property.value), - expected: JsonSchemaValueType::Number, - } - ); + status.capture(TraversalError::InvalidType { + actual: JsonSchemaValueType::from(&property.value), + expected: JsonSchemaValueType::Number, + }); } } } @@ -494,28 +474,26 @@ impl EntityVisitor for EntityPreprocessor { if f64::abs(current_value - converted_value) > f64::EPSILON { - extend_report!( - status, + status.capture( TraversalError::InvalidCanonicalValue { key: target.clone(), actual: current_value, expected: converted_value, - } + }, ); } } else { - extend_report!( - status, + status.add( Report::new(TraversalError::InvalidType { actual: JsonSchemaValueType::from( - &property.value + &property.value, ), expected: JsonSchemaValueType::Number, }) .attach_printable( "Values other than numbers are not yet \ - supported for conversions" - ) + supported for conversions", + ), ); } } @@ -528,34 +506,33 @@ impl EntityVisitor for EntityPreprocessor { } } } else { - extend_report!( - status, + status.add( Report::new(TraversalError::InvalidType { actual: JsonSchemaValueType::from(&property.value), expected: JsonSchemaValueType::Number, }) .attach_printable( "Values other than numbers are not yet supported for \ - conversions" - ) + conversions", + ), ); } } } - Err(err) => { - extend_report!(status, err); + Err(error) => { + status.add(error); } } } else { - extend_report!(status, TraversalError::AmbiguousDataType); + status.capture(TraversalError::AmbiguousDataType); } if let Err(error) = walk_one_of_property_value(self, schema, property, type_provider).await { - extend_report!(status, error); + status.add(error); } - status + status.finish() } async fn visit_array( @@ -563,39 +540,37 @@ impl EntityVisitor for EntityPreprocessor { schema: &ArraySchema, array: &mut PropertyWithMetadataArray, type_provider: &P, - ) -> Result<(), Report> + ) -> Result<(), Report<[TraversalError]>> where T: PropertyValueSchema + Sync, P: DataTypeProvider + PropertyTypeProvider + Sync, { - let mut status = walk_array(self, schema, array, type_provider).await; + let mut status = ReportSink::new(); + if let Err(error) = walk_array(self, schema, array, type_provider).await { + status.add(error); + } + if self.components.num_items { if let Some(min) = schema.min_items { if array.value.len() < min { - extend_report!( - status, - TraversalError::TooFewItems { - actual: array.value.len(), - min, - }, - ); + status.capture(TraversalError::TooFewItems { + actual: array.value.len(), + min, + }); } } if let Some(max) = schema.max_items { if array.value.len() > max { - extend_report!( - status, - TraversalError::TooManyItems { - actual: array.value.len(), - max, - }, - ); + status.capture(TraversalError::TooManyItems { + actual: array.value.len(), + max, + }); } } } - status + status.finish() } async fn visit_object( @@ -603,27 +578,27 @@ impl EntityVisitor for EntityPreprocessor { schema: &T, object: &mut PropertyWithMetadataObject, type_provider: &P, - ) -> Result<(), Report> + ) -> Result<(), Report<[TraversalError]>> where T: PropertyObjectSchema> + Sync, P: DataTypeProvider + PropertyTypeProvider + Sync, { - let mut status = walk_object(self, schema, object, type_provider).await; + let mut status = ReportSink::new(); + if let Err(error) = walk_object(self, schema, object, type_provider).await { + status.add(error); + } if self.components.required_properties { for required_property in schema.required() { if !object.value.contains_key(required_property) { - extend_report!( - status, - TraversalError::MissingRequiredProperty { - key: required_property.clone(), - } - ); + status.capture(TraversalError::MissingRequiredProperty { + key: required_property.clone(), + }); } } } - status + status.finish() } } diff --git a/libs/@local/hash-validation/src/lib.rs b/libs/@local/hash-validation/src/lib.rs index abea7ee42d2..68355c75164 100644 --- a/libs/@local/hash-validation/src/lib.rs +++ b/libs/@local/hash-validation/src/lib.rs @@ -24,7 +24,7 @@ pub trait Schema { value: &'a V, components: ValidateEntityComponents, provider: &'a P, - ) -> impl Future>> + Send + 'a; + ) -> impl Future>> + Send + 'a; } const fn default_true() -> bool { @@ -80,7 +80,7 @@ pub trait Validate { schema: &S, components: ValidateEntityComponents, context: &C, - ) -> impl Future>> + Send; + ) -> impl Future>> + Send; } pub trait EntityProvider { @@ -318,7 +318,7 @@ mod tests { property_types: impl IntoIterator + Send, data_types: impl IntoIterator + Send, components: ValidateEntityComponents, - ) -> Result> { + ) -> Result> { install_error_stack_hooks(); let provider = Provider::new( @@ -358,7 +358,7 @@ mod tests { property_types: impl IntoIterator + Send, data_types: impl IntoIterator + Send, components: ValidateEntityComponents, - ) -> Result> { + ) -> Result> { install_error_stack_hooks(); let property = Property::deserialize(property).expect("failed to deserialize property"); @@ -388,7 +388,7 @@ mod tests { mut value: JsonValue, data_type: &str, components: ValidateEntityComponents, - ) -> Result> { + ) -> Result> { install_error_stack_hooks(); let provider = Provider::new([], [], [], []); diff --git a/libs/@local/hash-validation/src/property.rs b/libs/@local/hash-validation/src/property.rs index aaf2f4fe496..3f9a287ddd5 100644 --- a/libs/@local/hash-validation/src/property.rs +++ b/libs/@local/hash-validation/src/property.rs @@ -1,4 +1,4 @@ -use error_stack::Report; +use error_stack::{Report, ReportSink}; use graph_types::knowledge::property::{PropertyMetadataObject, PropertyObject}; use crate::{EntityValidationError, Validate, ValidateEntityComponents}; @@ -14,8 +14,8 @@ where _object: &PropertyObject, _components: ValidateEntityComponents, _provider: &P, - ) -> Result<(), Report> { - let status: Result<(), Report> = Ok(()); + ) -> Result<(), Report<[Self::Error]>> { + let status = ReportSink::new(); // TODO: Validate metadata // - Check that all metadata keys are valid see: @@ -24,6 +24,6 @@ where // - see: https://linear.app/hash/issue/H-2800/validate-that-allowed-data-types-are-either-unambiguous-or-a-data-type // - see: https://linear.app/hash/issue/H-2801/validate-data-type-in-entity-property-metadata - status + status.finish() } } diff --git a/libs/@local/hql/diagnostics/src/diagnostic.rs b/libs/@local/hql/diagnostics/src/diagnostic.rs index 26ad910929b..14525ed88a7 100644 --- a/libs/@local/hql/diagnostics/src/diagnostic.rs +++ b/libs/@local/hql/diagnostics/src/diagnostic.rs @@ -4,7 +4,7 @@ use core::{ }; use ariadne::ColorGenerator; -use error_stack::{Report, Result}; +use error_stack::{Report, Result, TryReportIteratorExt, TryReportTupleExt}; use hql_span::{storage::SpanStorage, tree::SpanNode, Span, SpanId}; use crate::{ @@ -60,7 +60,7 @@ impl<'a> Diagnostic<'a, SpanId> { pub fn resolve( self, storage: &SpanStorage, - ) -> Result>, ResolveError> + ) -> Result>, [ResolveError]> where S: Span + Clone, { @@ -73,23 +73,13 @@ impl<'a> Diagnostic<'a, SpanId> { }) .transpose(); - let (span, labels) = self + let labels: Result, _> = self .labels .into_iter() .map(|label| label.resolve(storage)) - .fold(span.map(|node| (node, Vec::new())), |acc, label| { - match (acc, label) { - (Ok((span, mut labels)), Ok(label)) => { - labels.push(label); - Ok((span, labels)) - } - (Err(mut acc), Err(error)) => { - acc.extend_one(error); - Err(acc) - } - (Err(error), _) | (_, Err(error)) => Err(error), - } - })?; + .try_collect_reports(); + + let (span, labels) = (span, labels).try_collect()?; Ok(Diagnostic { category: self.category, diff --git a/libs/deer/Cargo.toml b/libs/deer/Cargo.toml index 1926b1259fe..a92ba5de018 100644 --- a/libs/deer/Cargo.toml +++ b/libs/deer/Cargo.toml @@ -15,7 +15,7 @@ publish = false [dependencies] # Public workspace dependencies -error-stack = { workspace = true, public = true, default-features = false } +error-stack = { workspace = true, public = true, default-features = false, features = ["unstable"]} # Public third-party dependencies erased-serde = { workspace = true, public = true, features = ['alloc'] } diff --git a/libs/deer/json/Cargo.toml b/libs/deer/json/Cargo.toml index eb500a79ec3..ee568f62cef 100644 --- a/libs/deer/json/Cargo.toml +++ b/libs/deer/json/Cargo.toml @@ -21,7 +21,7 @@ deer = { path = "..", public = true, default-features = false } # Public third-party dependencies # Private workspace dependencies -error-stack = { workspace = true, default-features = false } +error-stack = { workspace = true, default-features = false, features = ["unstable"]} # Private third-party dependencies justjson = { workspace = true, features = ["alloc"] } diff --git a/libs/deer/json/src/array.rs b/libs/deer/json/src/array.rs index 02a60d62268..817bc673b79 100644 --- a/libs/deer/json/src/array.rs +++ b/libs/deer/json/src/array.rs @@ -2,12 +2,12 @@ use deer::{ error::{ArrayAccessError, ArrayLengthError, DeserializerError, Error, Variant}, Context, Deserialize, Deserializer as _, }; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, ReportSink, Result, ResultExt}; use justjson::parser::{PeekableTokenKind, Token}; use crate::{ deserializer::Deserializer, - error::{ErrorAccumulator, Position, SyntaxError}, + error::{Position, SyntaxError}, skip::skip_tokens, }; @@ -50,7 +50,7 @@ impl<'de> deer::ArrayAccess<'de> for ArrayAccess<'_, '_, 'de> { where T: Deserialize<'de>, { - let mut errors = ErrorAccumulator::new(); + let mut errors = ReportSink::new(); if self.dirty { // we parse in a way where every subsequent invocation (except the first one) @@ -60,10 +60,14 @@ impl<'de> deer::ArrayAccess<'de> for ArrayAccess<'_, '_, 'de> { // the statement after this _will_ fail and return the visitor, therefore we don't // need to check for EOF if let Err(error) = self.try_skip_comma() { - errors.extend_one(error); + errors.add(error); } } + if let Err(error) = errors.finish() { + return Some(Err(error.change_context(ArrayAccessError))); + } + self.dirty = true; let peek_key = self.deserializer.peek(); diff --git a/libs/deer/json/src/deserializer.rs b/libs/deer/json/src/deserializer.rs index 385c7142875..6110a70db67 100644 --- a/libs/deer/json/src/deserializer.rs +++ b/libs/deer/json/src/deserializer.rs @@ -10,7 +10,7 @@ use deer::{ Context, Deserialize, EnumVisitor, IdentifierVisitor, Number, OptionalVisitor, Reflection, StructVisitor, Visitor, }; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, ReportSink, Result, ResultExt}; use justjson::{ parser::{PeekableTokenKind, Token, Tokenizer}, AnyStr, @@ -19,8 +19,7 @@ use justjson::{ use crate::{ array::ArrayAccess, error::{ - convert_tokenizer_error, BytesUnsupportedError, ErrorAccumulator, Position, - RecursionLimitError, SyntaxError, + convert_tokenizer_error, BytesUnsupportedError, Position, RecursionLimitError, SyntaxError, }, number::try_convert_number, object::ObjectAccess, @@ -373,24 +372,25 @@ impl<'de> deer::Deserializer<'de> for &mut Deserializer<'_, 'de> { let discriminant = result?; - let mut value = if is_map { - let mut errors = ErrorAccumulator::new(); + let value = if is_map { + let mut errors = ReportSink::new(); if let Err(error) = self.try_skip(PeekableTokenKind::Colon, SyntaxError::ExpectedColon) { - errors.extend_one(error); + errors.add(error); } - let errors = errors.into_result().change_context(DeserializerError); + let errors = errors.finish().change_context(DeserializerError); let value = visitor .visit_value(discriminant, &mut *self) .change_context(DeserializerError); // same as folding the tuple in main deer match (value, errors) { - (Err(mut value), Err(errors)) => { - value.extend_one(errors); - Err(value) + (Err(value), Err(errors)) => { + let mut value = value.expand(); + value.push(errors); + Err(value.change_context(DeserializerError)) } (Err(error), Ok(())) | (Ok(_), Err(error)) => Err(error), (Ok(value), Ok(())) => Ok(value), @@ -401,6 +401,8 @@ impl<'de> deer::Deserializer<'de> for &mut Deserializer<'_, 'de> { .change_context(DeserializerError) }; + let mut value = value.map_err(Report::expand); + if is_map { if self.peek() == Some(PeekableTokenKind::ObjectEnd) { // we can safely continue @@ -416,13 +418,13 @@ impl<'de> deer::Deserializer<'de> for &mut Deserializer<'_, 'de> { .change_context(DeserializerError); match &mut value { - Err(value) => value.extend_one(error), - value => *value = Err(error), + Err(value) => value.push(error), + value => *value = Err(error.expand()), } } } - value + value.change_context(DeserializerError) } fn deserialize_struct(self, visitor: V) -> Result diff --git a/libs/deer/json/src/error.rs b/libs/deer/json/src/error.rs index a2ff0c0b455..c1e4999d093 100644 --- a/libs/deer/json/src/error.rs +++ b/libs/deer/json/src/error.rs @@ -266,34 +266,3 @@ pub(crate) fn convert_tokenizer_error(error: &justjson::Error) -> Report { - inner: Option>, -} - -impl ErrorAccumulator { - pub(crate) const fn new() -> Self { - Self { inner: None } - } - - pub(crate) fn extend_one(&mut self, error: Report) { - match &mut self.inner { - Some(inner) => inner.extend_one(error), - inner => *inner = Some(error), - } - } - - pub(crate) fn into_result(self) -> Result<(), Report> { - self.inner.map_or_else(|| Ok(()), Err) - } - - pub(crate) fn extend_existing(self, mut error: Report) -> Report { - if let Some(inner) = self.inner { - error.extend_one(inner); - } - - error - } -} diff --git a/libs/deer/json/src/object.rs b/libs/deer/json/src/object.rs index d58e0e163ad..fb0fb255df1 100644 --- a/libs/deer/json/src/object.rs +++ b/libs/deer/json/src/object.rs @@ -2,12 +2,12 @@ use deer::{ error::{DeserializerError, Error, ObjectAccessError, ObjectLengthError, Variant}, Context, Deserializer as _, FieldVisitor, }; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, ReportSink, Result, ResultExt}; use justjson::parser::{PeekableTokenKind, Token}; use crate::{ deserializer::Deserializer, - error::{ErrorAccumulator, Position, Span, SyntaxError}, + error::{Position, Span, SyntaxError}, skip::skip_tokens, }; @@ -60,7 +60,7 @@ impl<'de> deer::ObjectAccess<'de> for ObjectAccess<'_, '_, 'de> { where F: FieldVisitor<'de>, { - let mut errors = ErrorAccumulator::new(); + let mut errors = ReportSink::new(); if self.dirty { // we parse in a way where every subsequent invocation (except the first one) @@ -70,7 +70,7 @@ impl<'de> deer::ObjectAccess<'de> for ObjectAccess<'_, '_, 'de> { // the statement after this _will_ fail and return the visitor, therefore we don't // need to check for EOF if let Err(error) = self.try_skip_comma() { - errors.extend_one(error); + errors.add(error); } } @@ -91,17 +91,20 @@ impl<'de> deer::ObjectAccess<'de> for ObjectAccess<'_, '_, 'de> { let span = self.deserializer.skip(); // skip key if let Err(skip) = self.try_skip_colon() { - errors.extend_one(skip); + errors.add(skip); } self.deserializer.skip(); // skip value - let error = errors.extend_existing( + errors.add( Report::new(SyntaxError::ObjectKeyMustBeString.into_error()) .attach(Span::new(span)), ); - return Ok(Err(error.change_context(ObjectAccessError))); + return Ok(Err(errors + .finish() + .change_context(ObjectAccessError) + .expect_err("infallible"))); } let key = visitor.visit_key(&mut *self.deserializer); @@ -109,12 +112,13 @@ impl<'de> deer::ObjectAccess<'de> for ObjectAccess<'_, '_, 'de> { let result = match key { Ok(key) => visitor .visit_value(key, &mut *self.deserializer) - .change_context(ObjectAccessError), + .change_context(ObjectAccessError) + .map_err(Report::expand), Err(error) => { - let mut error = error.change_context(ObjectAccessError); + let mut error = error.change_context(ObjectAccessError).expand(); if let Err(skip) = self.try_skip_colon() { - error.extend_one(skip.change_context(ObjectAccessError)); + error.push(skip.change_context(ObjectAccessError)); } self.deserializer.skip(); // skip value @@ -126,18 +130,16 @@ impl<'de> deer::ObjectAccess<'de> for ObjectAccess<'_, '_, 'de> { // key value are separated by `:`, if one forgets we will still error out but _try_ to // deserialize if let Err(skip) = self.try_skip_colon() { - errors.extend_one(skip); + errors.add(skip); } // same as `(result, errors).into_result()` - let result = match ( - result, - errors.into_result().change_context(ObjectAccessError), - ) { - (Err(error), Ok(())) | (Ok(_), Err(error)) => Err(error), + let result = match (result, errors.finish().change_context(ObjectAccessError)) { + (Err(error), Ok(())) => Err(error.change_context(ObjectAccessError)), + (Ok(_), Err(error)) => Err(error), (Err(mut result), Err(errors)) => { - result.extend_one(errors); - Err(result) + result.push(errors); + Err(result.change_context(ObjectAccessError)) } (Ok(result), Ok(())) => Ok(result), }; diff --git a/libs/deer/src/bound.rs b/libs/deer/src/bound.rs index f002f836744..10cee84b1ae 100644 --- a/libs/deer/src/bound.rs +++ b/libs/deer/src/bound.rs @@ -94,19 +94,19 @@ where } fn end(self) -> Result<(), ObjectAccessError> { - let mut result = self.access.end(); + let mut result = self.access.end().map_err(Report::expand); if self.remaining > 0 { let error = Report::new(BoundedContractViolationError::EndRemainingItems.into_error()) .change_context(ObjectAccessError); match &mut result { - Err(result) => result.extend_one(error), - result => *result = Err(error), + Err(result) => result.push(error), + result => *result = Err(error.expand()), } } - result + result.change_context(ObjectAccessError) } } @@ -182,18 +182,18 @@ where } fn end(self) -> Result<(), ArrayAccessError> { - let mut result = self.access.end(); + let mut result = self.access.end().map_err(Report::expand); if self.remaining > 0 { let error = Report::new(BoundedContractViolationError::EndRemainingItems.into_error()) .change_context(ArrayAccessError); match &mut result { - Err(result) => result.extend_one(error), - result => *result = Err(error), + Err(result) => result.push(error), + result => *result = Err(error.expand()), } } - result + result.change_context(ArrayAccessError) } } diff --git a/libs/deer/src/error/mod.rs b/libs/deer/src/error/mod.rs index b9447b13944..93b62f38166 100644 --- a/libs/deer/src/error/mod.rs +++ b/libs/deer/src/error/mod.rs @@ -203,7 +203,7 @@ pub trait ErrorProperties { fn value<'a>(stack: &[&'a Frame]) -> Self::Value<'a>; - fn output(value: Self::Value<'_>, map: &mut S) -> Result<(), SerdeSerializeError> + fn output(value: Self::Value<'_>, map: &mut S) -> Result<(), [SerdeSerializeError]> where S: SerializeMap; } @@ -223,7 +223,7 @@ impl ErrorProperties for T { ::value(stack) } - fn output(value: Self::Value<'_>, map: &mut S) -> Result<(), SerdeSerializeError> + fn output(value: Self::Value<'_>, map: &mut S) -> Result<(), [SerdeSerializeError]> where S: SerializeMap, { @@ -231,7 +231,7 @@ impl ErrorProperties for T { Ok(map .serialize_entry(key, &value) - .map_err(|err| SerdeSerializeError::new(&err))?) + .map_err(|err| Report::new(SerdeSerializeError::new(&err)))?) } } @@ -469,21 +469,12 @@ pub trait ReportExt { impl ReportExt for Report { fn export(self) -> Export { - Export::new(self) + Export::new(self.expand()) } } -pub(crate) trait ResultExtPrivate { - fn extend_one(&mut self, error: Report); -} - -impl ResultExtPrivate for Result { - fn extend_one(&mut self, error: Report) { - match self { - Err(errors) => { - errors.extend_one(error); - } - errors => *errors = Err(error), - } +impl ReportExt for Report<[C]> { + fn export(self) -> Export { + Export::new(self) } } diff --git a/libs/deer/src/error/serialize.rs b/libs/deer/src/error/serialize.rs index adf2490b210..fee80432a97 100644 --- a/libs/deer/src/error/serialize.rs +++ b/libs/deer/src/error/serialize.rs @@ -134,7 +134,7 @@ struct FrameSplitIterator<'a> { } impl<'a> FrameSplitIterator<'a> { - fn new(report: &'a Report) -> Self { + fn new(report: &'a Report<[impl Context]>) -> Self { let stack = report .current_frames() .iter() @@ -198,7 +198,7 @@ fn divide_frames<'a>( } fn serialize_report( - report: &Report, + report: &Report<[impl Context]>, serializer: S, ) -> Result { let frames = FrameSplitIterator::new(report); @@ -254,10 +254,10 @@ pub(super) fn impl_serialize<'a, E: Variant>( /// /// These types can then be used to generate a personalized message and will be attached to /// `properties` with the predefined key. -pub struct Export(Report); +pub struct Export(Report<[C]>); impl Export { - pub(crate) const fn new(report: Report) -> Self { + pub(crate) const fn new(report: Report<[C]>) -> Self { Self(report) } } @@ -347,17 +347,17 @@ mod tests { fn split() { let report_f = Report::new(Root).attach_printable(Printable("F")); let report_e = Report::new(Root).attach_printable(Printable("E")); - let mut report_d = Report::new(Root).attach_printable(Printable("D")); + let mut report_d = Report::new(Root).attach_printable(Printable("D")).expand(); - report_d.extend_one(report_e); + report_d.push(report_e); let mut report_b = report_d.attach_printable(Printable("B")); let report_c = report_f.attach_printable(Printable("C")); let report_g = Report::new(Root).attach_printable(Printable("G")); - report_b.extend_one(report_c); - report_b.extend_one(report_g); + report_b.push(report_c); + report_b.push(report_g); let report_a = report_b.attach_printable(Printable("A")); @@ -436,13 +436,14 @@ mod tests { let mut report_d = Report::new(Error::new(ErrorZ)) .attach_printable(Printable("D")) .change_context(Error::new(ErrorZ)) - .change_context(ErrorY); + .change_context(ErrorY) + .expand(); let report_e = Report::new(ErrorY) .change_context(Error::new(ErrorZ)) .attach_printable(Printable("E")) .change_context(ErrorY); - report_d.extend_one(report_e); + report_d.push(report_e); let mut report_b = report_d.attach_printable(Printable("B")); @@ -458,9 +459,9 @@ mod tests { let report_h = Report::new(ErrorY).attach_printable(Printable("H")); - report_b.extend_one(report_c); - report_b.extend_one(report_g); - report_b.extend_one(report_h); + report_b.push(report_c); + report_b.push(report_g); + report_b.push(report_h); let report_a = report_b.attach_printable(Printable("A")); @@ -488,7 +489,7 @@ mod tests { #[test] fn divide_ignore() { - let report = Report::new(ErrorY).attach(Printable("A")); + let report = Report::new(ErrorY).attach(Printable("A")).expand(); let split = FrameSplitIterator::new(&report); let frames = divide_frames(split); @@ -502,7 +503,8 @@ mod tests { .attach_printable(Printable("B")) .change_context(Error::new(ErrorZ)) .attach_printable(Printable("C")) - .attach_printable(Printable("D")); + .attach_printable(Printable("D")) + .expand(); let split = FrameSplitIterator::new(&report); let mut frames = divide_frames(split).into_iter(); @@ -560,7 +562,8 @@ mod tests { let mut missing = Report::new(Error::new(MissingError)) .attach(ExpectedType::new(StringSchema::document())) .attach(Location::Field("b")) - .change_context(VisitorError); + .change_context(VisitorError) + .expand(); let value = Report::new(Error::new(ValueError)) .attach(ReceivedValue::new(256_u16)) @@ -568,7 +571,7 @@ mod tests { .attach(Location::Field("a")) .change_context(VisitorError); - missing.extend_one(value); + missing.push(value); let report = missing.attach(Location::Array(0)); diff --git a/libs/deer/src/error/tuple.rs b/libs/deer/src/error/tuple.rs index 1fdde0196f5..c881745d8d0 100644 --- a/libs/deer/src/error/tuple.rs +++ b/libs/deer/src/error/tuple.rs @@ -81,18 +81,18 @@ macro_rules! properties { fn output( value: Self::Value<'_>, map: &mut S, - ) -> error_stack::Result<(), SerdeSerializeError> + ) -> error_stack::Result<(), [SerdeSerializeError]> where S: SerializeMap, { let ($($elem,)*) = value; - let mut errors: Option> = None; + let mut errors: Option> = None; $( if let Err(error) = $elem::output($elem, map) { match &mut errors { Some(errors) => { - errors.extend_one(error); + errors.append(error); } errors => *errors = Some(error), } @@ -112,7 +112,7 @@ impl ErrorProperties for () { fn value<'a>(_: &[&'a Frame]) -> Self::Value<'a> {} - fn output((): Self::Value<'_>, _: &mut S) -> error_stack::Result<(), SerdeSerializeError> + fn output((): Self::Value<'_>, _: &mut S) -> error_stack::Result<(), [SerdeSerializeError]> where S: SerializeMap, { diff --git a/libs/deer/src/ext.rs b/libs/deer/src/ext.rs deleted file mode 100644 index ede4a6120bf..00000000000 --- a/libs/deer/src/ext.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Temporary helper trait for folding reports until [#2377](https://github.com/hashintel/hash/discussions/2377) -//! is resolved and implemented. - -use error_stack::{Context, Report}; - -pub(crate) trait TupleExt { - type Context: Context; - type Ok; - - fn fold_reports(self) -> Result>; -} - -#[rustfmt::skip] -macro_rules! all_the_tuples { - ($name:ident) => { - $name!([T1], T2); - $name!([T1, T2], T3); - $name!([T1, T2, T3], T4); - $name!([T1, T2, T3, T4], T5); - $name!([T1, T2, T3, T4, T5], T6); - $name!([T1, T2, T3, T4, T5, T6], T7); - $name!([T1, T2, T3, T4, T5, T6, T7], T8); - $name!([T1, T2, T3, T4, T5, T6, T7, T8], T9); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16); - }; -} - -impl TupleExt for (Result>,) { - type Context = C; - type Ok = (T1,); - - fn fold_reports(self) -> Result> { - self.0.map(|value| (value,)) - } -} - -macro_rules! impl_tuple_ext { - ([$($elem:ident),*], $other:ident) => { - #[expect(non_snake_case)] - impl TupleExt for ($(Result<$elem, Report>, )* Result<$other, Report>) { - type Context = C; - type Ok = ($($elem ,)* $other); - - fn fold_reports(self) -> Result> { - let ( $($elem ,)* $other ) = self; - - let lhs = ( $($elem ,)* ).fold_reports(); - - match (lhs, $other) { - (Ok(( $($elem ,)* )), Ok(rhs)) => Ok(($($elem ,)* rhs)), - (Ok(_), Err(err)) | (Err(err), Ok(_)) => Err(err), - (Err(mut lhs), Err(rhs)) => { - lhs.extend_one(rhs); - - Err(lhs) - } - } - } - } - }; -} - -all_the_tuples!(impl_tuple_ext); diff --git a/libs/deer/src/helpers.rs b/libs/deer/src/helpers.rs index 35e815ebea1..56ce79f4f1c 100644 --- a/libs/deer/src/helpers.rs +++ b/libs/deer/src/helpers.rs @@ -1,9 +1,8 @@ -use error_stack::{Result, ResultExt}; +use error_stack::{Result, ResultExt, TryReportTupleExt}; use serde::{ser::SerializeMap, Serialize, Serializer}; use crate::{ error::{DeserializeError, VisitorError}, - ext::TupleExt, schema::Reference, Deserialize, Deserializer, Document, EnumVisitor, FieldVisitor, ObjectAccess, Reflection, Schema, Visitor, @@ -75,7 +74,7 @@ where let end = object.end(); (value, end) - .fold_reports() + .try_collect() .map(|(value, ())| value) .change_context(VisitorError) } diff --git a/libs/deer/src/impls/core/array.rs b/libs/deer/src/impls/core/array.rs index 2e23cec3934..0b58ffbe1ba 100644 --- a/libs/deer/src/impls/core/array.rs +++ b/libs/deer/src/impls/core/array.rs @@ -1,6 +1,6 @@ use core::{marker::PhantomData, mem, mem::MaybeUninit, ptr}; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, ReportSink, Result, ResultExt}; use crate::{ error::{ @@ -26,7 +26,7 @@ impl<'de, T: Deserialize<'de>, const N: usize> Visitor<'de> for ArrayVisitor<'de let mut array = array.into_bound(N).change_context(VisitorError)?; let size_hint = array.size_hint(); - let mut result: Result<(), ArrayAccessError> = Ok(()); + let mut result = ReportSink::new(); #[expect(unsafe_code)] // SAFETY: `uninit_assumed_init` is fine here, as `[MaybeUninit; N]` as no inhabitants, @@ -60,10 +60,7 @@ impl<'de, T: Deserialize<'de>, const N: usize> Visitor<'de> for ArrayVisitor<'de Some(Err(error)) => { let error = error.attach(Location::Array(index)); - match &mut result { - Err(result) => result.extend_one(error), - result => *result = Err(error), - } + result.add(error); failed = true; } @@ -71,10 +68,7 @@ impl<'de, T: Deserialize<'de>, const N: usize> Visitor<'de> for ArrayVisitor<'de } if let Err(error) = array.end() { - match &mut result { - Err(result) => result.extend_one(error), - result => *result = Err(error), - } + result.add(error); } if let Some(size_hint) = size_hint { @@ -87,13 +81,12 @@ impl<'de, T: Deserialize<'de>, const N: usize> Visitor<'de> for ArrayVisitor<'de .change_context(ArrayAccessError); // we received less items, which means we can emit another error - match &mut result { - Err(result) => result.extend_one(error), - result => *result = Err(error), - } + result.add(error); } } + let result = result.finish(); + // we do not need to check if we have enough items, as `set_bounded` guarantees that we // visit exactly `N` times and `v.end()` ensures that there aren't too many items. if result.is_err() { diff --git a/libs/deer/src/impls/core/ops.rs b/libs/deer/src/impls/core/ops.rs index 9e627a40de4..02e627505a4 100644 --- a/libs/deer/src/impls/core/ops.rs +++ b/libs/deer/src/impls/core/ops.rs @@ -3,14 +3,13 @@ use core::{ ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, }; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, ReportSink, Result, ResultExt, TryReportTupleExt}; use crate::{ error::{ - ArrayAccessError, DeserializeError, DuplicateField, DuplicateFieldError, Location, - ObjectAccessError, ResultExtPrivate, Variant, VisitorError, + ArrayAccessError, DeserializeError, DuplicateField, DuplicateFieldError, Location, Variant, + VisitorError, }, - ext::TupleExt, helpers::Properties, identifier, impls::UnitVariantVisitor, @@ -208,7 +207,7 @@ where .attach(Location::Tuple(1)); let (start, end, ()) = (start, end, array.end()) - .fold_reports() + .try_collect() .change_context(VisitorError)?; Ok((start, end)) @@ -221,14 +220,14 @@ where let mut start: Option = None; let mut end: Option = None; - let mut errors: Result<(), ObjectAccessError> = Ok(()); + let mut errors = ReportSink::new(); while let Some(field) = object.field(RangeFieldVisitor { start: &mut start, end: &mut end, }) { if let Err(error) = field { - errors.extend_one(error); + errors.add(error); } } @@ -253,10 +252,11 @@ where let (start, end, ..) = ( start, end, - errors.change_context(VisitorError), + errors.finish().change_context(VisitorError), object.end().change_context(VisitorError), ) - .fold_reports()?; + .try_collect() + .change_context(VisitorError)?; Ok((start, end)) } diff --git a/libs/deer/src/impls/core/tuples.rs b/libs/deer/src/impls/core/tuples.rs index cadbb700402..c3a49084fec 100644 --- a/libs/deer/src/impls/core/tuples.rs +++ b/libs/deer/src/impls/core/tuples.rs @@ -1,13 +1,12 @@ use core::marker::PhantomData; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, Result, ResultExt, TryReportTupleExt}; use crate::{ error::{ ArrayLengthError, DeserializeError, ExpectedLength, Location, ReceivedLength, Variant, VisitorError, }, - ext::TupleExt, ArrayAccess, Deserialize, Deserializer, Document, Reflection, Schema, Visitor, }; @@ -88,10 +87,10 @@ macro_rules! impl_tuple { length += 1; )* - let value = ($($elem,)*).fold_reports(); + let value = ($($elem,)*).try_collect(); (value, array.end()) - .fold_reports() + .try_collect() .map(|(value, ())| value) .change_context(VisitorError) } diff --git a/libs/deer/src/lib.rs b/libs/deer/src/lib.rs index 3e07175dbf2..2838e0de56f 100644 --- a/libs/deer/src/lib.rs +++ b/libs/deer/src/lib.rs @@ -40,7 +40,6 @@ mod impls; #[macro_use] mod macros; mod bound; -mod ext; pub mod helpers; mod number; pub mod schema; diff --git a/libs/deer/src/value/mod.rs b/libs/deer/src/value/mod.rs index 1b728cb3025..d758ba4a3ae 100644 --- a/libs/deer/src/value/mod.rs +++ b/libs/deer/src/value/mod.rs @@ -196,11 +196,9 @@ macro_rules! deserialize_identifier { where V: IdentifierVisitor<'de>, { - let value = self - .value - .try_into() + let value = TryFrom::try_from(self.value) .map_err(Report::new) - .change_context(TypeError.into_error()) + .map_err(|error| error.change_context(TypeError.into_error())) .attach(ExpectedType::new(visitor.expecting())) .attach(ReceivedType::new(<$primitive>::document())) .change_context(DeserializerError)?; diff --git a/libs/deer/src/value/object.rs b/libs/deer/src/value/object.rs index be9dee22c22..520da379fbc 100644 --- a/libs/deer/src/value/object.rs +++ b/libs/deer/src/value/object.rs @@ -1,11 +1,10 @@ -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, Result, ResultExt, TryReportTupleExt}; use crate::{ error::{ DeserializerError, ExpectedLength, ExpectedType, ObjectLengthError, ReceivedLength, ReceivedType, TypeError, Variant, VisitorError, }, - ext::TupleExt, schema::visitor::ObjectSchema, Context, Deserializer, EnumVisitor, FieldVisitor, IdentifierVisitor, ObjectAccess, OptionalVisitor, Reflection, StructVisitor, Visitor, @@ -108,7 +107,7 @@ where }; let (value, ()) = (value, access.end()) - .fold_reports() + .try_collect() .change_context(DeserializerError)?; Ok(value) diff --git a/libs/deer/tests/common.rs b/libs/deer/tests/common.rs index 7341cb22251..375fd4d3647 100644 --- a/libs/deer/tests/common.rs +++ b/libs/deer/tests/common.rs @@ -1,70 +1,2 @@ //! Temporary helper trait for folding reports until [#2377](https://github.com/hashintel/hash/discussions/2377) //! is resolved and implemented. - -use error_stack::{Context, Report}; - -#[allow(dead_code, reason = "Not used in all tests")] -pub(crate) trait TupleExt { - type Context: Context; - type Ok; - - fn fold_reports(self) -> Result>; -} - -#[rustfmt::skip] -macro_rules! all_the_tuples { - ($name:ident) => { - $name!([T1], T2); - $name!([T1, T2], T3); - $name!([T1, T2, T3], T4); - $name!([T1, T2, T3, T4], T5); - $name!([T1, T2, T3, T4, T5], T6); - $name!([T1, T2, T3, T4, T5, T6], T7); - $name!([T1, T2, T3, T4, T5, T6, T7], T8); - $name!([T1, T2, T3, T4, T5, T6, T7, T8], T9); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15); - $name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16); - }; -} - -impl TupleExt for (Result>,) { - type Context = C; - type Ok = (T1,); - - fn fold_reports(self) -> Result> { - self.0.map(|value| (value,)) - } -} - -macro_rules! impl_tuple_ext { - ([$($elem:ident),*], $other:ident) => { - #[expect(non_snake_case)] - impl TupleExt for ($(Result<$elem, Report>, )* Result<$other, Report>) { - type Context = C; - type Ok = ($($elem ,)* $other); - - fn fold_reports(self) -> Result> { - let ( $($elem ,)* $other ) = self; - - let lhs = ( $($elem ,)* ).fold_reports(); - - match (lhs, $other) { - (Ok(( $($elem ,)* )), Ok(rhs)) => Ok(($($elem ,)* rhs)), - (Ok(_), Err(err)) | (Err(err), Ok(_)) => Err(err), - (Err(mut lhs), Err(rhs)) => { - lhs.extend_one(rhs); - - Err(lhs) - } - } - } - } - }; -} - -all_the_tuples!(impl_tuple_ext); diff --git a/libs/deer/tests/test_bound.rs b/libs/deer/tests/test_bound.rs index 2e6d8ed474e..8ec7eb2db94 100644 --- a/libs/deer/tests/test_bound.rs +++ b/libs/deer/tests/test_bound.rs @@ -2,12 +2,12 @@ #![expect(clippy::min_ident_chars, reason = "Simplifies test cases")] use deer::{ - error::{ArrayAccessError, DeserializeError, ObjectAccessError, VisitorError}, + error::{DeserializeError, VisitorError}, schema::Reference, ArrayAccess, Deserialize, Deserializer, Document, ObjectAccess, Reflection, Schema, Visitor, }; use deer_desert::{assert_tokens, assert_tokens_error, error, Token}; -use error_stack::{Result, ResultExt}; +use error_stack::{ReportSink, Result, ResultExt}; use serde::{ser::SerializeMap, Serialize, Serializer}; use serde_json::json; @@ -62,7 +62,7 @@ impl<'de> Visitor<'de> for ArrayStatsVisitor { let mut array = array.into_bound(3).change_context(VisitorError)?; let mut stats = ArrayStats { total: 0, some: 0 }; - let mut errors: Result<(), ArrayAccessError> = Ok(()); + let mut errors = ReportSink::new(); while let Some(value) = array.next::>() { match value { @@ -73,21 +73,19 @@ impl<'de> Visitor<'de> for ArrayStatsVisitor { stats.some += 1; } } - Err(error) => match &mut errors { - Err(errors) => errors.extend_one(error), - errors => *errors = Err(error), - }, + Err(error) => { + errors.add(error); + } } } let error = array.end(); - match (errors, error) { - (Err(errors), Ok(())) | (Ok(()), Err(errors)) => { - Err(errors.change_context(VisitorError)) - } + match (errors.finish(), error) { + (Err(errors), Ok(())) => Err(errors.change_context(VisitorError)), + (Ok(()), Err(errors)) => Err(errors.change_context(VisitorError)), (Err(mut errors), Err(error)) => { - errors.extend_one(error); + errors.push(error); Err(errors.change_context(VisitorError)) } @@ -276,7 +274,7 @@ impl<'de> Visitor<'de> for ObjectStatsVisitor { none: 0, }; - let mut errors: Result<(), ObjectAccessError> = Ok(()); + let mut errors = ReportSink::new(); while let Some(value) = object.next::, Option<()>>() { match value { @@ -293,21 +291,19 @@ impl<'de> Visitor<'de> for ObjectStatsVisitor { None => stats.none += 1, } } - Err(error) => match &mut errors { - Err(errors) => errors.extend_one(error), - errors => *errors = Err(error), - }, + Err(error) => { + errors.add(error); + } } } let error = object.end(); - match (errors, error) { - (Err(errors), Ok(())) | (Ok(()), Err(errors)) => { - Err(errors.change_context(VisitorError)) - } + match (errors.finish(), error) { + (Err(errors), Ok(())) => Err(errors.change_context(VisitorError)), + (Ok(()), Err(errors)) => Err(errors.change_context(VisitorError)), (Err(mut errors), Err(error)) => { - errors.extend_one(error); + errors.push(error); Err(errors.change_context(VisitorError)) } diff --git a/libs/deer/tests/test_enum_visitor.rs b/libs/deer/tests/test_enum_visitor.rs index 75e3cdff21b..c80551b21ea 100644 --- a/libs/deer/tests/test_enum_visitor.rs +++ b/libs/deer/tests/test_enum_visitor.rs @@ -10,7 +10,7 @@ use deer::{ Schema, Visitor, }; use deer_desert::{assert_tokens, assert_tokens_error, error, Token}; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, ReportSink, Result, ResultExt}; use serde::{ser::SerializeMap, Serializer}; use serde_json::json; @@ -361,7 +361,7 @@ impl<'de> Visitor<'de> for StructEnumVisitor { let mut object = object.into_bound(1).change_context(VisitorError)?; let mut id = None; - let mut errors: Result<(), VisitorError> = Ok(()); + let mut errors = ReportSink::new(); match object.field(VariantFieldAccess) { None => { @@ -371,16 +371,12 @@ impl<'de> Visitor<'de> for StructEnumVisitor { .attach(ExpectedLength::new(1)) .change_context(VisitorError); - match &mut errors { - Ok(()) => errors = Err(error), - Err(errors) => errors.extend_one(error), - } + errors.add(error); } Some(Ok(VariantField::Id(value))) => id = Some(value), - Some(Err(error)) => match &mut errors { - Ok(()) => errors = Err(error.change_context(VisitorError)), - Err(errors) => errors.extend_one(error.change_context(VisitorError)), - }, + Some(Err(error)) => { + errors.add(error.change_context(VisitorError)); + } } if id.is_none() { @@ -388,13 +384,10 @@ impl<'de> Visitor<'de> for StructEnumVisitor { .attach(Location::Field("id")) .attach(ExpectedType::new(u8::reflection())); - match &mut errors { - Ok(()) => errors = Err(error.change_context(VisitorError)), - Err(errors) => errors.extend_one(error.change_context(VisitorError)), - } + errors.add(error.change_context(VisitorError)); } - errors?; + errors.finish().change_context(VisitorError)?; object.end().change_context(VisitorError)?; diff --git a/libs/deer/tests/test_struct_visitor.rs b/libs/deer/tests/test_struct_visitor.rs index f49475cb2ce..c15bd36511a 100644 --- a/libs/deer/tests/test_struct_visitor.rs +++ b/libs/deer/tests/test_struct_visitor.rs @@ -5,12 +5,11 @@ use deer::{ ArrayAccess, Deserialize, Deserializer, Document, FieldVisitor, ObjectAccess, Reflection, Schema, StructVisitor, Visitor, }; -use error_stack::{Report, Result, ResultExt}; +use error_stack::{Report, ReportSink, Result, ResultExt, TryReportTupleExt}; use serde_json::json; mod common; -use common::TupleExt; use deer::{ error::{ExpectedField, Location, ObjectAccessError, ReceivedField, UnknownFieldError}, helpers::Properties, @@ -214,7 +213,7 @@ impl<'de> StructVisitor<'de> for ExampleVisitor { .attach(Location::Tuple(2)); let (a, b, c, ()) = (a, b, c, array.end()) - .fold_reports() + .try_collect() .change_context(VisitorError)?; Ok(Example { a, b, c }) @@ -228,7 +227,7 @@ impl<'de> StructVisitor<'de> for ExampleVisitor { let mut b = None; let mut c = None; - let mut errors: Result<(), ObjectAccessError> = Ok(()); + let mut errors = ReportSink::new(); while let Some(field) = object.field(ExampleFieldVisitor { a: &mut a, @@ -236,12 +235,7 @@ impl<'de> StructVisitor<'de> for ExampleVisitor { c: &mut c, }) { if let Err(error) = field { - match &mut errors { - Err(errors) => { - errors.extend_one(error); - } - errors => *errors = Err(error), - } + errors.add(error); } } @@ -272,8 +266,8 @@ impl<'de> StructVisitor<'de> for ExampleVisitor { Ok, ); - let (a, b, c, ..) = (a, b, c, errors, object.end()) - .fold_reports() + let (a, b, c, ..) = (a, b, c, errors.finish(), object.end()) + .try_collect() .change_context(VisitorError)?; Ok(Example { a, b, c })