From b31d6099d7d032def62b1451bbb9b9b2c5e7f4cd Mon Sep 17 00:00:00 2001 From: Daniel Harvey Date: Thu, 27 Jun 2024 17:29:58 +0100 Subject: [PATCH] Tidy `model_permissions` and `command_permissions` stages (#775) ### What Much like https://github.com/hasura/v3-engine/pull/774, we split the big files into separate modules. Functional no-op. ### How Copy pasta, `just fix-local`, mostly. V3_GIT_ORIGIN_REV_ID: ba7e40057583f98df54573c72663a4a2d2c4a4ab --- .../command_permissions/command_permission.rs | 146 ++++++++++ .../src/stages/command_permissions/mod.rs | 156 +---------- .../src/stages/command_permissions/types.rs | 21 ++ .../src/stages/model_permissions/mod.rs | 246 +---------------- .../model_permissions/model_permission.rs | 249 ++++++++++++++++++ 5 files changed, 426 insertions(+), 392 deletions(-) create mode 100644 v3/crates/metadata-resolve/src/stages/command_permissions/command_permission.rs create mode 100644 v3/crates/metadata-resolve/src/stages/command_permissions/types.rs create mode 100644 v3/crates/metadata-resolve/src/stages/model_permissions/model_permission.rs diff --git a/v3/crates/metadata-resolve/src/stages/command_permissions/command_permission.rs b/v3/crates/metadata-resolve/src/stages/command_permissions/command_permission.rs new file mode 100644 index 00000000000..b134130dfcc --- /dev/null +++ b/v3/crates/metadata-resolve/src/stages/command_permissions/command_permission.rs @@ -0,0 +1,146 @@ +use hasura_authn_core::Role; +use indexmap::IndexMap; + +use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName}; + +use crate::stages::{ + boolean_expressions, commands, data_connector_scalar_types, data_connectors, models_graphql, + object_boolean_expressions, relationships, scalar_types, +}; +use crate::types::error::Error; +use crate::types::subgraph::Qualified; +use open_dds::arguments::ArgumentName; + +use crate::helpers::argument::resolve_value_expression_for_argument; + +use open_dds::permissions::CommandPermissionsV1; + +use super::types::CommandPermission; +use crate::helpers::typecheck; +use std::collections::BTreeMap; + +// get the ndc_models::Type for an argument if it is available +fn get_command_source_argument<'a>( + argument_name: &'a ArgumentName, + command: &'a commands::Command, +) -> Option<&'a ndc_models::Type> { + command + .source + .as_ref() + .and_then(|source| { + source + .argument_mappings + .get(argument_name) + .map(|connector_argument_name| source.source_arguments.get(connector_argument_name)) + }) + .flatten() +} + +pub fn resolve_command_permissions( + command: &commands::Command, + permissions: &CommandPermissionsV1, + object_types: &BTreeMap, relationships::ObjectTypeWithRelationships>, + scalar_types: &BTreeMap, scalar_types::ScalarTypeRepresentation>, + object_boolean_expression_types: &BTreeMap< + Qualified, + object_boolean_expressions::ObjectBooleanExpressionType, + >, + boolean_expression_types: &boolean_expressions::BooleanExpressionTypes, + models: &IndexMap, models_graphql::ModelWithGraphql>, + data_connectors: &data_connectors::DataConnectors, + data_connector_scalars: &BTreeMap< + Qualified, + data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap, + >, + subgraph: &str, +) -> Result, Error> { + let mut validated_permissions = BTreeMap::new(); + for command_permission in &permissions.permissions { + let mut argument_presets = BTreeMap::new(); + + for argument_preset in &command_permission.argument_presets { + if argument_presets.contains_key(&argument_preset.argument) { + return Err(Error::DuplicateCommandArgumentPreset { + command_name: command.name.clone(), + argument_name: argument_preset.argument.clone(), + }); + } + + let source_argument_type = + get_command_source_argument(&argument_preset.argument, command); + + let data_connector_name = command + .source + .as_ref() + .map(|source| &source.data_connector.name) + .ok_or(Error::CommandSourceRequiredForPredicate { + command_name: command.name.clone(), + })?; + + let data_connector_core_info = + data_connectors.0.get(data_connector_name).ok_or_else(|| { + Error::UnknownCommandDataConnector { + command_name: command.name.clone(), + data_connector: data_connector_name.clone(), + } + })?; + + let data_connector_link = data_connectors::DataConnectorLink::new( + data_connector_name.clone(), + &data_connector_core_info.inner, + )?; + + match command.arguments.get(&argument_preset.argument) { + Some(argument) => { + let value_expression = resolve_value_expression_for_argument( + &argument_preset.argument, + &argument_preset.value, + &argument.argument_type, + source_argument_type, + &data_connector_link, + subgraph, + object_types, + scalar_types, + object_boolean_expression_types, + boolean_expression_types, + models, + data_connector_scalars, + )?; + + // additionally typecheck literals + // we do this outside the argument resolve so that we can emit a command-specific error + // on typechecking failure + typecheck::typecheck_value_expression( + &argument.argument_type, + &argument_preset.value, + ) + .map_err(|type_error| { + Error::CommandArgumentPresetTypeError { + command_name: command.name.clone(), + argument_name: argument_preset.argument.clone(), + type_error, + } + })?; + + argument_presets.insert( + argument_preset.argument.clone(), + (argument.argument_type.clone(), value_expression), + ); + } + None => { + return Err(Error::CommandArgumentPresetMismatch { + command_name: command.name.clone(), + argument_name: argument_preset.argument.clone(), + }); + } + } + } + + let resolved_permission = CommandPermission { + allow_execution: command_permission.allow_execution, + argument_presets, + }; + validated_permissions.insert(command_permission.role.clone(), resolved_permission); + } + Ok(validated_permissions) +} diff --git a/v3/crates/metadata-resolve/src/stages/command_permissions/mod.rs b/v3/crates/metadata-resolve/src/stages/command_permissions/mod.rs index 86b1e7d4179..dada412047d 100644 --- a/v3/crates/metadata-resolve/src/stages/command_permissions/mod.rs +++ b/v3/crates/metadata-resolve/src/stages/command_permissions/mod.rs @@ -1,6 +1,4 @@ -use serde::{Deserialize, Serialize}; - -use hasura_authn_core::Role; +mod command_permission; use indexmap::IndexMap; use open_dds::{ @@ -13,29 +11,11 @@ use crate::stages::{ object_boolean_expressions, relationships, scalar_types, }; use crate::types::error::Error; -use crate::types::permission::ValueExpression; -use crate::types::subgraph::{Qualified, QualifiedTypeReference}; -use open_dds::arguments::ArgumentName; - -use crate::helpers::argument::resolve_value_expression_for_argument; - -use open_dds::permissions::CommandPermissionsV1; +use crate::types::subgraph::Qualified; use std::collections::BTreeMap; - -use crate::helpers::typecheck; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct CommandWithPermissions { - pub command: commands::Command, - pub permissions: BTreeMap, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct CommandPermission { - pub allow_execution: bool, - pub argument_presets: BTreeMap, -} +mod types; +pub use types::CommandWithPermissions; /// resolve command permissions pub fn resolve( @@ -82,7 +62,7 @@ pub fn resolve( command_name: qualified_command_name.clone(), })?; if command.permissions.is_empty() { - command.permissions = resolve_command_permissions( + command.permissions = command_permission::resolve_command_permissions( &command.command, command_permissions, object_types, @@ -102,129 +82,3 @@ pub fn resolve( } Ok(commands_with_permissions) } - -// get the ndc_models::Type for an argument if it is available -fn get_command_source_argument<'a>( - argument_name: &'a ArgumentName, - command: &'a commands::Command, -) -> Option<&'a ndc_models::Type> { - command - .source - .as_ref() - .and_then(|source| { - source - .argument_mappings - .get(argument_name) - .map(|connector_argument_name| source.source_arguments.get(connector_argument_name)) - }) - .flatten() -} - -pub fn resolve_command_permissions( - command: &commands::Command, - permissions: &CommandPermissionsV1, - object_types: &BTreeMap, relationships::ObjectTypeWithRelationships>, - scalar_types: &BTreeMap, scalar_types::ScalarTypeRepresentation>, - object_boolean_expression_types: &BTreeMap< - Qualified, - object_boolean_expressions::ObjectBooleanExpressionType, - >, - boolean_expression_types: &boolean_expressions::BooleanExpressionTypes, - models: &IndexMap, models_graphql::ModelWithGraphql>, - data_connectors: &data_connectors::DataConnectors, - data_connector_scalars: &BTreeMap< - Qualified, - data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap, - >, - subgraph: &str, -) -> Result, Error> { - let mut validated_permissions = BTreeMap::new(); - for command_permission in &permissions.permissions { - let mut argument_presets = BTreeMap::new(); - - for argument_preset in &command_permission.argument_presets { - if argument_presets.contains_key(&argument_preset.argument) { - return Err(Error::DuplicateCommandArgumentPreset { - command_name: command.name.clone(), - argument_name: argument_preset.argument.clone(), - }); - } - - let source_argument_type = - get_command_source_argument(&argument_preset.argument, command); - - let data_connector_name = command - .source - .as_ref() - .map(|source| &source.data_connector.name) - .ok_or(Error::CommandSourceRequiredForPredicate { - command_name: command.name.clone(), - })?; - - let data_connector_core_info = - data_connectors.0.get(data_connector_name).ok_or_else(|| { - Error::UnknownCommandDataConnector { - command_name: command.name.clone(), - data_connector: data_connector_name.clone(), - } - })?; - - let data_connector_link = data_connectors::DataConnectorLink::new( - data_connector_name.clone(), - &data_connector_core_info.inner, - )?; - - match command.arguments.get(&argument_preset.argument) { - Some(argument) => { - let value_expression = resolve_value_expression_for_argument( - &argument_preset.argument, - &argument_preset.value, - &argument.argument_type, - source_argument_type, - &data_connector_link, - subgraph, - object_types, - scalar_types, - object_boolean_expression_types, - boolean_expression_types, - models, - data_connector_scalars, - )?; - - // additionally typecheck literals - // we do this outside the argument resolve so that we can emit a command-specific error - // on typechecking failure - typecheck::typecheck_value_expression( - &argument.argument_type, - &argument_preset.value, - ) - .map_err(|type_error| { - Error::CommandArgumentPresetTypeError { - command_name: command.name.clone(), - argument_name: argument_preset.argument.clone(), - type_error, - } - })?; - - argument_presets.insert( - argument_preset.argument.clone(), - (argument.argument_type.clone(), value_expression), - ); - } - None => { - return Err(Error::CommandArgumentPresetMismatch { - command_name: command.name.clone(), - argument_name: argument_preset.argument.clone(), - }); - } - } - } - - let resolved_permission = CommandPermission { - allow_execution: command_permission.allow_execution, - argument_presets, - }; - validated_permissions.insert(command_permission.role.clone(), resolved_permission); - } - Ok(validated_permissions) -} diff --git a/v3/crates/metadata-resolve/src/stages/command_permissions/types.rs b/v3/crates/metadata-resolve/src/stages/command_permissions/types.rs new file mode 100644 index 00000000000..f602dcc32b1 --- /dev/null +++ b/v3/crates/metadata-resolve/src/stages/command_permissions/types.rs @@ -0,0 +1,21 @@ +use hasura_authn_core::Role; +use serde::{Deserialize, Serialize}; + +use crate::stages::commands; +use crate::types::permission::ValueExpression; +use crate::types::subgraph::QualifiedTypeReference; +use open_dds::arguments::ArgumentName; + +use std::collections::BTreeMap; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct CommandWithPermissions { + pub command: commands::Command, + pub permissions: BTreeMap, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct CommandPermission { + pub allow_execution: bool, + pub argument_presets: BTreeMap, +} diff --git a/v3/crates/metadata-resolve/src/stages/model_permissions/mod.rs b/v3/crates/metadata-resolve/src/stages/model_permissions/mod.rs index 74f3c236c82..52da3343ed4 100644 --- a/v3/crates/metadata-resolve/src/stages/model_permissions/mod.rs +++ b/v3/crates/metadata-resolve/src/stages/model_permissions/mod.rs @@ -1,8 +1,7 @@ mod types; -use crate::helpers::typecheck; use crate::stages::{ - boolean_expressions, data_connector_scalar_types, data_connectors, models, models_graphql, - object_boolean_expressions, object_types, relationships, scalar_types, + boolean_expressions, data_connector_scalar_types, data_connectors, models_graphql, + object_boolean_expressions, relationships, scalar_types, }; use indexmap::IndexMap; use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName}; @@ -11,22 +10,12 @@ pub use types::{ FilterPermission, ModelPredicate, ModelTargetSource, ModelWithPermissions, PredicateRelationshipInfo, SelectPermission, }; +mod model_permission; -use crate::helpers::argument::{ - resolve_model_predicate_with_type, resolve_value_expression_for_argument, -}; -use crate::types::error::{Error, TypePredicateError}; +use crate::types::error::Error; use crate::types::subgraph::Qualified; -use ndc_models; -use open_dds::permissions::NullableModelPredicate; -use open_dds::{ - arguments::ArgumentName, - permissions::{ModelPermissionsV1, Role}, - types::FieldName, -}; - /// resolve model permissions pub fn resolve( metadata_accessor: &open_dds::accessor::MetadataAccessor, @@ -86,7 +75,7 @@ pub fn resolve( }) .and_then(|bool_exp| bool_exp.graphql.as_ref()); - let select_permissions = resolve_model_select_permissions( + let select_permissions = model_permission::resolve_model_select_permissions( &model.model, subgraph, permissions, @@ -109,228 +98,3 @@ pub fn resolve( } Ok(models_with_permissions) } - -fn resolve_model_predicate_with_model( - model_predicate: &open_dds::permissions::ModelPredicate, - model: &models::Model, - subgraph: &str, - boolean_expression_graphql: Option<&boolean_expressions::BooleanExpressionGraphqlConfig>, - data_connectors: &data_connectors::DataConnectors, - data_connector_scalars: &BTreeMap< - Qualified, - data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap, - >, - fields: &IndexMap, - object_types: &BTreeMap, relationships::ObjectTypeWithRelationships>, - scalar_types: &BTreeMap, scalar_types::ScalarTypeRepresentation>, - boolean_expression_types: &boolean_expressions::BooleanExpressionTypes, - models: &IndexMap, models_graphql::ModelWithGraphql>, -) -> Result { - let model_source = model - .source - .clone() - .ok_or(Error::ModelSourceRequiredForPredicate { - model_name: model.name.clone(), - })?; - - let data_connector_name = &model_source.data_connector.name; - - // get available scalars defined for this data connector - let scalars = - data_connector_scalars - .get(data_connector_name) - .ok_or(Error::TypePredicateError { - type_predicate_error: TypePredicateError::UnknownTypeDataConnector { - type_name: model.data_type.clone(), - data_connector: data_connector_name.clone(), - }, - })?; - - // get the type that the expression is based on - let object_type_representation = - object_types - .get(&model.data_type) - .ok_or(Error::UnknownType { - data_type: model.data_type.clone(), - })?; - - // Get field mappings of model data type - let object_types::TypeMapping::Object { field_mappings, .. } = model_source - .type_mappings - .get(&model.data_type) - .ok_or(Error::TypeMappingRequired { - model_name: model.name.clone(), - type_name: model.data_type.clone(), - data_connector: model_source.data_connector.name.clone(), - })?; - - let data_connector_core_info = data_connectors.0.get(data_connector_name).ok_or_else(|| { - Error::UnknownModelDataConnector { - model_name: model.name.clone(), - data_connector: data_connector_name.clone(), - } - })?; - - let data_connector_link = data_connectors::DataConnectorLink::new( - data_connector_name.clone(), - &data_connector_core_info.inner, - )?; - - resolve_model_predicate_with_type( - model_predicate, - &model.data_type, - object_type_representation, - boolean_expression_graphql, - field_mappings, - &data_connector_link, - subgraph, - scalars, - object_types, - scalar_types, - boolean_expression_types, - models, - fields, - ) -} - -// get the ndc_models::Type for an argument if it is available -fn get_model_source_argument<'a>( - argument_name: &'a ArgumentName, - model: &'a models::Model, -) -> Option<&'a ndc_models::Type> { - model - .source - .as_ref() - .and_then(|source| { - source - .argument_mappings - .get(argument_name) - .map(|connector_argument_name| source.source_arguments.get(connector_argument_name)) - }) - .flatten() -} - -pub fn resolve_model_select_permissions( - model: &models::Model, - subgraph: &str, - model_permissions: &ModelPermissionsV1, - boolean_expression_graphql: Option<&boolean_expressions::BooleanExpressionGraphqlConfig>, - data_connectors: &data_connectors::DataConnectors, - data_connector_scalars: &BTreeMap< - Qualified, - data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap, - >, - object_types: &BTreeMap, relationships::ObjectTypeWithRelationships>, - scalar_types: &BTreeMap, scalar_types::ScalarTypeRepresentation>, - models: &IndexMap, models_graphql::ModelWithGraphql>, - object_boolean_expression_types: &BTreeMap< - Qualified, - object_boolean_expressions::ObjectBooleanExpressionType, - >, - boolean_expression_types: &boolean_expressions::BooleanExpressionTypes, -) -> Result, Error> { - let mut validated_permissions = BTreeMap::new(); - for model_permission in &model_permissions.permissions { - if let Some(select) = &model_permission.select { - let resolved_predicate = match &select.filter { - NullableModelPredicate::NotNull(model_predicate) => { - resolve_model_predicate_with_model( - model_predicate, - model, - subgraph, - boolean_expression_graphql, - data_connectors, - data_connector_scalars, - &model.type_fields, - object_types, - scalar_types, - boolean_expression_types, - models, - ) - .map(FilterPermission::Filter)? - } - NullableModelPredicate::Null(()) => FilterPermission::AllowAll, - }; - - let mut argument_presets = BTreeMap::new(); - - for argument_preset in &select.argument_presets { - if argument_presets.contains_key(&argument_preset.argument) { - return Err(Error::DuplicateModelArgumentPreset { - model_name: model.name.clone(), - argument_name: argument_preset.argument.clone(), - }); - } - - let source_argument_type = - get_model_source_argument(&argument_preset.argument, model); - - let data_connector_name = model - .source - .as_ref() - .map(|source| &source.data_connector.name) - .ok_or(Error::ModelSourceRequiredForPredicate { - model_name: model.name.clone(), - })?; - - let data_connector_core_info = data_connectors.0.get(data_connector_name).unwrap(); - let data_connector_link = data_connectors::DataConnectorLink::new( - data_connector_name.clone(), - &data_connector_core_info.inner, - )?; - - match model.arguments.get(&argument_preset.argument) { - Some(argument) => { - let value_expression = resolve_value_expression_for_argument( - &argument_preset.argument, - &argument_preset.value, - &argument.argument_type, - source_argument_type, - &data_connector_link, - subgraph, - object_types, - scalar_types, - object_boolean_expression_types, - boolean_expression_types, - models, - data_connector_scalars, - )?; - - // additionally typecheck literals - // we do this outside the argument resolve so that we can emit a model-specific error - // on typechecking failure - typecheck::typecheck_value_expression( - &argument.argument_type, - &argument_preset.value, - ) - .map_err(|type_error| { - Error::ModelArgumentPresetTypeError { - model_name: model.name.clone(), - argument_name: argument_preset.argument.clone(), - type_error, - } - })?; - - argument_presets.insert( - argument_preset.argument.clone(), - (argument.argument_type.clone(), value_expression), - ); - } - None => { - return Err(Error::ModelArgumentPresetMismatch { - model_name: model.name.clone(), - argument_name: argument_preset.argument.clone(), - }); - } - } - } - - let resolved_permission = SelectPermission { - filter: resolved_predicate.clone(), - argument_presets, - }; - validated_permissions.insert(model_permission.role.clone(), resolved_permission); - } - } - Ok(validated_permissions) -} diff --git a/v3/crates/metadata-resolve/src/stages/model_permissions/model_permission.rs b/v3/crates/metadata-resolve/src/stages/model_permissions/model_permission.rs new file mode 100644 index 00000000000..905f2e26d37 --- /dev/null +++ b/v3/crates/metadata-resolve/src/stages/model_permissions/model_permission.rs @@ -0,0 +1,249 @@ +use super::types::{FilterPermission, ModelPredicate, SelectPermission}; +use crate::helpers::typecheck; +use crate::stages::{ + boolean_expressions, data_connector_scalar_types, data_connectors, models, models_graphql, + object_boolean_expressions, object_types, relationships, scalar_types, +}; +use indexmap::IndexMap; +use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName}; +use std::collections::BTreeMap; + +use crate::helpers::argument::{ + resolve_model_predicate_with_type, resolve_value_expression_for_argument, +}; +use crate::types::error::{Error, TypePredicateError}; + +use crate::types::subgraph::Qualified; + +use ndc_models; +use open_dds::permissions::NullableModelPredicate; +use open_dds::{ + arguments::ArgumentName, + permissions::{ModelPermissionsV1, Role}, + types::FieldName, +}; + +fn resolve_model_predicate_with_model( + model_predicate: &open_dds::permissions::ModelPredicate, + model: &models::Model, + subgraph: &str, + boolean_expression_graphql: Option<&boolean_expressions::BooleanExpressionGraphqlConfig>, + data_connectors: &data_connectors::DataConnectors, + data_connector_scalars: &BTreeMap< + Qualified, + data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap, + >, + fields: &IndexMap, + object_types: &BTreeMap, relationships::ObjectTypeWithRelationships>, + scalar_types: &BTreeMap, scalar_types::ScalarTypeRepresentation>, + boolean_expression_types: &boolean_expressions::BooleanExpressionTypes, + models: &IndexMap, models_graphql::ModelWithGraphql>, +) -> Result { + let model_source = model + .source + .clone() + .ok_or(Error::ModelSourceRequiredForPredicate { + model_name: model.name.clone(), + })?; + + let data_connector_name = &model_source.data_connector.name; + + // get available scalars defined for this data connector + let scalars = + data_connector_scalars + .get(data_connector_name) + .ok_or(Error::TypePredicateError { + type_predicate_error: TypePredicateError::UnknownTypeDataConnector { + type_name: model.data_type.clone(), + data_connector: data_connector_name.clone(), + }, + })?; + + // get the type that the expression is based on + let object_type_representation = + object_types + .get(&model.data_type) + .ok_or(Error::UnknownType { + data_type: model.data_type.clone(), + })?; + + // Get field mappings of model data type + let object_types::TypeMapping::Object { field_mappings, .. } = model_source + .type_mappings + .get(&model.data_type) + .ok_or(Error::TypeMappingRequired { + model_name: model.name.clone(), + type_name: model.data_type.clone(), + data_connector: model_source.data_connector.name.clone(), + })?; + + let data_connector_core_info = data_connectors.0.get(data_connector_name).ok_or_else(|| { + Error::UnknownModelDataConnector { + model_name: model.name.clone(), + data_connector: data_connector_name.clone(), + } + })?; + + let data_connector_link = data_connectors::DataConnectorLink::new( + data_connector_name.clone(), + &data_connector_core_info.inner, + )?; + + resolve_model_predicate_with_type( + model_predicate, + &model.data_type, + object_type_representation, + boolean_expression_graphql, + field_mappings, + &data_connector_link, + subgraph, + scalars, + object_types, + scalar_types, + boolean_expression_types, + models, + fields, + ) +} + +// get the ndc_models::Type for an argument if it is available +fn get_model_source_argument<'a>( + argument_name: &'a ArgumentName, + model: &'a models::Model, +) -> Option<&'a ndc_models::Type> { + model + .source + .as_ref() + .and_then(|source| { + source + .argument_mappings + .get(argument_name) + .map(|connector_argument_name| source.source_arguments.get(connector_argument_name)) + }) + .flatten() +} + +pub fn resolve_model_select_permissions( + model: &models::Model, + subgraph: &str, + model_permissions: &ModelPermissionsV1, + boolean_expression_graphql: Option<&boolean_expressions::BooleanExpressionGraphqlConfig>, + data_connectors: &data_connectors::DataConnectors, + data_connector_scalars: &BTreeMap< + Qualified, + data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap, + >, + object_types: &BTreeMap, relationships::ObjectTypeWithRelationships>, + scalar_types: &BTreeMap, scalar_types::ScalarTypeRepresentation>, + models: &IndexMap, models_graphql::ModelWithGraphql>, + object_boolean_expression_types: &BTreeMap< + Qualified, + object_boolean_expressions::ObjectBooleanExpressionType, + >, + boolean_expression_types: &boolean_expressions::BooleanExpressionTypes, +) -> Result, Error> { + let mut validated_permissions = BTreeMap::new(); + for model_permission in &model_permissions.permissions { + if let Some(select) = &model_permission.select { + let resolved_predicate = match &select.filter { + NullableModelPredicate::NotNull(model_predicate) => { + resolve_model_predicate_with_model( + model_predicate, + model, + subgraph, + boolean_expression_graphql, + data_connectors, + data_connector_scalars, + &model.type_fields, + object_types, + scalar_types, + boolean_expression_types, + models, + ) + .map(FilterPermission::Filter)? + } + NullableModelPredicate::Null(()) => FilterPermission::AllowAll, + }; + + let mut argument_presets = BTreeMap::new(); + + for argument_preset in &select.argument_presets { + if argument_presets.contains_key(&argument_preset.argument) { + return Err(Error::DuplicateModelArgumentPreset { + model_name: model.name.clone(), + argument_name: argument_preset.argument.clone(), + }); + } + + let source_argument_type = + get_model_source_argument(&argument_preset.argument, model); + + let data_connector_name = model + .source + .as_ref() + .map(|source| &source.data_connector.name) + .ok_or(Error::ModelSourceRequiredForPredicate { + model_name: model.name.clone(), + })?; + + let data_connector_core_info = data_connectors.0.get(data_connector_name).unwrap(); + let data_connector_link = data_connectors::DataConnectorLink::new( + data_connector_name.clone(), + &data_connector_core_info.inner, + )?; + + match model.arguments.get(&argument_preset.argument) { + Some(argument) => { + let value_expression = resolve_value_expression_for_argument( + &argument_preset.argument, + &argument_preset.value, + &argument.argument_type, + source_argument_type, + &data_connector_link, + subgraph, + object_types, + scalar_types, + object_boolean_expression_types, + boolean_expression_types, + models, + data_connector_scalars, + )?; + + // additionally typecheck literals + // we do this outside the argument resolve so that we can emit a model-specific error + // on typechecking failure + typecheck::typecheck_value_expression( + &argument.argument_type, + &argument_preset.value, + ) + .map_err(|type_error| { + Error::ModelArgumentPresetTypeError { + model_name: model.name.clone(), + argument_name: argument_preset.argument.clone(), + type_error, + } + })?; + + argument_presets.insert( + argument_preset.argument.clone(), + (argument.argument_type.clone(), value_expression), + ); + } + None => { + return Err(Error::ModelArgumentPresetMismatch { + model_name: model.name.clone(), + argument_name: argument_preset.argument.clone(), + }); + } + } + } + + let resolved_permission = SelectPermission { + filter: resolved_predicate.clone(), + argument_presets, + }; + validated_permissions.insert(model_permission.role.clone(), resolved_permission); + } + } + Ok(validated_permissions) +}