Tidy model_permissions and command_permissions stages (#775)

<!-- The PR description should answer 2 (maybe 3) important questions:
-->

### What

Much like https://github.com/hasura/v3-engine/pull/774, we split the big
files into separate modules. Functional no-op.

<!-- What is this PR trying to accomplish (and why, if it's not
obvious)? -->

<!-- Consider: do we need to add a changelog entry? -->

### How

Copy pasta, `just fix-local`, mostly.

<!-- How is it trying to accomplish it (what are the implementation
steps)? -->

V3_GIT_ORIGIN_REV_ID: ba7e40057583f98df54573c72663a4a2d2c4a4ab
This commit is contained in:
Daniel Harvey 2024-06-27 17:29:58 +01:00 committed by hasura-bot
parent 9232af913a
commit b31d6099d7
5 changed files with 426 additions and 392 deletions

View File

@ -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<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
object_boolean_expression_types: &BTreeMap<
Qualified<CustomTypeName>,
object_boolean_expressions::ObjectBooleanExpressionType,
>,
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
data_connectors: &data_connectors::DataConnectors,
data_connector_scalars: &BTreeMap<
Qualified<DataConnectorName>,
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
>,
subgraph: &str,
) -> Result<BTreeMap<Role, CommandPermission>, 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)
}

View File

@ -1,6 +1,4 @@
use serde::{Deserialize, Serialize}; mod command_permission;
use hasura_authn_core::Role;
use indexmap::IndexMap; use indexmap::IndexMap;
use open_dds::{ use open_dds::{
@ -13,29 +11,11 @@ use crate::stages::{
object_boolean_expressions, relationships, scalar_types, object_boolean_expressions, relationships, scalar_types,
}; };
use crate::types::error::Error; use crate::types::error::Error;
use crate::types::permission::ValueExpression; use crate::types::subgraph::Qualified;
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 std::collections::BTreeMap; use std::collections::BTreeMap;
mod types;
use crate::helpers::typecheck; pub use types::CommandWithPermissions;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CommandWithPermissions {
pub command: commands::Command,
pub permissions: BTreeMap<Role, CommandPermission>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CommandPermission {
pub allow_execution: bool,
pub argument_presets: BTreeMap<ArgumentName, (QualifiedTypeReference, ValueExpression)>,
}
/// resolve command permissions /// resolve command permissions
pub fn resolve( pub fn resolve(
@ -82,7 +62,7 @@ pub fn resolve(
command_name: qualified_command_name.clone(), command_name: qualified_command_name.clone(),
})?; })?;
if command.permissions.is_empty() { if command.permissions.is_empty() {
command.permissions = resolve_command_permissions( command.permissions = command_permission::resolve_command_permissions(
&command.command, &command.command,
command_permissions, command_permissions,
object_types, object_types,
@ -102,129 +82,3 @@ pub fn resolve(
} }
Ok(commands_with_permissions) 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<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
object_boolean_expression_types: &BTreeMap<
Qualified<CustomTypeName>,
object_boolean_expressions::ObjectBooleanExpressionType,
>,
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
data_connectors: &data_connectors::DataConnectors,
data_connector_scalars: &BTreeMap<
Qualified<DataConnectorName>,
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
>,
subgraph: &str,
) -> Result<BTreeMap<Role, CommandPermission>, 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)
}

View File

@ -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<Role, CommandPermission>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct CommandPermission {
pub allow_execution: bool,
pub argument_presets: BTreeMap<ArgumentName, (QualifiedTypeReference, ValueExpression)>,
}

View File

@ -1,8 +1,7 @@
mod types; mod types;
use crate::helpers::typecheck;
use crate::stages::{ use crate::stages::{
boolean_expressions, data_connector_scalar_types, data_connectors, models, models_graphql, boolean_expressions, data_connector_scalar_types, data_connectors, models_graphql,
object_boolean_expressions, object_types, relationships, scalar_types, object_boolean_expressions, relationships, scalar_types,
}; };
use indexmap::IndexMap; use indexmap::IndexMap;
use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName}; use open_dds::{data_connector::DataConnectorName, models::ModelName, types::CustomTypeName};
@ -11,22 +10,12 @@ pub use types::{
FilterPermission, ModelPredicate, ModelTargetSource, ModelWithPermissions, FilterPermission, ModelPredicate, ModelTargetSource, ModelWithPermissions,
PredicateRelationshipInfo, SelectPermission, PredicateRelationshipInfo, SelectPermission,
}; };
mod model_permission;
use crate::helpers::argument::{ use crate::types::error::Error;
resolve_model_predicate_with_type, resolve_value_expression_for_argument,
};
use crate::types::error::{Error, TypePredicateError};
use crate::types::subgraph::Qualified; 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 /// resolve model permissions
pub fn resolve( pub fn resolve(
metadata_accessor: &open_dds::accessor::MetadataAccessor, metadata_accessor: &open_dds::accessor::MetadataAccessor,
@ -86,7 +75,7 @@ pub fn resolve(
}) })
.and_then(|bool_exp| bool_exp.graphql.as_ref()); .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, &model.model,
subgraph, subgraph,
permissions, permissions,
@ -109,228 +98,3 @@ pub fn resolve(
} }
Ok(models_with_permissions) 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<DataConnectorName>,
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
>,
fields: &IndexMap<FieldName, object_types::FieldDefinition>,
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
) -> Result<ModelPredicate, Error> {
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<DataConnectorName>,
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
>,
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
object_boolean_expression_types: &BTreeMap<
Qualified<CustomTypeName>,
object_boolean_expressions::ObjectBooleanExpressionType,
>,
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
) -> Result<BTreeMap<Role, SelectPermission>, 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)
}

View File

@ -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<DataConnectorName>,
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
>,
fields: &IndexMap<FieldName, object_types::FieldDefinition>,
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
) -> Result<ModelPredicate, Error> {
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<DataConnectorName>,
data_connector_scalar_types::ScalarTypeWithRepresentationInfoMap,
>,
object_types: &BTreeMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
scalar_types: &BTreeMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
models: &IndexMap<Qualified<ModelName>, models_graphql::ModelWithGraphql>,
object_boolean_expression_types: &BTreeMap<
Qualified<CustomTypeName>,
object_boolean_expressions::ObjectBooleanExpressionType,
>,
boolean_expression_types: &boolean_expressions::BooleanExpressionTypes,
) -> Result<BTreeMap<Role, SelectPermission>, 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)
}