diff --git a/crates/api/src/chronicle_graphql/authorization.rs b/crates/api/src/chronicle_graphql/authorization.rs index dfe5c69b..df3b99c2 100644 --- a/crates/api/src/chronicle_graphql/authorization.rs +++ b/crates/api/src/chronicle_graphql/authorization.rs @@ -42,9 +42,10 @@ pub enum Error { UnexpectedResponse { server: String, status: StatusCode }, } +#[derive(Clone)] pub struct TokenChecker { client: reqwest::Client, - verifier: Option, + verifier: Option>, jwks_uri: Option, userinfo_uri: Option, userinfo_cache: Arc>>>, @@ -65,7 +66,7 @@ impl TokenChecker { None, Duration::from_secs(cache_expiry_seconds.into()), ) - }), + }.into()), jwks_uri: jwks_uri.cloned(), userinfo_uri: userinfo_uri.cloned(), userinfo_cache: Arc::new(Mutex::new(TimedCache::with_lifespan( diff --git a/crates/api/src/chronicle_graphql/mod.rs b/crates/api/src/chronicle_graphql/mod.rs index d1d139e0..00043fed 100644 --- a/crates/api/src/chronicle_graphql/mod.rs +++ b/crates/api/src/chronicle_graphql/mod.rs @@ -56,7 +56,7 @@ use crate::{ApiDispatch, ApiError, StoreError}; #[macro_use] pub mod activity; pub mod agent; -mod authorization; +pub mod authorization; mod cursor_project; pub mod entity; pub mod mutation; @@ -453,6 +453,18 @@ impl SecurityConf { ) -> Self { Self { jwks_uri, userinfo_uri, id_claims, jwt_must_claim, allow_anonymous, opa } } + + pub fn as_endpoint_conf(&self, cache_expiry_seconds: u32) -> EndpointSecurityConfiguration { + EndpointSecurityConfiguration::new( + TokenChecker::new( + self.jwks_uri.as_ref(), + self.userinfo_uri.as_ref(), + cache_expiry_seconds + ), + self.jwt_must_claim.clone(), + self.allow_anonymous, + ) + } } #[async_trait::async_trait] @@ -576,14 +588,16 @@ async fn execute_opa_check( } } -struct EndpointSecurityConfiguration { + +#[derive(Clone)] +pub struct EndpointSecurityConfiguration { checker: TokenChecker, - must_claim: HashMap, - allow_anonymous: bool, + pub must_claim: HashMap, + pub allow_anonymous: bool, } impl EndpointSecurityConfiguration { - fn new( + pub fn new( checker: TokenChecker, must_claim: HashMap, allow_anonymous: bool, diff --git a/crates/chronicle-arrow/src/lib.rs b/crates/chronicle-arrow/src/lib.rs index 0dce7675..aa27c042 100644 --- a/crates/chronicle-arrow/src/lib.rs +++ b/crates/chronicle-arrow/src/lib.rs @@ -2,6 +2,7 @@ mod meta; mod operations; mod peekablestream; mod query; +use api::chronicle_graphql::{EndpointSecurityConfiguration}; use lazy_static::lazy_static; use tokio::sync::broadcast; @@ -27,7 +28,7 @@ use diesel::{r2d2::ConnectionManager, PgConnection}; use futures::{ future::join_all, stream::{self, BoxStream}, - FutureExt, StreamExt, TryFutureExt, TryStreamExt, + FutureExt, StreamExt, }; use meta::{DomainTypeMeta, Term}; @@ -171,7 +172,7 @@ pub async fn calculate_count_by_metadata_term( .and_then(|res| res.map_err(|e| Status::from_error(e.into()))) } -pub async fn create_flight_info_for_type( +async fn create_flight_info_for_type( pool: Arc>>, domain_items: Vec, term: Term, @@ -234,6 +235,7 @@ pub struct FlightServiceImpl { pool: r2d2::Pool>, api: ApiDispatch, record_batch_size: usize, + security: EndpointSecurityConfiguration } impl FlightServiceImpl { @@ -241,9 +243,10 @@ impl FlightServiceImpl { domain: &common::domain::ChronicleDomainDef, pool: &r2d2::Pool>, api: &ApiDispatch, + security: EndpointSecurityConfiguration, record_batch_size: usize, ) -> Self { - Self { domain: domain.clone(), pool: pool.clone(), api: api.clone(), record_batch_size } + Self { domain: domain.clone(), pool: pool.clone(), api: api.clone(), security, record_batch_size } } } @@ -643,18 +646,19 @@ pub async fn await_shutdown() { SHUTDOWN_CHANNEL.0.subscribe().recv().await.ok(); } -#[instrument(skip(pool, api))] +#[instrument(skip(pool, api, security))] pub async fn run_flight_service( domain: &common::domain::ChronicleDomainDef, pool: &Pool>, api: &ApiDispatch, + security: EndpointSecurityConfiguration, addrs: &Vec, record_batch_size: usize, ) -> Result<(), tonic::transport::Error> { meta::cache_domain_schemas(domain); let mut services = vec![]; for addr in addrs { - let flight_service = FlightServiceImpl::new(domain, pool, api, record_batch_size); + let flight_service = FlightServiceImpl::new(domain, pool, api, security.clone(), record_batch_size); info!("Starting flight service at {}", addr); @@ -675,7 +679,7 @@ pub async fn run_flight_service( #[cfg(test)] mod tests { - use api::commands::{ApiCommand, ImportCommand}; + use api::{chronicle_graphql::{authorization::TokenChecker, EndpointSecurityConfiguration}, commands::{ApiCommand, ImportCommand}}; use arrow_array::RecordBatch; use arrow_flight::{ decode::FlightRecordBatchStream, flight_service_client::FlightServiceClient, Criteria, @@ -696,15 +700,14 @@ mod tests { use futures::{pin_mut, stream, StreamExt}; use portpicker::pick_unused_port; - use std::{net::SocketAddr, time::Duration}; + use std::{collections::HashMap, net::SocketAddr, time::Duration}; use tonic::{transport::Channel, Request, Status}; use uuid::Uuid; use crate::{ meta::{cache_domain_schemas, get_domain_type_meta_from_cache, DomainTypeMeta}, query::{ - ActedOnBehalfOfRef, ActivityAndReferences, ActivityAssociationRef, AgentAndReferences, - AgentAttributionRef, DerivationRef, EntityAndReferences, EntityAttributionRef, + ActedOnBehalfOfRef, ActivityAndReferences, ActivityAssociationRef, AgentAndReferences, AgentAttributionRef, AgentInteraction, DerivationRef, EntityAndReferences, EntityAttributionRef }, }; @@ -722,7 +725,9 @@ mod tests { let dispatch = api.api_dispatch().clone(); let domain = domain.clone(); tokio::spawn(async move { - super::run_flight_service(&domain, &pool, &dispatch, &vec![addr], 10) + super::run_flight_service(&domain, &pool, &dispatch, + EndpointSecurityConfiguration::new(TokenChecker::new(None, None, 30), HashMap::default(), true), + &vec![addr], 10) .await .unwrap(); }); @@ -822,41 +827,41 @@ roles: ], was_derived_from: vec![ DerivationRef { - target: format!("entity-d-{}", i), + source: format!("entity-d-{}", i), activity: format!("activity-d-{}", i), }, DerivationRef { - target: format!("entity-d-{}", i), + source: format!("entity-d-{}", i), activity: format!("activity-d-{}", i), }, ], was_quoted_from: vec![ DerivationRef { - target: format!("entity-q-{}", i), + source: format!("entity-q-{}", i), activity: format!("activity-q-{}", i), }, DerivationRef { - target: format!("entity-q-{}", i), + source: format!("entity-q-{}", i), activity: format!("activity-q-{}", i), }, ], was_revision_of: vec![ DerivationRef { - target: format!("entity-r-{}", i), + source: format!("entity-r-{}", i), activity: format!("activity-r-{}", i), }, DerivationRef { - target: format!("entity-r-{}", i), + source: format!("entity-r-{}", i), activity: format!("activity-r-{}", i), }, ], had_primary_source: vec![ DerivationRef { - target: format!("entity-ps-{}", i), + source: format!("entity-ps-{}", i), activity: format!("activity-ps-{}", i), }, DerivationRef { - target: format!("entity-ps-{}", i), + source: format!("entity-ps-{}", i), activity: format!("activity-ps-{}", i), }, ], @@ -884,12 +889,20 @@ roles: ended: Some(Utc.with_ymd_and_hms(2022, 1, 2, 0, 0, 0).unwrap()), generated: vec![format!("entity-{}", i), format!("entity-{}", i + 1)], was_informed_by: vec![format!("activity-{}", i), format!("activity-{}", i + 1)], - was_associated_with: vec![ActivityAssociationRef { - responsible_agent: format!("agent-{}", i), - responsible_role: Some("CERTIFIER".to_string()), - delegate_agent: Some(format!("agent-{}", i + 1)), - delegate_role: Some("MANUFACTURER".to_string()), - }], + was_associated_with: vec![ + ActivityAssociationRef { + responsible: AgentInteraction { + agent: format!("agent-{}", i), + role: Some("ROLE_TYPE".to_string()), + }, + delegated: vec![ + AgentInteraction { + agent: format!("delegated-agent-{}", i), + role: Some("DELEGATED_ROLE".to_string()), + } + ], + } + ], used: vec![format!("entity-{}", i), format!("entity-{}", i + 1)], }; activities.push(activity); @@ -913,12 +926,12 @@ roles: attributes: create_attributes(meta.typ.as_deref(), &attributes), acted_on_behalf_of: vec![ActedOnBehalfOfRef { agent: format!("agent-{}", i), - role: Some("CERTIFIER".to_string()), - activity: Some(format!("activity-{}", i)), + role: Some("DELEGATED_CERTIFIER".to_string()), + activity: format!("activity-{}", i), }], was_attributed_to: vec![AgentAttributionRef { entity: format!("entity-{}", i), - role: Some("CERTIFIER".to_string()), + role: Some("UNSPECIFIED_INTERACTION".to_string()), }], }; agents.push(agent); @@ -1452,66 +1465,159 @@ roles: ], [ { - "acted_on_behalf_of": Array [], + "acted_on_behalf_of": Array [ + Object { + "activity": String("activity-0"), + "agent": String("agent-0"), + "role": String("DELEGATED_CERTIFIER"), + }, + ], "id": String("ContractorAgent-0"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), - "was_attributed_to": Array [], + "was_attributed_to": Array [ + Object { + "entity": String("entity-0"), + "role": String("UNSPECIFIED_INTERACTION"), + }, + ], }, { - "acted_on_behalf_of": Array [], + "acted_on_behalf_of": Array [ + Object { + "activity": String("activity-1"), + "agent": String("agent-1"), + "role": String("DELEGATED_CERTIFIER"), + }, + ], "id": String("ContractorAgent-1"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), - "was_attributed_to": Array [], + "was_attributed_to": Array [ + Object { + "entity": String("entity-1"), + "role": String("UNSPECIFIED_INTERACTION"), + }, + ], }, { - "acted_on_behalf_of": Array [], + "acted_on_behalf_of": Array [ + Object { + "activity": String("activity-2"), + "agent": String("agent-2"), + "role": String("DELEGATED_CERTIFIER"), + }, + ], "id": String("ContractorAgent-2"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), - "was_attributed_to": Array [], + "was_attributed_to": Array [ + Object { + "entity": String("entity-2"), + "role": String("UNSPECIFIED_INTERACTION"), + }, + ], }, { - "acted_on_behalf_of": Array [], + "acted_on_behalf_of": Array [ + Object { + "activity": String("activity-3"), + "agent": String("agent-3"), + "role": String("DELEGATED_CERTIFIER"), + }, + ], "id": String("ContractorAgent-3"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), - "was_attributed_to": Array [], + "was_attributed_to": Array [ + Object { + "entity": String("entity-3"), + "role": String("UNSPECIFIED_INTERACTION"), + }, + ], }, { - "acted_on_behalf_of": Array [], + "acted_on_behalf_of": Array [ + Object { + "activity": String("activity-4"), + "agent": String("agent-4"), + "role": String("DELEGATED_CERTIFIER"), + }, + ], "id": String("ContractorAgent-4"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), - "was_attributed_to": Array [], + "was_attributed_to": Array [ + Object { + "entity": String("entity-4"), + "role": String("UNSPECIFIED_INTERACTION"), + }, + ], }, { - "acted_on_behalf_of": Array [], + "acted_on_behalf_of": Array [ + Object { + "activity": String("activity-5"), + "agent": String("agent-5"), + "role": String("DELEGATED_CERTIFIER"), + }, + ], "id": String("ContractorAgent-5"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), - "was_attributed_to": Array [], + "was_attributed_to": Array [ + Object { + "entity": String("entity-5"), + "role": String("UNSPECIFIED_INTERACTION"), + }, + ], }, { - "acted_on_behalf_of": Array [], + "acted_on_behalf_of": Array [ + Object { + "activity": String("activity-6"), + "agent": String("agent-6"), + "role": String("DELEGATED_CERTIFIER"), + }, + ], "id": String("ContractorAgent-6"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), - "was_attributed_to": Array [], + "was_attributed_to": Array [ + Object { + "entity": String("entity-6"), + "role": String("UNSPECIFIED_INTERACTION"), + }, + ], }, { - "acted_on_behalf_of": Array [], + "acted_on_behalf_of": Array [ + Object { + "activity": String("activity-7"), + "agent": String("agent-7"), + "role": String("DELEGATED_CERTIFIER"), + }, + ], "id": String("ContractorAgent-7"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), - "was_attributed_to": Array [], + "was_attributed_to": Array [ + Object { + "entity": String("entity-7"), + "role": String("UNSPECIFIED_INTERACTION"), + }, + ], }, ], [ { "certIDAttribute": String("certIDAttribute-value"), - "had_primary_source": Array [], + "had_primary_source": Array [ + Object { + "activity": String("activity-ps-0"), + "source": String("CertificateEntity-0"), + }, + ], "id": String("CertificateEntity-0"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), @@ -1521,17 +1627,37 @@ roles: "role": String("CERTIFIER"), }, ], - "was_derived_from": Array [], + "was_derived_from": Array [ + Object { + "activity": String("activity-d-0"), + "source": String("CertificateEntity-0"), + }, + ], "was_generated_by": Array [ String("activity-0"), String("activity-1"), ], - "was_quoted_from": Array [], - "was_revision_of": Array [], + "was_quoted_from": Array [ + Object { + "activity": String("activity-q-0"), + "source": String("CertificateEntity-0"), + }, + ], + "was_revision_of": Array [ + Object { + "activity": String("activity-r-0"), + "source": String("CertificateEntity-0"), + }, + ], }, { "certIDAttribute": String("certIDAttribute-value"), - "had_primary_source": Array [], + "had_primary_source": Array [ + Object { + "activity": String("activity-ps-1"), + "source": String("CertificateEntity-1"), + }, + ], "id": String("CertificateEntity-1"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), @@ -1545,17 +1671,37 @@ roles: "role": String("MANUFACTURER"), }, ], - "was_derived_from": Array [], + "was_derived_from": Array [ + Object { + "activity": String("activity-d-1"), + "source": String("CertificateEntity-1"), + }, + ], "was_generated_by": Array [ String("activity-1"), String("activity-2"), ], - "was_quoted_from": Array [], - "was_revision_of": Array [], + "was_quoted_from": Array [ + Object { + "activity": String("activity-q-1"), + "source": String("CertificateEntity-1"), + }, + ], + "was_revision_of": Array [ + Object { + "activity": String("activity-r-1"), + "source": String("CertificateEntity-1"), + }, + ], }, { "certIDAttribute": String("certIDAttribute-value"), - "had_primary_source": Array [], + "had_primary_source": Array [ + Object { + "activity": String("activity-ps-2"), + "source": String("CertificateEntity-2"), + }, + ], "id": String("CertificateEntity-2"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), @@ -1569,17 +1715,37 @@ roles: "role": String("MANUFACTURER"), }, ], - "was_derived_from": Array [], + "was_derived_from": Array [ + Object { + "activity": String("activity-d-2"), + "source": String("CertificateEntity-2"), + }, + ], "was_generated_by": Array [ String("activity-2"), String("activity-3"), ], - "was_quoted_from": Array [], - "was_revision_of": Array [], + "was_quoted_from": Array [ + Object { + "activity": String("activity-q-2"), + "source": String("CertificateEntity-2"), + }, + ], + "was_revision_of": Array [ + Object { + "activity": String("activity-r-2"), + "source": String("CertificateEntity-2"), + }, + ], }, { "certIDAttribute": String("certIDAttribute-value"), - "had_primary_source": Array [], + "had_primary_source": Array [ + Object { + "activity": String("activity-ps-3"), + "source": String("CertificateEntity-3"), + }, + ], "id": String("CertificateEntity-3"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), @@ -1593,17 +1759,37 @@ roles: "role": String("MANUFACTURER"), }, ], - "was_derived_from": Array [], + "was_derived_from": Array [ + Object { + "activity": String("activity-d-3"), + "source": String("CertificateEntity-3"), + }, + ], "was_generated_by": Array [ String("activity-3"), String("activity-4"), ], - "was_quoted_from": Array [], - "was_revision_of": Array [], + "was_quoted_from": Array [ + Object { + "activity": String("activity-q-3"), + "source": String("CertificateEntity-3"), + }, + ], + "was_revision_of": Array [ + Object { + "activity": String("activity-r-3"), + "source": String("CertificateEntity-3"), + }, + ], }, { "certIDAttribute": String("certIDAttribute-value"), - "had_primary_source": Array [], + "had_primary_source": Array [ + Object { + "activity": String("activity-ps-4"), + "source": String("CertificateEntity-4"), + }, + ], "id": String("CertificateEntity-4"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), @@ -1617,17 +1803,37 @@ roles: "role": String("MANUFACTURER"), }, ], - "was_derived_from": Array [], + "was_derived_from": Array [ + Object { + "activity": String("activity-d-4"), + "source": String("CertificateEntity-4"), + }, + ], "was_generated_by": Array [ String("activity-4"), String("activity-5"), ], - "was_quoted_from": Array [], - "was_revision_of": Array [], + "was_quoted_from": Array [ + Object { + "activity": String("activity-q-4"), + "source": String("CertificateEntity-4"), + }, + ], + "was_revision_of": Array [ + Object { + "activity": String("activity-r-4"), + "source": String("CertificateEntity-4"), + }, + ], }, { "certIDAttribute": String("certIDAttribute-value"), - "had_primary_source": Array [], + "had_primary_source": Array [ + Object { + "activity": String("activity-ps-5"), + "source": String("CertificateEntity-5"), + }, + ], "id": String("CertificateEntity-5"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), @@ -1641,17 +1847,37 @@ roles: "role": String("MANUFACTURER"), }, ], - "was_derived_from": Array [], + "was_derived_from": Array [ + Object { + "activity": String("activity-d-5"), + "source": String("CertificateEntity-5"), + }, + ], "was_generated_by": Array [ String("activity-5"), String("activity-6"), ], - "was_quoted_from": Array [], - "was_revision_of": Array [], + "was_quoted_from": Array [ + Object { + "activity": String("activity-q-5"), + "source": String("CertificateEntity-5"), + }, + ], + "was_revision_of": Array [ + Object { + "activity": String("activity-r-5"), + "source": String("CertificateEntity-5"), + }, + ], }, { "certIDAttribute": String("certIDAttribute-value"), - "had_primary_source": Array [], + "had_primary_source": Array [ + Object { + "activity": String("activity-ps-6"), + "source": String("CertificateEntity-6"), + }, + ], "id": String("CertificateEntity-6"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), @@ -1665,17 +1891,37 @@ roles: "role": String("MANUFACTURER"), }, ], - "was_derived_from": Array [], + "was_derived_from": Array [ + Object { + "activity": String("activity-d-6"), + "source": String("CertificateEntity-6"), + }, + ], "was_generated_by": Array [ String("activity-6"), String("activity-7"), ], - "was_quoted_from": Array [], - "was_revision_of": Array [], + "was_quoted_from": Array [ + Object { + "activity": String("activity-q-6"), + "source": String("CertificateEntity-6"), + }, + ], + "was_revision_of": Array [ + Object { + "activity": String("activity-r-6"), + "source": String("CertificateEntity-6"), + }, + ], }, { "certIDAttribute": String("certIDAttribute-value"), - "had_primary_source": Array [], + "had_primary_source": Array [ + Object { + "activity": String("activity-ps-7"), + "source": String("CertificateEntity-7"), + }, + ], "id": String("CertificateEntity-7"), "namespace_name": String("default"), "namespace_uuid": String("00000000-0000-0000-0000-000000000000"), @@ -1689,13 +1935,28 @@ roles: "role": String("MANUFACTURER"), }, ], - "was_derived_from": Array [], + "was_derived_from": Array [ + Object { + "activity": String("activity-d-7"), + "source": String("CertificateEntity-7"), + }, + ], "was_generated_by": Array [ String("activity-7"), String("activity-8"), ], - "was_quoted_from": Array [], - "was_revision_of": Array [], + "was_quoted_from": Array [ + Object { + "activity": String("activity-q-7"), + "source": String("CertificateEntity-7"), + }, + ], + "was_revision_of": Array [ + Object { + "activity": String("activity-r-7"), + "source": String("CertificateEntity-7"), + }, + ], }, ], ] diff --git a/crates/chronicle-arrow/src/meta.rs b/crates/chronicle-arrow/src/meta.rs index 1f7a2469..4e6ef518 100644 --- a/crates/chronicle-arrow/src/meta.rs +++ b/crates/chronicle-arrow/src/meta.rs @@ -40,30 +40,50 @@ pub fn attribution_struct() -> arrow_schema::DataType { pub fn derivation_struct() -> arrow_schema::DataType { arrow_schema::DataType::Struct( vec![ - arrow_schema::Field::new("target", arrow_schema::DataType::Utf8, false), + arrow_schema::Field::new("source", arrow_schema::DataType::Utf8, false), arrow_schema::Field::new("activity", arrow_schema::DataType::Utf8, false), ] .into(), ) } -pub fn association_struct() -> arrow_schema::DataType { +pub fn qualified_agent_struct() -> arrow_schema::DataType { arrow_schema::DataType::Struct( vec![ - arrow_schema::Field::new("responsible_agent", arrow_schema::DataType::Utf8, false), - arrow_schema::Field::new("responsible_role", arrow_schema::DataType::Utf8, true), - arrow_schema::Field::new("delegate_agent", arrow_schema::DataType::Utf8, true), - arrow_schema::Field::new("delegate_role", arrow_schema::DataType::Utf8, true), + arrow_schema::Field::new("agent", arrow_schema::DataType::Utf8, false), + arrow_schema::Field::new("role", arrow_schema::DataType::Utf8, true), ] .into(), ) } +pub fn association_struct() -> arrow_schema::DataType { + arrow_schema::DataType::Struct( + vec![ + arrow_schema::Field::new( + "responsible", + qualified_agent_struct(), + false + ), + arrow_schema::Field::new( + "delegated", + arrow_schema::DataType::new_list( + qualified_agent_struct(), + false + ), + false + ), + ] + .into(), + ) +} + + pub fn agent_delegation_struct() -> arrow_schema::DataType { arrow_schema::DataType::Struct( vec![ arrow_schema::Field::new("agent", arrow_schema::DataType::Utf8, false), - arrow_schema::Field::new("activity", arrow_schema::DataType::Utf8, true), + arrow_schema::Field::new("activity", arrow_schema::DataType::Utf8, false), arrow_schema::Field::new("role", arrow_schema::DataType::Utf8, true), ] .into(), @@ -164,13 +184,13 @@ pub fn schema_for_activity(activity: &ActivityDef) -> Schema { builder.push(arrow_schema::Field::new( "used", arrow_schema::DataType::new_list(arrow_schema::DataType::Utf8, false), - true, + false, )); builder.push(arrow_schema::Field::new( "generated", arrow_schema::DataType::new_list(arrow_schema::DataType::Utf8, false), - true, + false, )); builder.push(arrow_schema::Field::new( @@ -203,7 +223,7 @@ pub fn schema_for_agent(agent: &AgentDef) -> Schema { builder.push(arrow_schema::Field::new( "acted_on_behalf_of", arrow_schema::DataType::new_list(agent_delegation_struct(), false), - true, + false, )); builder.push(arrow_schema::Field::new( diff --git a/crates/chronicle-arrow/src/operations/agent.rs b/crates/chronicle-arrow/src/operations/agent.rs index 55ea0d41..d93b5b3e 100644 --- a/crates/chronicle-arrow/src/operations/agent.rs +++ b/crates/chronicle-arrow/src/operations/agent.rs @@ -39,11 +39,11 @@ fn get_acted_on_behalf_of( "acted_on_behalf_of", row_index, "agent", - "role", "activity", + "role", )? .into_iter() - .map(|(agent, role, activity)| ActedOnBehalfOfRef { agent, role, activity }) + .map(|(agent, activity, role)| ActedOnBehalfOfRef { agent, role, activity }) .collect()) } @@ -81,7 +81,7 @@ pub fn agent_operations( ns.clone(), AgentId::from_external_id(id), AgentId::from_external_id(acted_on_behalf_of_ref.agent), - acted_on_behalf_of_ref.activity.map(ActivityId::from_external_id), + Some(ActivityId::from_external_id(acted_on_behalf_of_ref.activity)), acted_on_behalf_of_ref.role.map(Role::from), )); } diff --git a/crates/chronicle-arrow/src/operations/entity.rs b/crates/chronicle-arrow/src/operations/entity.rs index dc05c32a..cbb59cf0 100644 --- a/crates/chronicle-arrow/src/operations/entity.rs +++ b/crates/chronicle-arrow/src/operations/entity.rs @@ -44,9 +44,9 @@ fn get_derivation( record_batch: &RecordBatch, row_index: usize, ) -> Result, ChronicleArrowError> { - Ok(struct_2_list_column(record_batch, column_name, row_index, "target", "activity")? + Ok(struct_2_list_column(record_batch, column_name, row_index, "source", "activity")? .into_iter() - .map(|(target, activity)| DerivationRef { target, activity }) + .map(|(target, activity)| DerivationRef { source: target, activity }) .collect()) } @@ -92,8 +92,8 @@ pub fn entity_operations( for was_derived_from_ref in was_derived_from_refs { operations.push(ChronicleOperation::entity_derive( ns.clone(), + EntityId::from_external_id(was_derived_from_ref.source), EntityId::from_external_id(id), - EntityId::from_external_id(was_derived_from_ref.target), Some(ActivityId::from_external_id(was_derived_from_ref.activity)), DerivationType::None, )) @@ -104,8 +104,8 @@ pub fn entity_operations( for had_primary_source_ref in had_primary_source_refs { operations.push(ChronicleOperation::entity_derive( ns.clone(), + EntityId::from_external_id(had_primary_source_ref.source), EntityId::from_external_id(id), - EntityId::from_external_id(had_primary_source_ref.target), Some(ActivityId::from_external_id(had_primary_source_ref.activity)), DerivationType::PrimarySource, )) @@ -116,8 +116,8 @@ pub fn entity_operations( for was_quoted_from_ref in was_quoted_from_refs { operations.push(ChronicleOperation::entity_derive( ns.clone(), + EntityId::from_external_id(was_quoted_from_ref.source), EntityId::from_external_id(id), - EntityId::from_external_id(was_quoted_from_ref.target), Some(ActivityId::from_external_id(was_quoted_from_ref.activity)), DerivationType::Quotation, )) @@ -128,8 +128,8 @@ pub fn entity_operations( for was_revision_of_ref in was_revision_of_refs { operations.push(ChronicleOperation::entity_derive( ns.clone(), + EntityId::from_external_id(was_revision_of_ref.source), EntityId::from_external_id(id), - EntityId::from_external_id(was_revision_of_ref.target), Some(ActivityId::from_external_id(was_revision_of_ref.activity)), DerivationType::Revision, )) diff --git a/crates/chronicle-arrow/src/operations/mod.rs b/crates/chronicle-arrow/src/operations/mod.rs index aa03c5de..ae0089c7 100644 --- a/crates/chronicle-arrow/src/operations/mod.rs +++ b/crates/chronicle-arrow/src/operations/mod.rs @@ -23,8 +23,6 @@ use common::{ prov::{operations::ChronicleOperation, NamespaceId}, }; -use futures::StreamExt; - use uuid::Uuid; use crate::{ @@ -305,7 +303,7 @@ fn struct_3_list_column_opt_string( field1_name: &str, field2_name: &str, field3_name: &str, -) -> Result, Option)>, ChronicleArrowError> { +) -> Result)>, ChronicleArrowError> { let column_index = record_batch .schema() .index_of(column_name) @@ -329,18 +327,21 @@ fn struct_3_list_column_opt_string( .as_any() .downcast_ref::() .ok_or_else(|| ChronicleArrowError::ColumnTypeMismatch(field1_name.to_string()))?; - let field2_array = field2_index.as_any().downcast_ref::(); + let field2_array = field2_index + .as_any() + .downcast_ref::() + .ok_or_else(|| ChronicleArrowError::ColumnTypeMismatch(field2_name.to_string()))?; let field3_array = field3_index.as_any().downcast_ref::(); Ok((0..struct_array.len()) .map(|i| { ( field1_array.value(i).to_string(), - field2_array.map(|arr| arr.value(i).to_string()), + field2_array.value(i).to_string(), field3_array.map(|arr| arr.value(i).to_string()), ) }) - .collect::, Option)>>()) + .collect::)>>()) } else { Ok(vec![]) } diff --git a/crates/chronicle-arrow/src/query/activity.rs b/crates/chronicle-arrow/src/query/activity.rs index 7800117c..4c3834a9 100644 --- a/crates/chronicle-arrow/src/query/activity.rs +++ b/crates/chronicle-arrow/src/query/activity.rs @@ -1,16 +1,16 @@ use std::{collections::HashMap, sync::Arc}; -use crate::{meta::association_struct, ChronicleArrowError, DomainTypeMeta}; -use arrow::array::{ArrayBuilder, StringBuilder, StructBuilder}; +use crate::{meta::{qualified_agent_struct}, ChronicleArrowError, DomainTypeMeta}; +use arrow::array::{ArrayBuilder, ListBuilder, StringBuilder, StructBuilder}; use arrow_array::{ Array, BooleanArray, Int64Array, ListArray, RecordBatch, StringArray, TimestampNanosecondArray, }; -use arrow_buffer::{Buffer, ToByteSlice}; -use arrow_data::ArrayData; + + use arrow_schema::{DataType, Field}; use chronicle_persistence::{ - query::{Activity, Generation, Namespace, Usage, WasInformedBy}, - schema::{activity, entity, generation, namespace, usage, wasinformedby}, + query::{Activity, Association, Delegation, Generation, Namespace, Usage, WasInformedBy}, + schema::{activity, agent, association, delegation, entity, generation, namespace, usage, wasinformedby}, }; use chrono::{DateTime, Utc}; use common::{ @@ -40,12 +40,16 @@ pub fn activity_count_by_type( Ok(count) } +#[derive(Default)] +pub struct AgentInteraction { + pub(crate) agent: String, + pub(crate) role: Option, +} + #[derive(Default)] pub struct ActivityAssociationRef { - pub(crate) responsible_agent: String, - pub(crate) responsible_role: Option, - pub(crate) delegate_agent: Option, - pub(crate) delegate_role: Option, + pub(crate) responsible: AgentInteraction, + pub(crate) delegated: Vec } #[derive(Default)] @@ -204,90 +208,76 @@ impl ActivityAndReferences { } fn associations_to_list_array( - associations: Vec>, + associations: Vec>, ) -> Result { - let offsets: Vec = std::iter::once(0) - .chain(associations.iter().map(|v| v.len() as i32)) - .scan(0, |state, len| { - *state += len; - Some(*state) - }) - .collect(); - - let fields = vec![ - Field::new("responsible_agent", DataType::Utf8, false), - Field::new("responsible_role", DataType::Utf8, true), - Field::new("delegate_agent", DataType::Utf8, true), - Field::new("delegate_role", DataType::Utf8, true), - ]; - let field_builders = vec![ - Box::new(StringBuilder::new()) as Box, - Box::new(StringBuilder::new()) as Box, - Box::new(StringBuilder::new()) as Box, - Box::new(StringBuilder::new()) as Box, - ]; - - let mut builder = StructBuilder::new(fields, field_builders); - - for association in associations.into_iter().flatten() { - builder - .field_builder::(0) - .expect("Failed to get field builder for responsible_agent") - .append_value(&association.responsible_agent); - if let Some(role) = &association.responsible_role { - builder - .field_builder::(1) - .expect("Failed to get field builder for responsible_role") - .append_value(role); - } else { - builder - .field_builder::(1) - .expect("Failed to get field builder for responsible_role") - .append_null(); - } - if let Some(agent) = &association.delegate_agent { - builder - .field_builder::(2) - .expect("Failed to get field builder for delegate_agent") - .append_value(agent); - } else { - builder - .field_builder::(2) - .expect("Failed to get field builder for delegate_agent") - .append_null(); - } - if let Some(role) = &association.delegate_role { - builder - .field_builder::(3) - .expect("Failed to get field builder for delegate_role") - .append_value(role); - } else { - builder - .field_builder::(3) - .expect("Failed to get field builder for delegate_role") - .append_null(); - } - - builder.append(true); - } - - let values_array = builder.finish(); - - let data_type = DataType::new_list(association_struct(), false); - let offsets_buffer = Buffer::from(offsets.to_byte_slice()); - - let list_array = ListArray::from( - ArrayData::builder(data_type.clone()) - .add_child_data(values_array.to_data()) - .len(offsets.len() - 1) - .null_count(0) - .add_buffer(offsets_buffer) - .build()?, - ); - - Ok(list_array) + let mut list_builder = ListBuilder::new( + StructBuilder::new( + vec![ + Field::new("responsible", qualified_agent_struct(), false), + Field::new("delegated", DataType::List(Arc::new(Field::new( + "item", + qualified_agent_struct(), + false // Ensure the struct within the list is non-nullable + ))), false), + ], + vec![ + Box::new(StructBuilder::new( + vec![ + Field::new("agent", DataType::Utf8, false), + Field::new("role", DataType::Utf8, true), + ], + vec![ + Box::new(StringBuilder::new()), + Box::new(StringBuilder::new()), + ], + )), + Box::new(ListBuilder::new( + StructBuilder::new( + vec![ + Field::new("agent", DataType::Utf8, false), + Field::new("role", DataType::Utf8, true), + ], + vec![ + Box::new(StringBuilder::new()), + Box::new(StringBuilder::new()), + ], + ) + )), + ], + ) + ); + + for association_vec in associations { + let struct_builder = list_builder.values(); + for association in association_vec { + let responsible_builder = struct_builder.field_builder::(0).unwrap(); + responsible_builder.field_builder::(0).unwrap().append_value(&association.responsible.agent); + if let Some(role) = &association.responsible.role { + responsible_builder.field_builder::(1).unwrap().append_value(role); + } else { + responsible_builder.field_builder::(1).unwrap().append_null(); + } + responsible_builder.append(true); // Ensure to append to the struct builder after all fields are handled + + let delegated_builder = struct_builder.field_builder::>(1).unwrap(); + for delegate in &association.delegated { + let delegate_struct_builder = delegated_builder.values(); + delegate_struct_builder.field_builder::(0).unwrap().append_value(&delegate.agent); + if let Some(role) = &delegate.role { + delegate_struct_builder.field_builder::(1).unwrap().append_value(role); + } else { + delegate_struct_builder.field_builder::(1).unwrap().append_null(); + } + delegate_struct_builder.append(true); // Append each delegate struct + } + delegated_builder.append(true); // Append the list of delegates + } + struct_builder.append(true); // Append the overall struct + list_builder.append(true); // Append to the list builder + } + + Ok(list_builder.finish()) } - pub fn load_activities_by_type( pool: &Pool>, typ: &Option, @@ -349,6 +339,56 @@ pub fn load_activities_by_type( acc }); + let associations_map: HashMap> = + Association::belonging_to(&activities) + .inner_join(agent::table.on(association::agent_id.eq(agent::id))) + .select((association::activity_id, (agent::id, agent::external_id, association::role))) + .load::<(i32, (i32, String, String))>(&mut connection)? + .into_iter() + .fold(HashMap::new(), |mut acc: HashMap>, (activity_id, (agent_id, agent_external_id, role_external_id))| { + acc.entry(activity_id) + .or_default() + .insert(agent_id, (agent_external_id, role_external_id)); + acc + }); + + let delegations_map: HashMap> = + Delegation::belonging_to(&activities) + .inner_join(agent::table.on(delegation::delegate_id.eq(agent::id))) + .select((delegation::activity_id, (delegation::responsible_id, agent::external_id, delegation::role))) + .load::<(i32, (i32, String, String))>(&mut connection)? + .into_iter() + .fold(HashMap::new(), |mut acc: HashMap>, (activity_id, (agent_id, agent_external_id, role_external_id))| { + acc.entry(activity_id) + .or_default() + .insert(agent_id, (agent_external_id, role_external_id)); + acc + }); + + let mut activity_associations: HashMap> = HashMap::new(); + + for (activity_id, agent_map) in associations_map.into_iter() { + let mut association_refs = Vec::new(); + for (agent_id, (agent_external_id, role_external_id)) in agent_map.into_iter() { + let mut delegated_agents = Vec::new(); + if let Some(delegations) = delegations_map.get(&activity_id) { + if let Some((delegated_agent_external_id, delegated_role_external_id)) = delegations.get(&agent_id) { + delegated_agents.push(AgentInteraction { + agent: delegated_agent_external_id.clone(), + role: Some(delegated_role_external_id.clone()), + }); + } + } + association_refs.push(ActivityAssociationRef { + responsible: AgentInteraction { + agent: agent_external_id, + role: Some(role_external_id), + }, + delegated: delegated_agents, + }); + } + activity_associations.insert(activity_id, association_refs); + } let fetched_records = activities.len() as u64; let mut activities_and_references = vec![]; @@ -367,7 +407,7 @@ pub fn load_activities_by_type( was_informed_by: was_informed_by_map.remove(&activity.id).unwrap_or_default(), used: used_map.remove(&activity.id).unwrap_or_default(), generated: generated_map.remove(&activity.id).unwrap_or_default(), - ..Default::default() + was_associated_with: activity_associations.remove(&activity.id).unwrap_or_default(), }); } Ok((activities_and_references.into_iter(), fetched_records, fetched_records)) diff --git a/crates/chronicle-arrow/src/query/agent.rs b/crates/chronicle-arrow/src/query/agent.rs index 6a478a56..8b3ea6eb 100644 --- a/crates/chronicle-arrow/src/query/agent.rs +++ b/crates/chronicle-arrow/src/query/agent.rs @@ -10,13 +10,13 @@ use arrow_buffer::{Buffer, ToByteSlice}; use arrow_data::ArrayData; use arrow_schema::{DataType, Field}; use chronicle_persistence::{ - query::{Agent, Namespace}, - schema::{agent, namespace}, + query::{Agent, Attribution, Delegation, Namespace}, + schema::{activity, agent, attribution, delegation, entity, namespace}, }; use common::{ attributes::Attributes, domain::PrimitiveType, - prov::{DomaintypeId, ExternalIdPart}, + prov::{DomaintypeId, ExternalIdPart, Role}, }; use diesel::{ pg::PgConnection, @@ -41,7 +41,7 @@ pub fn agent_count_by_type( pub struct ActedOnBehalfOfRef { pub(crate) agent: String, pub(crate) role: Option, - pub(crate) activity: Option, + pub(crate) activity: String, } #[derive(Default)] @@ -167,6 +167,8 @@ impl AgentAndReferences { RecordBatch::try_new(meta.schema.clone(), columns).map_err(ChronicleArrowError::from) } } + + fn agent_acted_on_behalf_of_to_list_array( agent_attributions: Vec>, ) -> Result { @@ -184,7 +186,7 @@ fn agent_acted_on_behalf_of_to_list_array( let fields = vec![ Field::new("agent", DataType::Utf8, false), - Field::new("activity", DataType::Utf8, true), + Field::new("activity", DataType::Utf8, false), Field::new("role", DataType::Utf8, true), ]; let field_builders = vec![ @@ -203,7 +205,7 @@ fn agent_acted_on_behalf_of_to_list_array( builder .field_builder::(1) .expect("Failed to get activity field builder") - .append_option(acted_on_behalf_of.activity.as_deref()); + .append_value(acted_on_behalf_of.activity); builder .field_builder::(2) .expect("Failed to get role field builder") @@ -315,6 +317,42 @@ pub fn load_agents_by_type( let (agents, namespaces): (Vec, Vec) = agents_and_namespaces.into_iter().unzip(); + let mut attributions_map: HashMap> = + Attribution::belonging_to(&agents) + .inner_join(entity::table.on(attribution::entity_id.eq(entity::id))) + .select((attribution::agent_id, attribution::role, entity::external_id)) + .load::<(i32, Role, String)>(&mut connection)? + .into_iter() + .fold( + HashMap::new(), + |mut acc: HashMap>, (id, role, external_id)| { + acc.entry(id).or_default().push(AgentAttributionRef { + entity: external_id, + role: Some(role.to_string()), + }); + acc + }, + ); + + let mut delegations_map: HashMap> = + Delegation::belonging_to(&agents) + .inner_join(activity::table.on(delegation::activity_id.eq(activity::id))) + .inner_join(agent::table.on(delegation::delegate_id.eq(agent::id))) + .select((delegation::responsible_id, delegation::role, activity::external_id, agent::external_id)) + .load::<(i32, Role, String,String)>(&mut connection)? + .into_iter() + .fold( + HashMap::new(), + |mut acc: HashMap>, (id, role, activity,delegate)| { + acc.entry(id).or_default().push(ActedOnBehalfOfRef { + agent: delegate, + activity, + role: Some(role.to_string()), + }); + acc + }, + ); + let mut agents_and_references = vec![]; for (agent, ns) in agents.into_iter().zip(namespaces) { @@ -325,8 +363,9 @@ pub fn load_agents_by_type( attributes: Attributes::new( agent.domaintype.map(DomaintypeId::from_external_id), vec![], - ), // Placeholder for attribute loading logic - ..Default::default() + ), + was_attributed_to: attributions_map.remove(&agent.id).unwrap_or_default(), + acted_on_behalf_of: delegations_map.remove(&agent.id).unwrap_or_default() }); } diff --git a/crates/chronicle-arrow/src/query/entity.rs b/crates/chronicle-arrow/src/query/entity.rs index 7c612e8e..8dfd0a2a 100644 --- a/crates/chronicle-arrow/src/query/entity.rs +++ b/crates/chronicle-arrow/src/query/entity.rs @@ -67,7 +67,7 @@ pub fn entity_count_by_type( #[derive(Default, Debug)] pub struct DerivationRef { - pub target: String, + pub source: String, pub activity: String, } @@ -237,7 +237,7 @@ fn derivations_to_list_array( .collect(); let fields = vec![ - Field::new("target", DataType::Utf8, false), + Field::new("source", DataType::Utf8, false), Field::new("activity", DataType::Utf8, false), ]; let field_builders = vec![ @@ -251,7 +251,7 @@ fn derivations_to_list_array( builder .field_builder::(0) .unwrap() - .append_value(derivation.target); + .append_value(derivation.source); builder .field_builder::(1) .unwrap() @@ -439,7 +439,7 @@ pub fn load_entities_by_type( (entity_id, activity_external_id, entity_external_id, derivation_type)| { acc.entry((entity_id, derivation_type)).or_default().push(DerivationRef { activity: activity_external_id, - target: entity_external_id, + source: entity_external_id, }); acc }, diff --git a/crates/chronicle-arrow/src/query/mod.rs b/crates/chronicle-arrow/src/query/mod.rs index a8e9d10a..f118df03 100644 --- a/crates/chronicle-arrow/src/query/mod.rs +++ b/crates/chronicle-arrow/src/query/mod.rs @@ -13,11 +13,7 @@ use arrow_buffer::{Buffer, ToByteSlice}; use arrow_data::ArrayData; use arrow_schema::DataType; -use common::prov::DomaintypeId; -use diesel::{ - pg::PgConnection, - r2d2::{ConnectionManager, Pool}, -}; + // For simple id only relations, we can just reuse this mapping diff --git a/crates/chronicle-persistence/src/query.rs b/crates/chronicle-persistence/src/query.rs index 3c150e91..0fd03fe5 100644 --- a/crates/chronicle-persistence/src/query.rs +++ b/crates/chronicle-persistence/src/query.rs @@ -134,10 +134,11 @@ pub struct Usage { entity_id: i32, } -#[derive(Debug, Queryable, Selectable, Associations, PartialEq)] +#[derive(Debug, Queryable, Selectable, Identifiable, Associations, PartialEq)] #[diesel(table_name = association)] #[diesel(belongs_to(Agent))] #[diesel(belongs_to(Activity))] +#[diesel(primary_key(agent_id, activity_id, role))] pub struct Association { agent_id: i32, activity_id: i32, @@ -155,10 +156,11 @@ pub struct Attribution { role: String, } -#[derive(Debug, Queryable, Selectable, Associations, PartialEq)] +#[derive(Debug, Queryable, Selectable, Associations, Identifiable, PartialEq)] #[diesel(table_name = delegation)] #[diesel(belongs_to(Agent, foreign_key = delegate_id, foreign_key = responsible_id))] #[diesel(belongs_to(Activity))] +#[diesel(primary_key(delegate_id, responsible_id, activity_id, role))] pub struct Delegation { delegate_id: i32, responsible_id: i32, diff --git a/crates/chronicle/src/bootstrap/mod.rs b/crates/chronicle/src/bootstrap/mod.rs index 1316c814..db717fb2 100644 --- a/crates/chronicle/src/bootstrap/mod.rs +++ b/crates/chronicle/src/bootstrap/mod.rs @@ -1,7 +1,7 @@ mod cli; pub mod opa; use api::{ - chronicle_graphql::{ChronicleApiServer, ChronicleGraphQl, JwksUri, SecurityConf, UserInfoUri}, + chronicle_graphql::{authorization::TokenChecker, ChronicleApiServer, ChronicleGraphQl, EndpointSecurityConfiguration, JwksUri, SecurityConf, UserInfoUri}, commands::ApiResponse, Api, ApiDispatch, ApiError, StoreError, UuidGen, }; @@ -137,13 +137,14 @@ pub async fn arrow_api_server( api: &ApiDispatch, pool: &ConnectionPool, addresses: Option>, - _security_conf: &SecurityConf, + security_conf: EndpointSecurityConfiguration, record_batch_size: usize, operation_batch_size: usize, ) -> Result> + Send>, ApiError> { tracing::info!( addresses = ?addresses, - security_conf = ?_security_conf, + allow_anonymous = ?security_conf.allow_anonymous, + jwt_must_claim = ?security_conf.must_claim, record_batch_size, operation_batch_size, "Starting arrow flight with the provided configuration" @@ -151,7 +152,7 @@ pub async fn arrow_api_server( match addresses { Some(addresses) => - chronicle_arrow::run_flight_service(domain, pool, api, &addresses, record_batch_size) + chronicle_arrow::run_flight_service(domain, pool, api, security_conf, &addresses, record_batch_size) .await .map_err(|e| ApiError::ArrowService(e.into())) .map(|_| Some(futures::future::ready(Ok(())))), @@ -553,13 +554,13 @@ where jwks_uri, userinfo_uri, id_claims, - jwt_must_claim, + jwt_must_claim.clone(), allow_anonymous, opa.context().clone(), ); let arrow = - arrow_api_server(domain, &api, &pool, arrow_interface, &security_conf, 1000, 100); + arrow_api_server(domain, &api, &pool, arrow_interface, security_conf.as_endpoint_conf(30), 1000, 100); let serve_graphql = endpoints.contains(&"graphql".to_string()); let serve_data = endpoints.contains(&"data".to_string()); diff --git a/crates/common/src/opa/core.rs b/crates/common/src/opa/core.rs index 3acd4849..42e5d5f3 100644 --- a/crates/common/src/opa/core.rs +++ b/crates/common/src/opa/core.rs @@ -320,7 +320,7 @@ impl MaxEncodedLen for OpaSettings { #[cfg(feature = "parity-encoding")] pub mod codec { use super::*; - use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + use parity_scale_codec::{Decode, Encode}; use scale_decode::DecodeAsType; use scale_encode::EncodeAsType;