mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-16 01:44:03 +03:00
Separate model_permissions
resolve step (#526)
<!-- Thank you for submitting this PR! :) --> ## Description Following work in https://github.com/hasura/v3-engine/pull/523 and friends, this resolves `model_permissions` in a fresh step, creating a new `ModelWithPermissions` type to differentiate from the regular `Model` which no longer has any notion of permissions. Functional no-op. V3_GIT_ORIGIN_REV_ID: fdf928ae12159a6c4ef87effc4704193c5306e46
This commit is contained in:
parent
4b7aa437f0
commit
9046cc45b4
@ -18,9 +18,10 @@ pub use stages::boolean_expressions::{
|
|||||||
pub use stages::command_permissions::CommandWithPermissions;
|
pub use stages::command_permissions::CommandWithPermissions;
|
||||||
pub use stages::commands::Command;
|
pub use stages::commands::Command;
|
||||||
pub use stages::data_connector_type_mappings::{FieldMapping, TypeMapping};
|
pub use stages::data_connector_type_mappings::{FieldMapping, TypeMapping};
|
||||||
|
pub use stages::model_permissions::{FilterPermission, ModelPredicate, ModelWithPermissions};
|
||||||
pub use stages::models::{
|
pub use stages::models::{
|
||||||
FilterPermission, Model, ModelOrderByExpression, ModelPredicate, ModelSource,
|
Model, ModelOrderByExpression, ModelSource, SelectManyGraphQlDefinition,
|
||||||
SelectManyGraphQlDefinition, SelectUniqueGraphQlDefinition,
|
SelectUniqueGraphQlDefinition,
|
||||||
};
|
};
|
||||||
/// we seem to be exporting functions. perhaps these would be better served as methods on the data
|
/// we seem to be exporting functions. perhaps these would be better served as methods on the data
|
||||||
/// types we export?
|
/// types we export?
|
||||||
|
@ -5,8 +5,8 @@ use crate::metadata::resolved::model::resolve_ndc_type;
|
|||||||
use crate::metadata::resolved::ndc_validation;
|
use crate::metadata::resolved::ndc_validation;
|
||||||
use crate::metadata::resolved::permission::ValueExpression;
|
use crate::metadata::resolved::permission::ValueExpression;
|
||||||
use crate::metadata::resolved::stages::{
|
use crate::metadata::resolved::stages::{
|
||||||
boolean_expressions, data_connector_scalar_types, data_connector_type_mappings, models,
|
boolean_expressions, data_connector_scalar_types, data_connector_type_mappings,
|
||||||
relationships, scalar_types, type_permissions,
|
model_permissions, relationships, scalar_types, type_permissions,
|
||||||
};
|
};
|
||||||
use crate::metadata::resolved::subgraph::{ArgumentInfo, Qualified};
|
use crate::metadata::resolved::subgraph::{ArgumentInfo, Qualified};
|
||||||
use crate::metadata::resolved::subgraph::{QualifiedBaseType, QualifiedTypeReference};
|
use crate::metadata::resolved::subgraph::{QualifiedBaseType, QualifiedTypeReference};
|
||||||
@ -256,7 +256,7 @@ pub(crate) fn resolve_model_predicate_with_type(
|
|||||||
subgraph: &str,
|
subgraph: &str,
|
||||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||||
fields: &IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
fields: &IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
||||||
) -> Result<models::ModelPredicate, Error> {
|
) -> Result<model_permissions::ModelPredicate, Error> {
|
||||||
match model_predicate {
|
match model_predicate {
|
||||||
permissions::ModelPredicate::FieldComparison(permissions::FieldComparisonPredicate {
|
permissions::ModelPredicate::FieldComparison(permissions::FieldComparisonPredicate {
|
||||||
field,
|
field,
|
||||||
@ -328,7 +328,7 @@ pub(crate) fn resolve_model_predicate_with_type(
|
|||||||
}),
|
}),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
Ok(models::ModelPredicate::BinaryFieldComparison {
|
Ok(model_permissions::ModelPredicate::BinaryFieldComparison {
|
||||||
field: field.clone(),
|
field: field.clone(),
|
||||||
ndc_column: field_mapping.column.clone(),
|
ndc_column: field_mapping.column.clone(),
|
||||||
operator: resolved_operator,
|
operator: resolved_operator,
|
||||||
@ -347,7 +347,7 @@ pub(crate) fn resolve_model_predicate_with_type(
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(models::ModelPredicate::UnaryFieldComparison {
|
Ok(model_permissions::ModelPredicate::UnaryFieldComparison {
|
||||||
field: field.clone(),
|
field: field.clone(),
|
||||||
ndc_column: field_mapping.column.clone(),
|
ndc_column: field_mapping.column.clone(),
|
||||||
operator: ndc_models::UnaryComparisonOperator::IsNull,
|
operator: ndc_models::UnaryComparisonOperator::IsNull,
|
||||||
@ -368,7 +368,9 @@ pub(crate) fn resolve_model_predicate_with_type(
|
|||||||
data_connectors,
|
data_connectors,
|
||||||
fields,
|
fields,
|
||||||
)?;
|
)?;
|
||||||
Ok(models::ModelPredicate::Not(Box::new(resolved_predicate)))
|
Ok(model_permissions::ModelPredicate::Not(Box::new(
|
||||||
|
resolved_predicate,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
permissions::ModelPredicate::And(predicates) => {
|
permissions::ModelPredicate::And(predicates) => {
|
||||||
let mut resolved_predicates = Vec::new();
|
let mut resolved_predicates = Vec::new();
|
||||||
@ -383,7 +385,7 @@ pub(crate) fn resolve_model_predicate_with_type(
|
|||||||
fields,
|
fields,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
Ok(models::ModelPredicate::And(resolved_predicates))
|
Ok(model_permissions::ModelPredicate::And(resolved_predicates))
|
||||||
}
|
}
|
||||||
permissions::ModelPredicate::Or(predicates) => {
|
permissions::ModelPredicate::Or(predicates) => {
|
||||||
let mut resolved_predicates = Vec::new();
|
let mut resolved_predicates = Vec::new();
|
||||||
@ -398,7 +400,7 @@ pub(crate) fn resolve_model_predicate_with_type(
|
|||||||
fields,
|
fields,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
Ok(models::ModelPredicate::Or(resolved_predicates))
|
Ok(model_permissions::ModelPredicate::Or(resolved_predicates))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,11 @@ use serde::{Deserialize, Serialize};
|
|||||||
use open_dds::{commands::CommandName, models::ModelName, types::CustomTypeName};
|
use open_dds::{commands::CommandName, models::ModelName, types::CustomTypeName};
|
||||||
|
|
||||||
use crate::metadata::resolved::error::Error;
|
use crate::metadata::resolved::error::Error;
|
||||||
use crate::metadata::resolved::model::resolve_model_select_permissions;
|
|
||||||
use crate::metadata::resolved::subgraph::Qualified;
|
use crate::metadata::resolved::subgraph::Qualified;
|
||||||
|
|
||||||
use crate::metadata::resolved::stages::{
|
use crate::metadata::resolved::stages::{
|
||||||
boolean_expressions, command_permissions, data_connector_scalar_types,
|
boolean_expressions, command_permissions, graphql_config, model_permissions, relationships,
|
||||||
data_connector_type_mappings, graphql_config, models, relationships, roles, scalar_types,
|
roles, scalar_types,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Resolved and validated metadata for a project. Used internally in the v3 server.
|
/// Resolved and validated metadata for a project. Used internally in the v3 server.
|
||||||
@ -21,7 +20,7 @@ pub struct Metadata {
|
|||||||
pub object_types:
|
pub object_types:
|
||||||
HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
pub scalar_types: HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
pub scalar_types: HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||||
pub models: IndexMap<Qualified<ModelName>, models::Model>,
|
pub models: IndexMap<Qualified<ModelName>, model_permissions::ModelWithPermissions>,
|
||||||
pub commands: IndexMap<Qualified<CommandName>, command_permissions::CommandWithPermissions>,
|
pub commands: IndexMap<Qualified<CommandName>, command_permissions::CommandWithPermissions>,
|
||||||
pub boolean_expression_types:
|
pub boolean_expression_types:
|
||||||
HashMap<Qualified<CustomTypeName>, boolean_expressions::ObjectBooleanExpressionType>,
|
HashMap<Qualified<CustomTypeName>, boolean_expressions::ObjectBooleanExpressionType>,
|
||||||
@ -33,33 +32,16 @@ pub struct Metadata {
|
|||||||
Functions to validate and resolve OpenDD spec to internal metadata
|
Functions to validate and resolve OpenDD spec to internal metadata
|
||||||
*******************/
|
*******************/
|
||||||
pub fn resolve_metadata(
|
pub fn resolve_metadata(
|
||||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
|
||||||
graphql_config: &graphql_config::GraphqlConfig,
|
graphql_config: &graphql_config::GraphqlConfig,
|
||||||
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
|
||||||
object_types: HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
object_types: HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
scalar_types: &HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
scalar_types: &HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||||
boolean_expression_types: &HashMap<
|
boolean_expression_types: &HashMap<
|
||||||
Qualified<CustomTypeName>,
|
Qualified<CustomTypeName>,
|
||||||
boolean_expressions::ObjectBooleanExpressionType,
|
boolean_expressions::ObjectBooleanExpressionType,
|
||||||
>,
|
>,
|
||||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
models: IndexMap<Qualified<ModelName>, model_permissions::ModelWithPermissions>,
|
||||||
mut models: IndexMap<Qualified<ModelName>, models::Model>,
|
|
||||||
commands: IndexMap<Qualified<CommandName>, command_permissions::CommandWithPermissions>,
|
commands: IndexMap<Qualified<CommandName>, command_permissions::CommandWithPermissions>,
|
||||||
) -> Result<Metadata, Error> {
|
) -> Result<Metadata, Error> {
|
||||||
// resolve model permissions
|
|
||||||
// Note: Model permissions's predicate can include the relationship field,
|
|
||||||
// hence Model permissions should be resolved after the relationships of a
|
|
||||||
// model is resolved.
|
|
||||||
// TODO: make this return values rather than blindly mutating it's inputs
|
|
||||||
resolve_model_permissions(
|
|
||||||
metadata_accessor,
|
|
||||||
data_connectors,
|
|
||||||
&object_types,
|
|
||||||
&mut models,
|
|
||||||
boolean_expression_types,
|
|
||||||
data_connector_type_mappings,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let roles = roles::resolve(&object_types, &models, &commands);
|
let roles = roles::resolve(&object_types, &models, &commands);
|
||||||
|
|
||||||
Ok(Metadata {
|
Ok(Metadata {
|
||||||
@ -72,60 +54,3 @@ pub fn resolve_metadata(
|
|||||||
roles,
|
roles,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// resolve model permissions
|
|
||||||
/// this currently works by mutating `models`, let's change it to
|
|
||||||
/// return new values instead where possible
|
|
||||||
fn resolve_model_permissions(
|
|
||||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
|
||||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
|
||||||
object_types: &HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
|
||||||
models: &mut IndexMap<Qualified<ModelName>, models::Model>,
|
|
||||||
boolean_expression_types: &HashMap<
|
|
||||||
Qualified<CustomTypeName>,
|
|
||||||
boolean_expressions::ObjectBooleanExpressionType,
|
|
||||||
>,
|
|
||||||
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Note: Model permissions's predicate can include the relationship field,
|
|
||||||
// hence Model permissions should be resolved after the relationships of a
|
|
||||||
// model is resolved.
|
|
||||||
for open_dds::accessor::QualifiedObject {
|
|
||||||
subgraph,
|
|
||||||
object: permissions,
|
|
||||||
} in &metadata_accessor.model_permissions
|
|
||||||
{
|
|
||||||
let model_name = Qualified::new(subgraph.to_string(), permissions.model_name.clone());
|
|
||||||
let model =
|
|
||||||
models
|
|
||||||
.get(&model_name)
|
|
||||||
.ok_or_else(|| Error::UnknownModelInModelSelectPermissions {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if model.select_permissions.is_none() {
|
|
||||||
let select_permissions = Some(resolve_model_select_permissions(
|
|
||||||
model,
|
|
||||||
subgraph,
|
|
||||||
permissions,
|
|
||||||
data_connectors,
|
|
||||||
object_types,
|
|
||||||
models, // This is required to get the model for the relationship target
|
|
||||||
boolean_expression_types,
|
|
||||||
data_connector_type_mappings,
|
|
||||||
)?);
|
|
||||||
|
|
||||||
let model = models.get_mut(&model_name).ok_or_else(|| {
|
|
||||||
Error::UnknownModelInModelSelectPermissions {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
model.select_permissions = select_permissions;
|
|
||||||
} else {
|
|
||||||
return Err(Error::DuplicateModelSelectPermission {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
@ -1,31 +1,15 @@
|
|||||||
use super::permission::ValueExpression;
|
use super::stages::data_connector_scalar_types;
|
||||||
use super::stages::{
|
|
||||||
boolean_expressions, data_connector_scalar_types, data_connector_type_mappings, models,
|
|
||||||
relationships,
|
|
||||||
};
|
|
||||||
use super::typecheck;
|
|
||||||
|
|
||||||
use crate::metadata::resolved::argument::resolve_value_expression_for_argument;
|
use crate::metadata::resolved::error::Error;
|
||||||
use crate::metadata::resolved::error::{Error, RelationshipError};
|
|
||||||
|
|
||||||
use crate::metadata::resolved::subgraph::{
|
use crate::metadata::resolved::subgraph::{
|
||||||
mk_qualified_type_name, Qualified, QualifiedBaseType, QualifiedTypeReference,
|
mk_qualified_type_name, Qualified, QualifiedBaseType, QualifiedTypeReference,
|
||||||
};
|
};
|
||||||
use crate::metadata::resolved::types::mk_name;
|
|
||||||
use crate::schema::types::output_type::relationship::{
|
|
||||||
ModelTargetSource, PredicateRelationshipAnnotation,
|
|
||||||
};
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
|
|
||||||
use ndc_models;
|
use ndc_models;
|
||||||
use open_dds::permissions::{FieldIsNullPredicate, NullableModelPredicate, RelationshipPredicate};
|
|
||||||
use open_dds::{
|
use open_dds::data_connector::DataConnectorName;
|
||||||
data_connector::DataConnectorName,
|
use std::collections::HashMap;
|
||||||
models::ModelName,
|
|
||||||
permissions::{self, ModelPermissionsV1, Role},
|
|
||||||
types::{CustomTypeName, FieldName, OperatorName},
|
|
||||||
};
|
|
||||||
use std::collections::{BTreeMap, HashMap};
|
|
||||||
|
|
||||||
// helper function to resolve ndc types to dds type based on scalar type representations
|
// helper function to resolve ndc types to dds type based on scalar type representations
|
||||||
pub(crate) fn resolve_ndc_type(
|
pub(crate) fn resolve_ndc_type(
|
||||||
@ -76,445 +60,3 @@ pub(crate) fn resolve_ndc_type(
|
|||||||
ndc_models::Type::Predicate { .. } => Err(Error::PredicateTypesUnsupported),
|
ndc_models::Type::Predicate { .. } => Err(Error::PredicateTypesUnsupported),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_model_predicate(
|
|
||||||
model_predicate: &permissions::ModelPredicate,
|
|
||||||
model: &models::Model,
|
|
||||||
subgraph: &str,
|
|
||||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
|
||||||
fields: &IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
|
||||||
object_types: &HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
|
||||||
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
|
||||||
) -> Result<models::ModelPredicate, Error> {
|
|
||||||
match model_predicate {
|
|
||||||
permissions::ModelPredicate::FieldComparison(permissions::FieldComparisonPredicate {
|
|
||||||
field,
|
|
||||||
operator,
|
|
||||||
value,
|
|
||||||
}) => {
|
|
||||||
// TODO: (anon) typecheck the value expression with the field
|
|
||||||
// TODO: resolve the "in" operator too (ndc_models::BinaryArrayComparisonOperator)
|
|
||||||
if let Some(model_source) = &model.source {
|
|
||||||
// Get field mappings of model data type
|
|
||||||
let data_connector_type_mappings::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(),
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Determine field_mapping for the predicate field
|
|
||||||
let field_mapping = field_mappings.get(field).ok_or_else(|| {
|
|
||||||
Error::UnknownFieldInSelectPermissionsDefinition {
|
|
||||||
field_name: field.clone(),
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
// Determine ndc type of the field
|
|
||||||
let field_ndc_type = &field_mapping.column_type;
|
|
||||||
|
|
||||||
// Determine whether the ndc type is a simple scalar
|
|
||||||
// Get available scalars defined in the data connector
|
|
||||||
let scalars = &data_connectors
|
|
||||||
.data_connectors_with_scalars
|
|
||||||
.get(&model_source.data_connector.name)
|
|
||||||
.ok_or(Error::UnknownModelDataConnector {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
data_connector: model_source.data_connector.name.clone(),
|
|
||||||
})?
|
|
||||||
.scalars;
|
|
||||||
|
|
||||||
// Get scalar type info from the data connector
|
|
||||||
let (_, scalar_type_info) =
|
|
||||||
data_connector_scalar_types::get_simple_scalar(field_ndc_type.clone(), scalars)
|
|
||||||
.ok_or_else(|| Error::UnsupportedFieldInSelectPermissionsPredicate {
|
|
||||||
field_name: field.clone(),
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (resolved_operator, argument_type) = resolve_binary_operator_for_model(
|
|
||||||
operator,
|
|
||||||
&model.name,
|
|
||||||
&model_source.data_connector.name,
|
|
||||||
field,
|
|
||||||
fields,
|
|
||||||
scalars,
|
|
||||||
scalar_type_info.scalar_type,
|
|
||||||
subgraph,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let value_expression = match value {
|
|
||||||
open_dds::permissions::ValueExpression::Literal(json_value) => {
|
|
||||||
Ok(ValueExpression::Literal(json_value.clone()))
|
|
||||||
}
|
|
||||||
open_dds::permissions::ValueExpression::SessionVariable(session_variable) => {
|
|
||||||
Ok(ValueExpression::SessionVariable(session_variable.clone()))
|
|
||||||
}
|
|
||||||
open_dds::permissions::ValueExpression::BooleanExpression(_model_predicate) => {
|
|
||||||
Err(Error::NestedPredicateInSelectPermissionPredicate {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
|
|
||||||
Ok(models::ModelPredicate::BinaryFieldComparison {
|
|
||||||
field: field.clone(),
|
|
||||||
ndc_column: field_mapping.column.clone(),
|
|
||||||
operator: resolved_operator,
|
|
||||||
argument_type,
|
|
||||||
value: value_expression,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(Error::ModelSourceRequiredForPredicate {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
permissions::ModelPredicate::FieldIsNull(FieldIsNullPredicate { field }) => {
|
|
||||||
if let Some(model_source) = &model.source {
|
|
||||||
// Get field mappings of model data type
|
|
||||||
let data_connector_type_mappings::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(),
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
// Determine field_mapping for the predicate field
|
|
||||||
let field_mapping = field_mappings.get(field).ok_or_else(|| {
|
|
||||||
Error::UnknownFieldInSelectPermissionsDefinition {
|
|
||||||
field_name: field.clone(),
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(models::ModelPredicate::UnaryFieldComparison {
|
|
||||||
field: field.clone(),
|
|
||||||
ndc_column: field_mapping.column.clone(),
|
|
||||||
operator: ndc_models::UnaryComparisonOperator::IsNull,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(Error::ModelSourceRequiredForPredicate {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
permissions::ModelPredicate::Relationship(RelationshipPredicate { name, predicate }) => {
|
|
||||||
if let Some(nested_predicate) = predicate {
|
|
||||||
let object_type_representation = get_model_object_type_representation(
|
|
||||||
object_types,
|
|
||||||
&model.data_type,
|
|
||||||
&model.name,
|
|
||||||
)?;
|
|
||||||
let relationship_field_name = mk_name(&name.0)?;
|
|
||||||
let relationship = &object_type_representation
|
|
||||||
.relationships
|
|
||||||
.get(&relationship_field_name)
|
|
||||||
.ok_or_else(|| Error::UnknownRelationshipInSelectPermissionsPredicate {
|
|
||||||
relationship_name: name.clone(),
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
type_name: model.data_type.clone(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
match &relationship.target {
|
|
||||||
relationships::RelationshipTarget::Command { .. } => {
|
|
||||||
Err(Error::UnsupportedFeature {
|
|
||||||
message: "Predicate cannot be built using command relationships"
|
|
||||||
.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
relationships::RelationshipTarget::Model {
|
|
||||||
model_name,
|
|
||||||
relationship_type,
|
|
||||||
target_typename,
|
|
||||||
mappings,
|
|
||||||
} => {
|
|
||||||
let target_model = models.get(model_name).ok_or_else(|| {
|
|
||||||
Error::UnknownModelUsedInRelationshipSelectPermissionsPredicate {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
target_model_name: model_name.clone(),
|
|
||||||
relationship_name: name.clone(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// predicates with relationships is currently only supported for local relationships
|
|
||||||
if let (Some(target_model_source), Some(model_source)) =
|
|
||||||
(&target_model.source, &model.source)
|
|
||||||
{
|
|
||||||
if target_model_source.data_connector.name
|
|
||||||
== model_source.data_connector.name
|
|
||||||
{
|
|
||||||
let target_source = ModelTargetSource::from_model_source(
|
|
||||||
target_model_source,
|
|
||||||
relationship,
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::RelationshipError {
|
|
||||||
relationship_error:
|
|
||||||
RelationshipError::NoRelationshipCapabilitiesDefined {
|
|
||||||
relationship_name: relationship.name.clone(),
|
|
||||||
type_name: model.data_type.clone(),
|
|
||||||
data_connector_name: target_model_source
|
|
||||||
.data_connector
|
|
||||||
.name
|
|
||||||
.clone(),
|
|
||||||
},
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let annotation = PredicateRelationshipAnnotation {
|
|
||||||
source_type: relationship.source.clone(),
|
|
||||||
relationship_name: relationship.name.clone(),
|
|
||||||
target_model_name: model_name.clone(),
|
|
||||||
target_source: target_source.clone(),
|
|
||||||
target_type: target_typename.clone(),
|
|
||||||
relationship_type: relationship_type.clone(),
|
|
||||||
mappings: mappings.clone(),
|
|
||||||
source_data_connector: model_source.data_connector.clone(),
|
|
||||||
source_type_mappings: model_source.type_mappings.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let target_model_predicate = resolve_model_predicate(
|
|
||||||
nested_predicate,
|
|
||||||
target_model,
|
|
||||||
// local relationships exists in the same subgraph as the source model
|
|
||||||
subgraph,
|
|
||||||
data_connectors,
|
|
||||||
&target_model.type_fields,
|
|
||||||
object_types,
|
|
||||||
models,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(models::ModelPredicate::Relationship {
|
|
||||||
relationship_info: annotation,
|
|
||||||
predicate: Box::new(target_model_predicate),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(Error::UnsupportedFeature {
|
|
||||||
message: "Predicate cannot be built using remote relationships"
|
|
||||||
.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(
|
|
||||||
Error::ModelAndTargetSourceRequiredForRelationshipPredicate {
|
|
||||||
source_model_name: model.name.clone(),
|
|
||||||
target_model_name: target_model.name.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(Error::NoPredicateDefinedForRelationshipPredicate {
|
|
||||||
model_name: model.name.clone(),
|
|
||||||
relationship_name: name.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
permissions::ModelPredicate::Not(predicate) => {
|
|
||||||
let resolved_predicate = resolve_model_predicate(
|
|
||||||
predicate,
|
|
||||||
model,
|
|
||||||
subgraph,
|
|
||||||
data_connectors,
|
|
||||||
fields,
|
|
||||||
object_types,
|
|
||||||
models,
|
|
||||||
)?;
|
|
||||||
Ok(models::ModelPredicate::Not(Box::new(resolved_predicate)))
|
|
||||||
}
|
|
||||||
permissions::ModelPredicate::And(predicates) => {
|
|
||||||
let mut resolved_predicates = Vec::new();
|
|
||||||
for predicate in predicates {
|
|
||||||
resolved_predicates.push(resolve_model_predicate(
|
|
||||||
predicate,
|
|
||||||
model,
|
|
||||||
subgraph,
|
|
||||||
data_connectors,
|
|
||||||
fields,
|
|
||||||
object_types,
|
|
||||||
models,
|
|
||||||
)?);
|
|
||||||
}
|
|
||||||
Ok(models::ModelPredicate::And(resolved_predicates))
|
|
||||||
}
|
|
||||||
permissions::ModelPredicate::Or(predicates) => {
|
|
||||||
let mut resolved_predicates = Vec::new();
|
|
||||||
for predicate in predicates {
|
|
||||||
resolved_predicates.push(resolve_model_predicate(
|
|
||||||
predicate,
|
|
||||||
model,
|
|
||||||
subgraph,
|
|
||||||
data_connectors,
|
|
||||||
fields,
|
|
||||||
object_types,
|
|
||||||
models,
|
|
||||||
)?);
|
|
||||||
}
|
|
||||||
Ok(models::ModelPredicate::Or(resolved_predicates))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_model_select_permissions(
|
|
||||||
model: &models::Model,
|
|
||||||
subgraph: &str,
|
|
||||||
model_permissions: &ModelPermissionsV1,
|
|
||||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
|
||||||
object_types: &HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
|
||||||
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
|
||||||
boolean_expression_types: &HashMap<
|
|
||||||
Qualified<CustomTypeName>,
|
|
||||||
boolean_expressions::ObjectBooleanExpressionType,
|
|
||||||
>,
|
|
||||||
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
|
||||||
) -> Result<HashMap<Role, models::SelectPermission>, Error> {
|
|
||||||
let mut validated_permissions = HashMap::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(
|
|
||||||
model_predicate,
|
|
||||||
model,
|
|
||||||
subgraph,
|
|
||||||
data_connectors,
|
|
||||||
&model.type_fields,
|
|
||||||
object_types,
|
|
||||||
models,
|
|
||||||
)
|
|
||||||
.map(models::FilterPermission::Filter)?,
|
|
||||||
NullableModelPredicate::Null(()) => models::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(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
subgraph,
|
|
||||||
object_types,
|
|
||||||
boolean_expression_types,
|
|
||||||
data_connectors,
|
|
||||||
data_connector_type_mappings,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// 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 = models::SelectPermission {
|
|
||||||
filter: resolved_predicate.clone(),
|
|
||||||
argument_presets,
|
|
||||||
};
|
|
||||||
validated_permissions.insert(model_permission.role.clone(), resolved_permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(validated_permissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
/// this is mostly the same code as `resolve_binary_operator_for_type`, they could probably be
|
|
||||||
/// recombined if we nest our error types better, so we don't need to specify the model name this deep
|
|
||||||
/// into the code
|
|
||||||
fn resolve_binary_operator_for_model(
|
|
||||||
operator: &OperatorName,
|
|
||||||
model_name: &Qualified<ModelName>,
|
|
||||||
data_connector: &Qualified<DataConnectorName>,
|
|
||||||
field_name: &FieldName,
|
|
||||||
fields: &IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
|
||||||
scalars: &HashMap<&str, data_connector_scalar_types::ScalarTypeWithRepresentationInfo>,
|
|
||||||
ndc_scalar_type: &ndc_models::ScalarType,
|
|
||||||
subgraph: &str,
|
|
||||||
) -> Result<(String, QualifiedTypeReference), Error> {
|
|
||||||
let field_definition =
|
|
||||||
fields
|
|
||||||
.get(field_name)
|
|
||||||
.ok_or_else(|| Error::UnknownFieldInSelectPermissionsDefinition {
|
|
||||||
field_name: field_name.clone(),
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
})?;
|
|
||||||
let comparison_operator_definition = &ndc_scalar_type
|
|
||||||
.comparison_operators
|
|
||||||
.get(&operator.0)
|
|
||||||
.ok_or_else(|| Error::InvalidOperatorInModelSelectPermission {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
operator_name: operator.clone(),
|
|
||||||
})?;
|
|
||||||
match comparison_operator_definition {
|
|
||||||
ndc_models::ComparisonOperatorDefinition::Equal => {
|
|
||||||
Ok((operator.0.clone(), field_definition.field_type.clone()))
|
|
||||||
}
|
|
||||||
ndc_models::ComparisonOperatorDefinition::In => Ok((
|
|
||||||
operator.0.clone(),
|
|
||||||
QualifiedTypeReference {
|
|
||||||
underlying_type: QualifiedBaseType::List(Box::new(
|
|
||||||
field_definition.field_type.clone(),
|
|
||||||
)),
|
|
||||||
nullable: true,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
ndc_models::ComparisonOperatorDefinition::Custom { argument_type } => Ok((
|
|
||||||
operator.0.clone(),
|
|
||||||
resolve_ndc_type(data_connector, argument_type, scalars, subgraph)?,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the `type_permissions::ObjectTypeWithPermissions` of the type identified with the
|
|
||||||
/// `data_type`, it will throw an error if the type is not found to be an object
|
|
||||||
/// or if the model has an unknown data type.
|
|
||||||
fn get_model_object_type_representation<'s>(
|
|
||||||
object_types: &'s HashMap<
|
|
||||||
Qualified<CustomTypeName>,
|
|
||||||
relationships::ObjectTypeWithRelationships,
|
|
||||||
>,
|
|
||||||
data_type: &Qualified<CustomTypeName>,
|
|
||||||
model_name: &Qualified<ModelName>,
|
|
||||||
) -> Result<&'s relationships::ObjectTypeWithRelationships, crate::metadata::resolved::error::Error>
|
|
||||||
{
|
|
||||||
match object_types.get(data_type) {
|
|
||||||
Some(object_type_representation) => Ok(object_type_representation),
|
|
||||||
None => Err(Error::UnknownModelDataType {
|
|
||||||
model_name: model_name.clone(),
|
|
||||||
data_type: data_type.clone(),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use crate::metadata::resolved::stages::models;
|
use crate::metadata::resolved::stages::model_permissions;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ValueExpression {
|
pub enum ValueExpression {
|
||||||
Literal(serde_json::Value),
|
Literal(serde_json::Value),
|
||||||
SessionVariable(open_dds::session_variables::SessionVariable),
|
SessionVariable(open_dds::session_variables::SessionVariable),
|
||||||
BooleanExpression(Box<models::ModelPredicate>),
|
BooleanExpression(Box<model_permissions::ModelPredicate>),
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ pub mod data_connector_scalar_types;
|
|||||||
pub mod data_connector_type_mappings;
|
pub mod data_connector_type_mappings;
|
||||||
pub mod data_connectors;
|
pub mod data_connectors;
|
||||||
pub mod graphql_config;
|
pub mod graphql_config;
|
||||||
|
pub mod model_permissions;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod relationships;
|
pub mod relationships;
|
||||||
pub mod roles;
|
pub mod roles;
|
||||||
@ -116,15 +117,21 @@ pub fn resolve(metadata: open_dds::Metadata) -> Result<Metadata, Error> {
|
|||||||
&data_connector_type_mappings,
|
&data_connector_type_mappings,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
resolve_metadata(
|
let models_with_permissions = model_permissions::resolve(
|
||||||
&metadata_accessor,
|
&metadata_accessor,
|
||||||
&graphql_config,
|
&data_connectors,
|
||||||
|
&object_types_with_relationships,
|
||||||
|
&models,
|
||||||
|
&boolean_expression_types,
|
||||||
&data_connector_type_mappings,
|
&data_connector_type_mappings,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
resolve_metadata(
|
||||||
|
&graphql_config,
|
||||||
object_types_with_relationships,
|
object_types_with_relationships,
|
||||||
&scalar_types,
|
&scalar_types,
|
||||||
&boolean_expression_types,
|
&boolean_expression_types,
|
||||||
&data_connectors,
|
models_with_permissions,
|
||||||
models,
|
|
||||||
commands_with_permissions,
|
commands_with_permissions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,589 @@
|
|||||||
|
mod types;
|
||||||
|
use crate::metadata::resolved::permission::ValueExpression;
|
||||||
|
use crate::metadata::resolved::stages::{
|
||||||
|
boolean_expressions, data_connector_scalar_types, data_connector_type_mappings, models,
|
||||||
|
relationships,
|
||||||
|
};
|
||||||
|
use crate::metadata::resolved::typecheck;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use open_dds::{models::ModelName, types::CustomTypeName};
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
pub use types::{FilterPermission, ModelPredicate, ModelWithPermissions, SelectPermission};
|
||||||
|
|
||||||
|
use crate::metadata::resolved::argument::resolve_value_expression_for_argument;
|
||||||
|
use crate::metadata::resolved::error::{Error, RelationshipError};
|
||||||
|
|
||||||
|
use crate::metadata::resolved::subgraph::{
|
||||||
|
mk_qualified_type_name, Qualified, QualifiedBaseType, QualifiedTypeReference,
|
||||||
|
};
|
||||||
|
use crate::metadata::resolved::types::mk_name;
|
||||||
|
use crate::schema::types::output_type::relationship::{
|
||||||
|
ModelTargetSource, PredicateRelationshipAnnotation,
|
||||||
|
};
|
||||||
|
|
||||||
|
use ndc_models;
|
||||||
|
use open_dds::permissions::{FieldIsNullPredicate, NullableModelPredicate, RelationshipPredicate};
|
||||||
|
use open_dds::{
|
||||||
|
data_connector::DataConnectorName,
|
||||||
|
permissions::{ModelPermissionsV1, Role},
|
||||||
|
types::{FieldName, OperatorName},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// resolve model permissions
|
||||||
|
pub fn resolve(
|
||||||
|
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||||
|
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||||
|
object_types: &HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
|
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
||||||
|
boolean_expression_types: &HashMap<
|
||||||
|
Qualified<CustomTypeName>,
|
||||||
|
boolean_expressions::ObjectBooleanExpressionType,
|
||||||
|
>,
|
||||||
|
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
||||||
|
) -> Result<IndexMap<Qualified<ModelName>, ModelWithPermissions>, Error> {
|
||||||
|
let mut models_with_permissions: IndexMap<Qualified<ModelName>, ModelWithPermissions> = models
|
||||||
|
.iter()
|
||||||
|
.map(|(model_name, model)| {
|
||||||
|
(
|
||||||
|
model_name.clone(),
|
||||||
|
ModelWithPermissions {
|
||||||
|
model: model.clone(),
|
||||||
|
select_permissions: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Note: Model permissions's predicate can include the relationship field,
|
||||||
|
// hence Model permissions should be resolved after the relationships of a
|
||||||
|
// model is resolved.
|
||||||
|
for open_dds::accessor::QualifiedObject {
|
||||||
|
subgraph,
|
||||||
|
object: permissions,
|
||||||
|
} in &metadata_accessor.model_permissions
|
||||||
|
{
|
||||||
|
let model_name = Qualified::new(subgraph.to_string(), permissions.model_name.clone());
|
||||||
|
let model = models_with_permissions
|
||||||
|
.get_mut(&model_name)
|
||||||
|
.ok_or_else(|| Error::UnknownModelInModelSelectPermissions {
|
||||||
|
model_name: model_name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if model.select_permissions.is_none() {
|
||||||
|
let select_permissions = Some(resolve_model_select_permissions(
|
||||||
|
&model.model,
|
||||||
|
subgraph,
|
||||||
|
permissions,
|
||||||
|
data_connectors,
|
||||||
|
object_types,
|
||||||
|
models, // This is required to get the model for the relationship target
|
||||||
|
boolean_expression_types,
|
||||||
|
data_connector_type_mappings,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
model.select_permissions = select_permissions;
|
||||||
|
} else {
|
||||||
|
return Err(Error::DuplicateModelSelectPermission {
|
||||||
|
model_name: model_name.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(models_with_permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to resolve ndc types to dds type based on scalar type representations
|
||||||
|
pub(crate) fn resolve_ndc_type(
|
||||||
|
data_connector: &Qualified<DataConnectorName>,
|
||||||
|
source_type: &ndc_models::Type,
|
||||||
|
scalars: &HashMap<&str, data_connector_scalar_types::ScalarTypeWithRepresentationInfo>,
|
||||||
|
subgraph: &str,
|
||||||
|
) -> Result<QualifiedTypeReference, Error> {
|
||||||
|
match source_type {
|
||||||
|
ndc_models::Type::Named { name } => {
|
||||||
|
let scalar_type =
|
||||||
|
scalars
|
||||||
|
.get(name.as_str())
|
||||||
|
.ok_or(Error::UnknownScalarTypeInDataConnector {
|
||||||
|
data_connector: data_connector.clone(),
|
||||||
|
scalar_type: name.clone(),
|
||||||
|
})?;
|
||||||
|
scalar_type
|
||||||
|
.representation
|
||||||
|
.clone()
|
||||||
|
.ok_or(Error::DataConnectorScalarRepresentationRequired {
|
||||||
|
data_connector: data_connector.clone(),
|
||||||
|
scalar_type: name.clone(),
|
||||||
|
})
|
||||||
|
.map(|ty| QualifiedTypeReference {
|
||||||
|
underlying_type: QualifiedBaseType::Named(mk_qualified_type_name(
|
||||||
|
&ty, subgraph,
|
||||||
|
)),
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ndc_models::Type::Nullable { underlying_type } => {
|
||||||
|
resolve_ndc_type(data_connector, underlying_type, scalars, subgraph).map(|ty| {
|
||||||
|
QualifiedTypeReference {
|
||||||
|
underlying_type: ty.underlying_type,
|
||||||
|
nullable: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ndc_models::Type::Array { element_type } => {
|
||||||
|
resolve_ndc_type(data_connector, element_type, scalars, subgraph).map(|ty| {
|
||||||
|
QualifiedTypeReference {
|
||||||
|
underlying_type: QualifiedBaseType::List(Box::new(ty)),
|
||||||
|
nullable: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ndc_models::Type::Predicate { .. } => Err(Error::PredicateTypesUnsupported),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_model_predicate(
|
||||||
|
model_predicate: &open_dds::permissions::ModelPredicate,
|
||||||
|
model: &models::Model,
|
||||||
|
subgraph: &str,
|
||||||
|
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||||
|
fields: &IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
||||||
|
object_types: &HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
|
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
||||||
|
) -> Result<ModelPredicate, Error> {
|
||||||
|
match model_predicate {
|
||||||
|
open_dds::permissions::ModelPredicate::FieldComparison(
|
||||||
|
open_dds::permissions::FieldComparisonPredicate {
|
||||||
|
field,
|
||||||
|
operator,
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
// TODO: (anon) typecheck the value expression with the field
|
||||||
|
// TODO: resolve the "in" operator too (ndc_models::BinaryArrayComparisonOperator)
|
||||||
|
if let Some(model_source) = &model.source {
|
||||||
|
// Get field mappings of model data type
|
||||||
|
let data_connector_type_mappings::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(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Determine field_mapping for the predicate field
|
||||||
|
let field_mapping = field_mappings.get(field).ok_or_else(|| {
|
||||||
|
Error::UnknownFieldInSelectPermissionsDefinition {
|
||||||
|
field_name: field.clone(),
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
// Determine ndc type of the field
|
||||||
|
let field_ndc_type = &field_mapping.column_type;
|
||||||
|
|
||||||
|
// Determine whether the ndc type is a simple scalar
|
||||||
|
// Get available scalars defined in the data connector
|
||||||
|
let scalars = &data_connectors
|
||||||
|
.data_connectors_with_scalars
|
||||||
|
.get(&model_source.data_connector.name)
|
||||||
|
.ok_or(Error::UnknownModelDataConnector {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
data_connector: model_source.data_connector.name.clone(),
|
||||||
|
})?
|
||||||
|
.scalars;
|
||||||
|
|
||||||
|
// Get scalar type info from the data connector
|
||||||
|
let (_, scalar_type_info) =
|
||||||
|
data_connector_scalar_types::get_simple_scalar(field_ndc_type.clone(), scalars)
|
||||||
|
.ok_or_else(|| Error::UnsupportedFieldInSelectPermissionsPredicate {
|
||||||
|
field_name: field.clone(),
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let (resolved_operator, argument_type) = resolve_binary_operator_for_model(
|
||||||
|
operator,
|
||||||
|
&model.name,
|
||||||
|
&model_source.data_connector.name,
|
||||||
|
field,
|
||||||
|
fields,
|
||||||
|
scalars,
|
||||||
|
scalar_type_info.scalar_type,
|
||||||
|
subgraph,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let value_expression = match value {
|
||||||
|
open_dds::permissions::ValueExpression::Literal(json_value) => {
|
||||||
|
Ok(ValueExpression::Literal(json_value.clone()))
|
||||||
|
}
|
||||||
|
open_dds::permissions::ValueExpression::SessionVariable(session_variable) => {
|
||||||
|
Ok(ValueExpression::SessionVariable(session_variable.clone()))
|
||||||
|
}
|
||||||
|
open_dds::permissions::ValueExpression::BooleanExpression(_model_predicate) => {
|
||||||
|
Err(Error::NestedPredicateInSelectPermissionPredicate {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok(ModelPredicate::BinaryFieldComparison {
|
||||||
|
field: field.clone(),
|
||||||
|
ndc_column: field_mapping.column.clone(),
|
||||||
|
operator: resolved_operator,
|
||||||
|
argument_type,
|
||||||
|
value: value_expression,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::ModelSourceRequiredForPredicate {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open_dds::permissions::ModelPredicate::FieldIsNull(FieldIsNullPredicate { field }) => {
|
||||||
|
if let Some(model_source) = &model.source {
|
||||||
|
// Get field mappings of model data type
|
||||||
|
let data_connector_type_mappings::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(),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
// Determine field_mapping for the predicate field
|
||||||
|
let field_mapping = field_mappings.get(field).ok_or_else(|| {
|
||||||
|
Error::UnknownFieldInSelectPermissionsDefinition {
|
||||||
|
field_name: field.clone(),
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(ModelPredicate::UnaryFieldComparison {
|
||||||
|
field: field.clone(),
|
||||||
|
ndc_column: field_mapping.column.clone(),
|
||||||
|
operator: ndc_models::UnaryComparisonOperator::IsNull,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::ModelSourceRequiredForPredicate {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open_dds::permissions::ModelPredicate::Relationship(RelationshipPredicate {
|
||||||
|
name,
|
||||||
|
predicate,
|
||||||
|
}) => {
|
||||||
|
if let Some(nested_predicate) = predicate {
|
||||||
|
let object_type_representation = get_model_object_type_representation(
|
||||||
|
object_types,
|
||||||
|
&model.data_type,
|
||||||
|
&model.name,
|
||||||
|
)?;
|
||||||
|
let relationship_field_name = mk_name(&name.0)?;
|
||||||
|
let relationship = &object_type_representation
|
||||||
|
.relationships
|
||||||
|
.get(&relationship_field_name)
|
||||||
|
.ok_or_else(|| Error::UnknownRelationshipInSelectPermissionsPredicate {
|
||||||
|
relationship_name: name.clone(),
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
type_name: model.data_type.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match &relationship.target {
|
||||||
|
relationships::RelationshipTarget::Command { .. } => {
|
||||||
|
Err(Error::UnsupportedFeature {
|
||||||
|
message: "Predicate cannot be built using command relationships"
|
||||||
|
.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
relationships::RelationshipTarget::Model {
|
||||||
|
model_name,
|
||||||
|
relationship_type,
|
||||||
|
target_typename,
|
||||||
|
mappings,
|
||||||
|
} => {
|
||||||
|
let target_model = models.get(model_name).ok_or_else(|| {
|
||||||
|
Error::UnknownModelUsedInRelationshipSelectPermissionsPredicate {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
target_model_name: model_name.clone(),
|
||||||
|
relationship_name: name.clone(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// predicates with relationships is currently only supported for local relationships
|
||||||
|
if let (Some(target_model_source), Some(model_source)) =
|
||||||
|
(&target_model.source, &model.source)
|
||||||
|
{
|
||||||
|
if target_model_source.data_connector.name
|
||||||
|
== model_source.data_connector.name
|
||||||
|
{
|
||||||
|
let target_source = ModelTargetSource::from_model_source(
|
||||||
|
target_model_source,
|
||||||
|
relationship,
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::RelationshipError {
|
||||||
|
relationship_error:
|
||||||
|
RelationshipError::NoRelationshipCapabilitiesDefined {
|
||||||
|
relationship_name: relationship.name.clone(),
|
||||||
|
type_name: model.data_type.clone(),
|
||||||
|
data_connector_name: target_model_source
|
||||||
|
.data_connector
|
||||||
|
.name
|
||||||
|
.clone(),
|
||||||
|
},
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let annotation = PredicateRelationshipAnnotation {
|
||||||
|
source_type: relationship.source.clone(),
|
||||||
|
relationship_name: relationship.name.clone(),
|
||||||
|
target_model_name: model_name.clone(),
|
||||||
|
target_source: target_source.clone(),
|
||||||
|
target_type: target_typename.clone(),
|
||||||
|
relationship_type: relationship_type.clone(),
|
||||||
|
mappings: mappings.clone(),
|
||||||
|
source_data_connector: model_source.data_connector.clone(),
|
||||||
|
source_type_mappings: model_source.type_mappings.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_model_predicate = resolve_model_predicate(
|
||||||
|
nested_predicate,
|
||||||
|
target_model,
|
||||||
|
// local relationships exists in the same subgraph as the source model
|
||||||
|
subgraph,
|
||||||
|
data_connectors,
|
||||||
|
&target_model.type_fields,
|
||||||
|
object_types,
|
||||||
|
models,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(ModelPredicate::Relationship {
|
||||||
|
relationship_info: annotation,
|
||||||
|
predicate: Box::new(target_model_predicate),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(Error::UnsupportedFeature {
|
||||||
|
message: "Predicate cannot be built using remote relationships"
|
||||||
|
.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(
|
||||||
|
Error::ModelAndTargetSourceRequiredForRelationshipPredicate {
|
||||||
|
source_model_name: model.name.clone(),
|
||||||
|
target_model_name: target_model.name.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error::NoPredicateDefinedForRelationshipPredicate {
|
||||||
|
model_name: model.name.clone(),
|
||||||
|
relationship_name: name.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open_dds::permissions::ModelPredicate::Not(predicate) => {
|
||||||
|
let resolved_predicate = resolve_model_predicate(
|
||||||
|
predicate,
|
||||||
|
model,
|
||||||
|
subgraph,
|
||||||
|
data_connectors,
|
||||||
|
fields,
|
||||||
|
object_types,
|
||||||
|
models,
|
||||||
|
)?;
|
||||||
|
Ok(ModelPredicate::Not(Box::new(resolved_predicate)))
|
||||||
|
}
|
||||||
|
open_dds::permissions::ModelPredicate::And(predicates) => {
|
||||||
|
let mut resolved_predicates = Vec::new();
|
||||||
|
for predicate in predicates {
|
||||||
|
resolved_predicates.push(resolve_model_predicate(
|
||||||
|
predicate,
|
||||||
|
model,
|
||||||
|
subgraph,
|
||||||
|
data_connectors,
|
||||||
|
fields,
|
||||||
|
object_types,
|
||||||
|
models,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
Ok(ModelPredicate::And(resolved_predicates))
|
||||||
|
}
|
||||||
|
open_dds::permissions::ModelPredicate::Or(predicates) => {
|
||||||
|
let mut resolved_predicates = Vec::new();
|
||||||
|
for predicate in predicates {
|
||||||
|
resolved_predicates.push(resolve_model_predicate(
|
||||||
|
predicate,
|
||||||
|
model,
|
||||||
|
subgraph,
|
||||||
|
data_connectors,
|
||||||
|
fields,
|
||||||
|
object_types,
|
||||||
|
models,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
Ok(ModelPredicate::Or(resolved_predicates))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
/// this is mostly the same code as `resolve_binary_operator_for_type`, they could probably be
|
||||||
|
/// recombined if we nest our error types better, so we don't need to specify the model name this deep
|
||||||
|
/// into the code
|
||||||
|
fn resolve_binary_operator_for_model(
|
||||||
|
operator: &OperatorName,
|
||||||
|
model_name: &Qualified<ModelName>,
|
||||||
|
data_connector: &Qualified<DataConnectorName>,
|
||||||
|
field_name: &FieldName,
|
||||||
|
fields: &IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
||||||
|
scalars: &HashMap<&str, data_connector_scalar_types::ScalarTypeWithRepresentationInfo>,
|
||||||
|
ndc_scalar_type: &ndc_models::ScalarType,
|
||||||
|
subgraph: &str,
|
||||||
|
) -> Result<(String, QualifiedTypeReference), Error> {
|
||||||
|
let field_definition =
|
||||||
|
fields
|
||||||
|
.get(field_name)
|
||||||
|
.ok_or_else(|| Error::UnknownFieldInSelectPermissionsDefinition {
|
||||||
|
field_name: field_name.clone(),
|
||||||
|
model_name: model_name.clone(),
|
||||||
|
})?;
|
||||||
|
let comparison_operator_definition = &ndc_scalar_type
|
||||||
|
.comparison_operators
|
||||||
|
.get(&operator.0)
|
||||||
|
.ok_or_else(|| Error::InvalidOperatorInModelSelectPermission {
|
||||||
|
model_name: model_name.clone(),
|
||||||
|
operator_name: operator.clone(),
|
||||||
|
})?;
|
||||||
|
match comparison_operator_definition {
|
||||||
|
ndc_models::ComparisonOperatorDefinition::Equal => {
|
||||||
|
Ok((operator.0.clone(), field_definition.field_type.clone()))
|
||||||
|
}
|
||||||
|
ndc_models::ComparisonOperatorDefinition::In => Ok((
|
||||||
|
operator.0.clone(),
|
||||||
|
QualifiedTypeReference {
|
||||||
|
underlying_type: QualifiedBaseType::List(Box::new(
|
||||||
|
field_definition.field_type.clone(),
|
||||||
|
)),
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
ndc_models::ComparisonOperatorDefinition::Custom { argument_type } => Ok((
|
||||||
|
operator.0.clone(),
|
||||||
|
resolve_ndc_type(data_connector, argument_type, scalars, subgraph)?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the `type_permissions::ObjectTypeWithPermissions` of the type identified with the
|
||||||
|
/// `data_type`, it will throw an error if the type is not found to be an object
|
||||||
|
/// or if the model has an unknown data type.
|
||||||
|
fn get_model_object_type_representation<'s>(
|
||||||
|
object_types: &'s HashMap<
|
||||||
|
Qualified<CustomTypeName>,
|
||||||
|
relationships::ObjectTypeWithRelationships,
|
||||||
|
>,
|
||||||
|
data_type: &Qualified<CustomTypeName>,
|
||||||
|
model_name: &Qualified<ModelName>,
|
||||||
|
) -> Result<&'s relationships::ObjectTypeWithRelationships, crate::metadata::resolved::error::Error>
|
||||||
|
{
|
||||||
|
match object_types.get(data_type) {
|
||||||
|
Some(object_type_representation) => Ok(object_type_representation),
|
||||||
|
None => Err(Error::UnknownModelDataType {
|
||||||
|
model_name: model_name.clone(),
|
||||||
|
data_type: data_type.clone(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_model_select_permissions(
|
||||||
|
model: &models::Model,
|
||||||
|
subgraph: &str,
|
||||||
|
model_permissions: &ModelPermissionsV1,
|
||||||
|
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||||
|
object_types: &HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
|
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
||||||
|
boolean_expression_types: &HashMap<
|
||||||
|
Qualified<CustomTypeName>,
|
||||||
|
boolean_expressions::ObjectBooleanExpressionType,
|
||||||
|
>,
|
||||||
|
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
||||||
|
) -> Result<HashMap<Role, SelectPermission>, Error> {
|
||||||
|
let mut validated_permissions = HashMap::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(
|
||||||
|
model_predicate,
|
||||||
|
model,
|
||||||
|
subgraph,
|
||||||
|
data_connectors,
|
||||||
|
&model.type_fields,
|
||||||
|
object_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(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
subgraph,
|
||||||
|
object_types,
|
||||||
|
boolean_expression_types,
|
||||||
|
data_connectors,
|
||||||
|
data_connector_type_mappings,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
use crate::metadata::resolved::permission::ValueExpression;
|
||||||
|
use crate::metadata::resolved::stages::models;
|
||||||
|
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
|
use crate::metadata::resolved::subgraph::QualifiedTypeReference;
|
||||||
|
|
||||||
|
use crate::schema::types::output_type::relationship::PredicateRelationshipAnnotation;
|
||||||
|
|
||||||
|
use ndc_models;
|
||||||
|
|
||||||
|
use open_dds::{arguments::ArgumentName, permissions::Role, types::FieldName};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
|
pub struct ModelWithPermissions {
|
||||||
|
pub model: models::Model,
|
||||||
|
pub select_permissions: Option<HashMap<Role, SelectPermission>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum FilterPermission {
|
||||||
|
AllowAll,
|
||||||
|
Filter(ModelPredicate),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SelectPermission {
|
||||||
|
pub filter: FilterPermission,
|
||||||
|
// pub allow_aggregations: bool,
|
||||||
|
pub argument_presets: BTreeMap<ArgumentName, (QualifiedTypeReference, ValueExpression)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ModelPredicate {
|
||||||
|
UnaryFieldComparison {
|
||||||
|
field: FieldName,
|
||||||
|
ndc_column: String,
|
||||||
|
operator: ndc_models::UnaryComparisonOperator,
|
||||||
|
},
|
||||||
|
BinaryFieldComparison {
|
||||||
|
field: FieldName,
|
||||||
|
ndc_column: String,
|
||||||
|
operator: String,
|
||||||
|
argument_type: QualifiedTypeReference,
|
||||||
|
value: ValueExpression,
|
||||||
|
},
|
||||||
|
Relationship {
|
||||||
|
relationship_info: PredicateRelationshipAnnotation,
|
||||||
|
predicate: Box<ModelPredicate>,
|
||||||
|
},
|
||||||
|
And(Vec<ModelPredicate>),
|
||||||
|
Or(Vec<ModelPredicate>),
|
||||||
|
Not(Box<ModelPredicate>),
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
pub use types::{
|
pub use types::{
|
||||||
FilterPermission, LimitFieldGraphqlConfig, Model, ModelGraphQlApi,
|
LimitFieldGraphqlConfig, Model, ModelGraphQlApi, ModelGraphqlApiArgumentsConfig,
|
||||||
ModelGraphqlApiArgumentsConfig, ModelOrderByExpression, ModelPredicate, ModelSource,
|
ModelOrderByExpression, ModelSource, ModelsOutput, NDCFieldSourceMapping,
|
||||||
ModelsOutput, NDCFieldSourceMapping, OffsetFieldGraphqlConfig, OrderByExpressionInfo,
|
OffsetFieldGraphqlConfig, OrderByExpressionInfo, SelectManyGraphQlDefinition,
|
||||||
SelectManyGraphQlDefinition, SelectPermission, SelectUniqueGraphQlDefinition,
|
SelectUniqueGraphQlDefinition, UniqueIdentifierField,
|
||||||
UniqueIdentifierField,
|
|
||||||
};
|
};
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
@ -356,7 +355,6 @@ fn resolve_model(
|
|||||||
arguments,
|
arguments,
|
||||||
graphql_api: ModelGraphQlApi::default(),
|
graphql_api: ModelGraphQlApi::default(),
|
||||||
source: None,
|
source: None,
|
||||||
select_permissions: None,
|
|
||||||
global_id_source,
|
global_id_source,
|
||||||
apollo_federation_key_source,
|
apollo_federation_key_source,
|
||||||
filter_expression_type,
|
filter_expression_type,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use crate::metadata::resolved::permission::ValueExpression;
|
|
||||||
use crate::metadata::resolved::stages::{
|
use crate::metadata::resolved::stages::{
|
||||||
boolean_expressions, data_connector_type_mappings, data_connectors,
|
boolean_expressions, data_connector_type_mappings, data_connectors,
|
||||||
};
|
};
|
||||||
@ -8,17 +7,14 @@ use crate::metadata::resolved::subgraph::{
|
|||||||
};
|
};
|
||||||
use crate::metadata::resolved::types::NdcColumnForComparison;
|
use crate::metadata::resolved::types::NdcColumnForComparison;
|
||||||
|
|
||||||
use crate::schema::types::output_type::relationship::PredicateRelationshipAnnotation;
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use lang_graphql::ast::common::{self as ast, Name};
|
use lang_graphql::ast::common::{self as ast, Name};
|
||||||
use ndc_models;
|
|
||||||
|
|
||||||
use open_dds::types::Deprecated;
|
use open_dds::types::Deprecated;
|
||||||
use open_dds::{
|
use open_dds::{
|
||||||
arguments::ArgumentName,
|
arguments::ArgumentName,
|
||||||
data_connector::DataConnectorName,
|
data_connector::DataConnectorName,
|
||||||
models::{ModelName, OrderableField},
|
models::{ModelName, OrderableField},
|
||||||
permissions::Role,
|
|
||||||
types::{CustomTypeName, FieldName},
|
types::{CustomTypeName, FieldName},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -104,42 +100,6 @@ pub struct ModelSource {
|
|||||||
pub argument_mappings: HashMap<ArgumentName, String>,
|
pub argument_mappings: HashMap<ArgumentName, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum FilterPermission {
|
|
||||||
AllowAll,
|
|
||||||
Filter(ModelPredicate),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct SelectPermission {
|
|
||||||
pub filter: FilterPermission,
|
|
||||||
// pub allow_aggregations: bool,
|
|
||||||
pub argument_presets: BTreeMap<ArgumentName, (QualifiedTypeReference, ValueExpression)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ModelPredicate {
|
|
||||||
UnaryFieldComparison {
|
|
||||||
field: FieldName,
|
|
||||||
ndc_column: String,
|
|
||||||
operator: ndc_models::UnaryComparisonOperator,
|
|
||||||
},
|
|
||||||
BinaryFieldComparison {
|
|
||||||
field: FieldName,
|
|
||||||
ndc_column: String,
|
|
||||||
operator: String,
|
|
||||||
argument_type: QualifiedTypeReference,
|
|
||||||
value: ValueExpression,
|
|
||||||
},
|
|
||||||
Relationship {
|
|
||||||
relationship_info: PredicateRelationshipAnnotation,
|
|
||||||
predicate: Box<ModelPredicate>,
|
|
||||||
},
|
|
||||||
And(Vec<ModelPredicate>),
|
|
||||||
Or(Vec<ModelPredicate>),
|
|
||||||
Not(Box<ModelPredicate>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
pub name: Qualified<ModelName>,
|
pub name: Qualified<ModelName>,
|
||||||
@ -149,7 +109,6 @@ pub struct Model {
|
|||||||
pub arguments: IndexMap<ArgumentName, ArgumentInfo>,
|
pub arguments: IndexMap<ArgumentName, ArgumentInfo>,
|
||||||
pub graphql_api: ModelGraphQlApi,
|
pub graphql_api: ModelGraphQlApi,
|
||||||
pub source: Option<ModelSource>,
|
pub source: Option<ModelSource>,
|
||||||
pub select_permissions: Option<HashMap<Role, SelectPermission>>,
|
|
||||||
pub global_id_source: Option<NDCFieldSourceMapping>,
|
pub global_id_source: Option<NDCFieldSourceMapping>,
|
||||||
pub apollo_federation_key_source: Option<NDCFieldSourceMapping>,
|
pub apollo_federation_key_source: Option<NDCFieldSourceMapping>,
|
||||||
pub filter_expression_type: Option<boolean_expressions::ObjectBooleanExpressionType>,
|
pub filter_expression_type: Option<boolean_expressions::ObjectBooleanExpressionType>,
|
||||||
|
@ -7,12 +7,12 @@ use open_dds::{commands::CommandName, models::ModelName, types::CustomTypeName};
|
|||||||
|
|
||||||
use crate::metadata::resolved::subgraph::Qualified;
|
use crate::metadata::resolved::subgraph::Qualified;
|
||||||
|
|
||||||
use crate::metadata::resolved::stages::{command_permissions, models, relationships};
|
use crate::metadata::resolved::stages::{command_permissions, model_permissions, relationships};
|
||||||
|
|
||||||
/// Gather all roles from various permission objects.
|
/// Gather all roles from various permission objects.
|
||||||
pub fn resolve(
|
pub fn resolve(
|
||||||
object_types: &HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
object_types: &HashMap<Qualified<CustomTypeName>, relationships::ObjectTypeWithRelationships>,
|
||||||
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
models: &IndexMap<Qualified<ModelName>, model_permissions::ModelWithPermissions>,
|
||||||
commands: &IndexMap<Qualified<CommandName>, command_permissions::CommandWithPermissions>,
|
commands: &IndexMap<Qualified<CommandName>, command_permissions::CommandWithPermissions>,
|
||||||
) -> Vec<Role> {
|
) -> Vec<Role> {
|
||||||
let mut roles = Vec::new();
|
let mut roles = Vec::new();
|
||||||
|
@ -22,10 +22,11 @@ pub fn apollo_federation_entities_schema(
|
|||||||
let entity_typename = mk_typename("_Entity")?;
|
let entity_typename = mk_typename("_Entity")?;
|
||||||
let mut entity_members = BTreeMap::new();
|
let mut entity_members = BTreeMap::new();
|
||||||
for model in gds.metadata.models.values() {
|
for model in gds.metadata.models.values() {
|
||||||
if model.apollo_federation_key_source.is_some() {
|
if model.model.apollo_federation_key_source.is_some() {
|
||||||
let object_type_representation = get_object_type_representation(gds, &model.data_type)?;
|
let object_type_representation =
|
||||||
|
get_object_type_representation(gds, &model.model.data_type)?;
|
||||||
|
|
||||||
let object_typename = get_custom_output_type(gds, builder, &model.data_type)?;
|
let object_typename = get_custom_output_type(gds, builder, &model.model.data_type)?;
|
||||||
let entity_union_permissions =
|
let entity_union_permissions =
|
||||||
permissions::get_entity_union_permissions(object_type_representation);
|
permissions::get_entity_union_permissions(object_type_representation);
|
||||||
entity_members.insert(
|
entity_members.insert(
|
||||||
|
@ -155,11 +155,11 @@ pub fn build_boolean_expression_input_schema(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let target_object_type_representation =
|
let target_object_type_representation =
|
||||||
get_object_type_representation(gds, &target_model.data_type)?;
|
get_object_type_representation(gds, &target_model.model.data_type)?;
|
||||||
|
|
||||||
// Build relationship field in filter expression only when
|
// Build relationship field in filter expression only when
|
||||||
// the target_model is backed by a source
|
// the target_model is backed by a source
|
||||||
if let Some(target_source) = &target_model.source {
|
if let Some(target_source) = &target_model.model.source {
|
||||||
let target_model_source =
|
let target_model_source =
|
||||||
ModelTargetSource::from_model_source(target_source, relationship)?;
|
ModelTargetSource::from_model_source(target_source, relationship)?;
|
||||||
|
|
||||||
@ -176,12 +176,12 @@ pub fn build_boolean_expression_input_schema(
|
|||||||
{
|
{
|
||||||
// If the relationship target model does not have filterExpressionType do not include
|
// If the relationship target model does not have filterExpressionType do not include
|
||||||
// it in the source model filter expression input type.
|
// it in the source model filter expression input type.
|
||||||
if let Some(ref target_model_filter_expression) = &target_model
|
if let Some(ref target_model_filter_expression) =
|
||||||
.clone()
|
&target_model.model.clone().filter_expression_type.and_then(
|
||||||
.filter_expression_type
|
|ref boolean_expression_type| {
|
||||||
.and_then(|ref boolean_expression_type| {
|
boolean_expression_type.clone().graphql
|
||||||
boolean_expression_type.clone().graphql
|
},
|
||||||
})
|
)
|
||||||
{
|
{
|
||||||
let target_model_filter_expression_type_name =
|
let target_model_filter_expression_type_name =
|
||||||
&target_model_filter_expression.type_name;
|
&target_model_filter_expression.type_name;
|
||||||
@ -191,7 +191,7 @@ pub fn build_boolean_expression_input_schema(
|
|||||||
relationship_name: relationship.name.clone(),
|
relationship_name: relationship.name.clone(),
|
||||||
target_source: target_model_source.clone(),
|
target_source: target_model_source.clone(),
|
||||||
target_type: target_typename.clone(),
|
target_type: target_typename.clone(),
|
||||||
target_model_name: target_model.name.clone(),
|
target_model_name: target_model.model.name.clone(),
|
||||||
relationship_type: relationship_type.clone(),
|
relationship_type: relationship_type.clone(),
|
||||||
mappings: mappings.clone(),
|
mappings: mappings.clone(),
|
||||||
source_data_connector: boolean_expression_type
|
source_data_connector: boolean_expression_type
|
||||||
|
@ -14,20 +14,21 @@ use crate::metadata::resolved::subgraph::Qualified;
|
|||||||
/// arguments fields will live.
|
/// arguments fields will live.
|
||||||
pub fn get_model_arguments_input_field(
|
pub fn get_model_arguments_input_field(
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
model: &resolved::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
) -> Result<gql_schema::InputField<GDS>, crate::schema::Error> {
|
) -> Result<gql_schema::InputField<GDS>, crate::schema::Error> {
|
||||||
model
|
model
|
||||||
|
.model
|
||||||
.graphql_api
|
.graphql_api
|
||||||
.arguments_input_config
|
.arguments_input_config
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(crate::schema::Error::NoArgumentsInputConfigForSelectMany {
|
.ok_or(crate::schema::Error::NoArgumentsInputConfigForSelectMany {
|
||||||
model_name: model.name.clone(),
|
model_name: model.model.name.clone(),
|
||||||
})
|
})
|
||||||
.map(|arguments_input_config| {
|
.map(|arguments_input_config| {
|
||||||
// This function call adds the model arguments to the
|
// This function call adds the model arguments to the
|
||||||
// `args` input object
|
// `args` input object
|
||||||
builder.register_type(TypeId::ModelArgumentsInput {
|
builder.register_type(TypeId::ModelArgumentsInput {
|
||||||
model_name: model.name.clone(),
|
model_name: model.model.name.clone(),
|
||||||
type_name: arguments_input_config.type_name.clone(),
|
type_name: arguments_input_config.type_name.clone(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,12 +50,13 @@ pub fn get_model_arguments_input_field(
|
|||||||
pub fn build_model_argument_fields(
|
pub fn build_model_argument_fields(
|
||||||
gds: &GDS,
|
gds: &GDS,
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
model: &resolved::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
BTreeMap<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
BTreeMap<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||||
crate::schema::Error,
|
crate::schema::Error,
|
||||||
> {
|
> {
|
||||||
model
|
model
|
||||||
|
.model
|
||||||
.arguments
|
.arguments
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(argument_name, argument_type)| {
|
.map(|(argument_name, argument_type)| {
|
||||||
@ -68,6 +70,7 @@ pub fn build_model_argument_fields(
|
|||||||
ModelInputAnnotation::ModelArgument {
|
ModelInputAnnotation::ModelArgument {
|
||||||
argument_type: argument_type.argument_type.clone(),
|
argument_type: argument_type.argument_type.clone(),
|
||||||
ndc_table_argument: model
|
ndc_table_argument: model
|
||||||
|
.model
|
||||||
.source
|
.source
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|model_source| {
|
.and_then(|model_source| {
|
||||||
|
@ -112,7 +112,7 @@ pub fn build_model_order_by_input_schema(
|
|||||||
model_name: model_name.clone(),
|
model_name: model_name.clone(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let object_type_representation = get_object_type_representation(gds, &model.data_type)?;
|
let object_type_representation = get_object_type_representation(gds, &model.model.data_type)?;
|
||||||
|
|
||||||
let mut fields = BTreeMap::new();
|
let mut fields = BTreeMap::new();
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ pub fn build_model_order_by_input_schema(
|
|||||||
model_name: model_name.clone(),
|
model_name: model_name.clone(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Some(model_order_by_expression) = model.graphql_api.order_by_expression.as_ref() {
|
if let Some(model_order_by_expression) = model.model.graphql_api.order_by_expression.as_ref() {
|
||||||
for (field_name, order_by_expression) in &model_order_by_expression.order_by_fields {
|
for (field_name, order_by_expression) in &model_order_by_expression.order_by_fields {
|
||||||
let graphql_field_name = mk_name(field_name.clone().0.as_str())?;
|
let graphql_field_name = mk_name(field_name.clone().0.as_str())?;
|
||||||
let input_type =
|
let input_type =
|
||||||
@ -171,12 +171,12 @@ pub fn build_model_order_by_input_schema(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let target_object_type_representation =
|
let target_object_type_representation =
|
||||||
get_object_type_representation(gds, &target_model.data_type)?;
|
get_object_type_representation(gds, &target_model.model.data_type)?;
|
||||||
|
|
||||||
// Build relationship field in filter expression only when both
|
// Build relationship field in filter expression only when both
|
||||||
// the target_model and source model are backed by a source
|
// the target_model and source model are backed by a source
|
||||||
if let (Some(target_source), Some(model_source)) =
|
if let (Some(target_source), Some(model_source)) =
|
||||||
(&target_model.source, &model.source)
|
(&target_model.model.source, &model.model.source)
|
||||||
{
|
{
|
||||||
let target_model_source =
|
let target_model_source =
|
||||||
ModelTargetSource::from_model_source(target_source, relationship)?;
|
ModelTargetSource::from_model_source(target_source, relationship)?;
|
||||||
@ -193,7 +193,7 @@ pub fn build_model_order_by_input_schema(
|
|||||||
// If the relationship target model does not have orderByExpressionType do not include
|
// If the relationship target model does not have orderByExpressionType do not include
|
||||||
// it in the source model order_by input type.
|
// it in the source model order_by input type.
|
||||||
if let Some(target_model_order_by_expression) =
|
if let Some(target_model_order_by_expression) =
|
||||||
target_model.graphql_api.order_by_expression.as_ref()
|
target_model.model.graphql_api.order_by_expression.as_ref()
|
||||||
{
|
{
|
||||||
let target_model_order_by_expression_type_name =
|
let target_model_order_by_expression_type_name =
|
||||||
&target_model_order_by_expression.order_by_type_name;
|
&target_model_order_by_expression.order_by_type_name;
|
||||||
|
@ -15,7 +15,7 @@ use super::types::ArgumentNameAndPath;
|
|||||||
|
|
||||||
/// Build namespace annotation for select permissions
|
/// Build namespace annotation for select permissions
|
||||||
pub(crate) fn get_select_permissions_namespace_annotations(
|
pub(crate) fn get_select_permissions_namespace_annotations(
|
||||||
model: &models::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
object_types: &HashMap<Qualified<CustomTypeName>, resolved::ObjectTypeWithRelationships>,
|
object_types: &HashMap<Qualified<CustomTypeName>, resolved::ObjectTypeWithRelationships>,
|
||||||
) -> Result<HashMap<Role, Option<types::NamespaceAnnotation>>, schema::Error> {
|
) -> Result<HashMap<Role, Option<types::NamespaceAnnotation>>, schema::Error> {
|
||||||
let mut permissions: HashMap<Role, Option<types::NamespaceAnnotation>> = model
|
let mut permissions: HashMap<Role, Option<types::NamespaceAnnotation>> = model
|
||||||
@ -37,6 +37,7 @@ pub(crate) fn get_select_permissions_namespace_annotations(
|
|||||||
(
|
(
|
||||||
ArgumentNameAndPath {
|
ArgumentNameAndPath {
|
||||||
ndc_argument_name: model
|
ndc_argument_name: model
|
||||||
|
.model
|
||||||
.source
|
.source
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|model_source| {
|
.and_then(|model_source| {
|
||||||
@ -61,9 +62,9 @@ pub(crate) fn get_select_permissions_namespace_annotations(
|
|||||||
// them to model argument preset annotations as well. if there is no
|
// them to model argument preset annotations as well. if there is no
|
||||||
// source defined for the model, we don't generate these preset
|
// source defined for the model, we don't generate these preset
|
||||||
// annotations.
|
// annotations.
|
||||||
if let Some(model_source) = model.source.as_ref() {
|
if let Some(model_source) = model.model.source.as_ref() {
|
||||||
let mut role_presets_map = HashMap::new();
|
let mut role_presets_map = HashMap::new();
|
||||||
for (arg_name, arg_info) in &model.arguments {
|
for (arg_name, arg_info) in &model.model.arguments {
|
||||||
// get the NDC argument name of this command source
|
// get the NDC argument name of this command source
|
||||||
let ndc_argument_name = model_source.argument_mappings.get(arg_name).cloned();
|
let ndc_argument_name = model_source.argument_mappings.get(arg_name).cloned();
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ pub(crate) fn get_select_permissions_namespace_annotations(
|
|||||||
/// This is different from generating permissions for select_many etc,
|
/// This is different from generating permissions for select_many etc,
|
||||||
/// as we need to check the permissions of the arguments used in the selection.
|
/// as we need to check the permissions of the arguments used in the selection.
|
||||||
pub(crate) fn get_select_one_namespace_annotations(
|
pub(crate) fn get_select_one_namespace_annotations(
|
||||||
model: &models::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
object_type_representation: &resolved::ObjectTypeWithRelationships,
|
object_type_representation: &resolved::ObjectTypeWithRelationships,
|
||||||
select_unique: &models::SelectUniqueGraphQlDefinition,
|
select_unique: &models::SelectUniqueGraphQlDefinition,
|
||||||
object_types: &HashMap<Qualified<CustomTypeName>, resolved::ObjectTypeWithRelationships>,
|
object_types: &HashMap<Qualified<CustomTypeName>, resolved::ObjectTypeWithRelationships>,
|
||||||
@ -121,7 +122,7 @@ pub(crate) fn get_select_one_namespace_annotations(
|
|||||||
/// We need to check the permissions of the source and target fields
|
/// We need to check the permissions of the source and target fields
|
||||||
/// in the relationship mappings.
|
/// in the relationship mappings.
|
||||||
pub(crate) fn get_model_relationship_namespace_annotations(
|
pub(crate) fn get_model_relationship_namespace_annotations(
|
||||||
target_model: &models::Model,
|
target_model: &resolved::ModelWithPermissions,
|
||||||
source_object_type_representation: &resolved::ObjectTypeWithRelationships,
|
source_object_type_representation: &resolved::ObjectTypeWithRelationships,
|
||||||
target_object_type_representation: &resolved::ObjectTypeWithRelationships,
|
target_object_type_representation: &resolved::ObjectTypeWithRelationships,
|
||||||
mappings: &[resolved::RelationshipModelMapping],
|
mappings: &[resolved::RelationshipModelMapping],
|
||||||
@ -439,7 +440,7 @@ pub(crate) fn get_allowed_roles_for_field<'a>(
|
|||||||
/// Builds namespace annotations for the `node` field.
|
/// Builds namespace annotations for the `node` field.
|
||||||
pub(crate) fn get_node_field_namespace_permissions(
|
pub(crate) fn get_node_field_namespace_permissions(
|
||||||
object_type_representation: &resolved::ObjectTypeWithRelationships,
|
object_type_representation: &resolved::ObjectTypeWithRelationships,
|
||||||
model: &models::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
) -> HashMap<Role, resolved::FilterPermission> {
|
) -> HashMap<Role, resolved::FilterPermission> {
|
||||||
let mut permissions = HashMap::new();
|
let mut permissions = HashMap::new();
|
||||||
|
|
||||||
@ -478,7 +479,7 @@ pub(crate) fn get_node_field_namespace_permissions(
|
|||||||
/// Builds namespace annotations for the `_entities` field.
|
/// Builds namespace annotations for the `_entities` field.
|
||||||
pub(crate) fn get_entities_field_namespace_permissions(
|
pub(crate) fn get_entities_field_namespace_permissions(
|
||||||
object_type_representation: &resolved::ObjectTypeWithRelationships,
|
object_type_representation: &resolved::ObjectTypeWithRelationships,
|
||||||
model: &models::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
) -> HashMap<Role, resolved::FilterPermission> {
|
) -> HashMap<Role, resolved::FilterPermission> {
|
||||||
let mut permissions = HashMap::new();
|
let mut permissions = HashMap::new();
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ pub fn query_root_schema(
|
|||||||
) -> Result<gql_schema::Object<GDS>, crate::schema::Error> {
|
) -> Result<gql_schema::Object<GDS>, crate::schema::Error> {
|
||||||
let mut fields = BTreeMap::new();
|
let mut fields = BTreeMap::new();
|
||||||
for model in gds.metadata.models.values() {
|
for model in gds.metadata.models.values() {
|
||||||
for select_unique in model.graphql_api.select_uniques.iter() {
|
for select_unique in model.model.graphql_api.select_uniques.iter() {
|
||||||
let (field_name, field) = select_one::select_one_field(
|
let (field_name, field) = select_one::select_one_field(
|
||||||
gds,
|
gds,
|
||||||
builder,
|
builder,
|
||||||
@ -34,7 +34,7 @@ pub fn query_root_schema(
|
|||||||
)?;
|
)?;
|
||||||
fields.insert(field_name, field);
|
fields.insert(field_name, field);
|
||||||
}
|
}
|
||||||
for select_many in model.graphql_api.select_many.iter() {
|
for select_many in model.model.graphql_api.select_many.iter() {
|
||||||
let (field_name, field) = select_many::select_many_field(
|
let (field_name, field) = select_many::select_many_field(
|
||||||
gds,
|
gds,
|
||||||
builder,
|
builder,
|
||||||
|
@ -40,25 +40,27 @@ pub(crate) fn apollo_federation_field(
|
|||||||
> = HashMap::new();
|
> = HashMap::new();
|
||||||
let mut typename_mappings = HashMap::new();
|
let mut typename_mappings = HashMap::new();
|
||||||
for model in gds.metadata.models.values() {
|
for model in gds.metadata.models.values() {
|
||||||
if let Some(apollo_federation_key_source) = &model.apollo_federation_key_source {
|
if let Some(apollo_federation_key_source) = &model.model.apollo_federation_key_source {
|
||||||
let output_typename = get_custom_output_type(gds, builder, &model.data_type)?;
|
let output_typename = get_custom_output_type(gds, builder, &model.model.data_type)?;
|
||||||
|
|
||||||
let object_type_representation = get_object_type_representation(gds, &model.data_type)?;
|
let object_type_representation =
|
||||||
|
get_object_type_representation(gds, &model.model.data_type)?;
|
||||||
|
|
||||||
let entities_field_permissions =
|
let entities_field_permissions =
|
||||||
get_entities_field_namespace_permissions(object_type_representation, model);
|
get_entities_field_namespace_permissions(object_type_representation, model);
|
||||||
|
|
||||||
for (role, model_predicate) in entities_field_permissions.iter() {
|
for (role, model_predicate) in entities_field_permissions.iter() {
|
||||||
let role_type_permissions = roles_type_permissions.entry(role.clone()).or_default();
|
let role_type_permissions = roles_type_permissions.entry(role.clone()).or_default();
|
||||||
role_type_permissions.insert(model.data_type.clone(), model_predicate.clone());
|
role_type_permissions
|
||||||
|
.insert(model.model.data_type.clone(), model_predicate.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if typename_mappings
|
if typename_mappings
|
||||||
.insert(
|
.insert(
|
||||||
output_typename.type_name().clone(),
|
output_typename.type_name().clone(),
|
||||||
EntityFieldTypeNameMapping {
|
EntityFieldTypeNameMapping {
|
||||||
type_name: model.data_type.clone(),
|
type_name: model.model.data_type.clone(),
|
||||||
model_source: model.source.clone(),
|
model_source: model.model.source.clone(),
|
||||||
key_fields_ndc_mapping: apollo_federation_key_source.ndc_mapping.clone(),
|
key_fields_ndc_mapping: apollo_federation_key_source.ndc_mapping.clone(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -56,25 +56,27 @@ pub(crate) fn relay_node_field(
|
|||||||
HashMap<Qualified<CustomTypeName>, resolved::FilterPermission>,
|
HashMap<Qualified<CustomTypeName>, resolved::FilterPermission>,
|
||||||
> = HashMap::new();
|
> = HashMap::new();
|
||||||
for model in gds.metadata.models.values() {
|
for model in gds.metadata.models.values() {
|
||||||
if let Some(global_id_source) = &model.global_id_source {
|
if let Some(global_id_source) = &model.model.global_id_source {
|
||||||
let output_typename = get_custom_output_type(gds, builder, &model.data_type)?;
|
let output_typename = get_custom_output_type(gds, builder, &model.model.data_type)?;
|
||||||
|
|
||||||
let object_type_representation = get_object_type_representation(gds, &model.data_type)?;
|
let object_type_representation =
|
||||||
|
get_object_type_representation(gds, &model.model.data_type)?;
|
||||||
|
|
||||||
let node_field_permissions =
|
let node_field_permissions =
|
||||||
get_node_field_namespace_permissions(object_type_representation, model);
|
get_node_field_namespace_permissions(object_type_representation, model);
|
||||||
|
|
||||||
for (role, model_predicate) in node_field_permissions.iter() {
|
for (role, model_predicate) in node_field_permissions.iter() {
|
||||||
let role_type_permissions = roles_type_permissions.entry(role.clone()).or_default();
|
let role_type_permissions = roles_type_permissions.entry(role.clone()).or_default();
|
||||||
role_type_permissions.insert(model.data_type.clone(), model_predicate.clone());
|
role_type_permissions
|
||||||
|
.insert(model.model.data_type.clone(), model_predicate.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if typename_mappings
|
if typename_mappings
|
||||||
.insert(
|
.insert(
|
||||||
output_typename.type_name().clone(),
|
output_typename.type_name().clone(),
|
||||||
NodeFieldTypeNameMapping {
|
NodeFieldTypeNameMapping {
|
||||||
type_name: model.data_type.clone(),
|
type_name: model.model.data_type.clone(),
|
||||||
model_source: model.source.clone(),
|
model_source: model.model.source.clone(),
|
||||||
global_id_fields_ndc_mapping: global_id_source.ndc_mapping.clone(),
|
global_id_fields_ndc_mapping: global_id_source.ndc_mapping.clone(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,7 @@ use crate::schema::{
|
|||||||
/// limit, offset, order_by and where.
|
/// limit, offset, order_by and where.
|
||||||
pub(crate) fn generate_select_many_arguments(
|
pub(crate) fn generate_select_many_arguments(
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
model: &resolved::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
BTreeMap<Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
BTreeMap<Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||||
crate::schema::Error,
|
crate::schema::Error,
|
||||||
@ -30,7 +30,7 @@ pub(crate) fn generate_select_many_arguments(
|
|||||||
let mut arguments = BTreeMap::new();
|
let mut arguments = BTreeMap::new();
|
||||||
|
|
||||||
// insert limit argument
|
// insert limit argument
|
||||||
if let Some(limit_field) = &model.graphql_api.limit_field {
|
if let Some(limit_field) = &model.model.graphql_api.limit_field {
|
||||||
let limit_argument = generate_int_input_argument(
|
let limit_argument = generate_int_input_argument(
|
||||||
limit_field.field_name.as_str(),
|
limit_field.field_name.as_str(),
|
||||||
Annotation::Input(types::InputAnnotation::Model(
|
Annotation::Input(types::InputAnnotation::Model(
|
||||||
@ -44,7 +44,7 @@ pub(crate) fn generate_select_many_arguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// insert offset argument
|
// insert offset argument
|
||||||
if let Some(offset_field) = &model.graphql_api.offset_field {
|
if let Some(offset_field) = &model.model.graphql_api.offset_field {
|
||||||
let offset_argument = generate_int_input_argument(
|
let offset_argument = generate_int_input_argument(
|
||||||
offset_field.field_name.as_str(),
|
offset_field.field_name.as_str(),
|
||||||
Annotation::Input(types::InputAnnotation::Model(
|
Annotation::Input(types::InputAnnotation::Model(
|
||||||
@ -59,11 +59,11 @@ pub(crate) fn generate_select_many_arguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate and insert order_by argument
|
// generate and insert order_by argument
|
||||||
if let Some(order_by_expression_info) = &model.graphql_api.order_by_expression {
|
if let Some(order_by_expression_info) = &model.model.graphql_api.order_by_expression {
|
||||||
let order_by_argument = {
|
let order_by_argument = {
|
||||||
get_order_by_expression_input_field(
|
get_order_by_expression_input_field(
|
||||||
builder,
|
builder,
|
||||||
model.name.clone(),
|
model.model.name.clone(),
|
||||||
order_by_expression_info,
|
order_by_expression_info,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@ -75,7 +75,7 @@ pub(crate) fn generate_select_many_arguments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate and insert where argument
|
// generate and insert where argument
|
||||||
if let Some(boolean_expression_type) = &model.filter_expression_type {
|
if let Some(boolean_expression_type) = &model.model.filter_expression_type {
|
||||||
if let Some(boolean_expression) = &boolean_expression_type.graphql {
|
if let Some(boolean_expression) = &boolean_expression_type.graphql {
|
||||||
let where_argument = get_where_expression_input_field(
|
let where_argument = get_where_expression_input_field(
|
||||||
builder,
|
builder,
|
||||||
@ -97,7 +97,7 @@ pub(crate) fn generate_select_many_arguments(
|
|||||||
pub(crate) fn select_many_field(
|
pub(crate) fn select_many_field(
|
||||||
gds: &GDS,
|
gds: &GDS,
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
model: &resolved::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
select_many: &resolved::SelectManyGraphQlDefinition,
|
select_many: &resolved::SelectManyGraphQlDefinition,
|
||||||
parent_type: &ast::TypeName,
|
parent_type: &ast::TypeName,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
@ -112,7 +112,7 @@ pub(crate) fn select_many_field(
|
|||||||
|
|
||||||
// Generate the `args` input object and add the model
|
// Generate the `args` input object and add the model
|
||||||
// arguments within it.
|
// arguments within it.
|
||||||
if !model.arguments.is_empty() {
|
if !model.model.arguments.is_empty() {
|
||||||
let model_arguments_input =
|
let model_arguments_input =
|
||||||
model_arguments::get_model_arguments_input_field(builder, model)?;
|
model_arguments::get_model_arguments_input_field(builder, model)?;
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ pub(crate) fn select_many_field(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let field_type = ast::TypeContainer::list_null(ast::TypeContainer::named_non_null(
|
let field_type = ast::TypeContainer::list_null(ast::TypeContainer::named_non_null(
|
||||||
get_custom_output_type(gds, builder, &model.data_type)?,
|
get_custom_output_type(gds, builder, &model.model.data_type)?,
|
||||||
));
|
));
|
||||||
|
|
||||||
let field = builder.conditional_namespaced(
|
let field = builder.conditional_namespaced(
|
||||||
@ -145,10 +145,10 @@ pub(crate) fn select_many_field(
|
|||||||
select_many.description.clone(),
|
select_many.description.clone(),
|
||||||
Annotation::Output(types::OutputAnnotation::RootField(
|
Annotation::Output(types::OutputAnnotation::RootField(
|
||||||
types::RootFieldAnnotation::Model {
|
types::RootFieldAnnotation::Model {
|
||||||
data_type: model.data_type.clone(),
|
data_type: model.model.data_type.clone(),
|
||||||
source: model.source.clone(),
|
source: model.model.source.clone(),
|
||||||
kind: types::RootFieldKind::SelectMany,
|
kind: types::RootFieldKind::SelectMany,
|
||||||
name: model.name.clone(),
|
name: model.model.name.clone(),
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
field_type,
|
field_type,
|
||||||
|
@ -21,7 +21,7 @@ use crate::schema::{
|
|||||||
pub(crate) fn select_one_field(
|
pub(crate) fn select_one_field(
|
||||||
gds: &GDS,
|
gds: &GDS,
|
||||||
builder: &mut gql_schema::Builder<GDS>,
|
builder: &mut gql_schema::Builder<GDS>,
|
||||||
model: &resolved::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
select_unique: &resolved::SelectUniqueGraphQlDefinition,
|
select_unique: &resolved::SelectUniqueGraphQlDefinition,
|
||||||
parent_type: &ast::TypeName,
|
parent_type: &ast::TypeName,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
@ -70,8 +70,8 @@ pub(crate) fn select_one_field(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let object_type_representation = get_object_type_representation(gds, &model.data_type)?;
|
let object_type_representation = get_object_type_representation(gds, &model.model.data_type)?;
|
||||||
let output_typename = get_custom_output_type(gds, builder, &model.data_type)?;
|
let output_typename = get_custom_output_type(gds, builder, &model.model.data_type)?;
|
||||||
|
|
||||||
let field_annotations = permissions::get_select_one_namespace_annotations(
|
let field_annotations = permissions::get_select_one_namespace_annotations(
|
||||||
model,
|
model,
|
||||||
@ -86,10 +86,10 @@ pub(crate) fn select_one_field(
|
|||||||
select_unique.description.clone(),
|
select_unique.description.clone(),
|
||||||
Annotation::Output(types::OutputAnnotation::RootField(
|
Annotation::Output(types::OutputAnnotation::RootField(
|
||||||
types::RootFieldAnnotation::Model {
|
types::RootFieldAnnotation::Model {
|
||||||
data_type: model.data_type.clone(),
|
data_type: model.model.data_type.clone(),
|
||||||
source: model.source.clone(),
|
source: model.model.source.clone(),
|
||||||
kind: types::RootFieldKind::SelectOne,
|
kind: types::RootFieldKind::SelectOne,
|
||||||
name: model.name.clone(),
|
name: model.model.name.clone(),
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
ast::TypeContainer::named_null(output_typename),
|
ast::TypeContainer::named_null(output_typename),
|
||||||
|
@ -23,10 +23,11 @@ pub fn node_interface_schema(
|
|||||||
let mut roles_implementing_global_id: HashMap<Role, Option<types::NamespaceAnnotation>> =
|
let mut roles_implementing_global_id: HashMap<Role, Option<types::NamespaceAnnotation>> =
|
||||||
HashMap::new();
|
HashMap::new();
|
||||||
for model in gds.metadata.models.values() {
|
for model in gds.metadata.models.values() {
|
||||||
if model.global_id_source.is_some() {
|
if model.model.global_id_source.is_some() {
|
||||||
let object_type_representation = get_object_type_representation(gds, &model.data_type)?;
|
let object_type_representation =
|
||||||
|
get_object_type_representation(gds, &model.model.data_type)?;
|
||||||
|
|
||||||
let object_typename = get_custom_output_type(gds, builder, &model.data_type)?;
|
let object_typename = get_custom_output_type(gds, builder, &model.model.data_type)?;
|
||||||
|
|
||||||
let node_interface_annotations =
|
let node_interface_annotations =
|
||||||
permissions::get_node_interface_annotations(object_type_representation);
|
permissions::get_node_interface_annotations(object_type_representation);
|
||||||
@ -43,7 +44,7 @@ pub fn node_interface_schema(
|
|||||||
// Multiple models can be backed by the same type
|
// Multiple models can be backed by the same type
|
||||||
typename_global_id_mappings.insert(
|
typename_global_id_mappings.insert(
|
||||||
object_typename.type_name().clone(),
|
object_typename.type_name().clone(),
|
||||||
model.global_id_fields.clone(),
|
model.model.global_id_fields.clone(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,7 +316,7 @@ fn object_type_fields(
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !model.arguments.is_empty() {
|
if !model.model.arguments.is_empty() {
|
||||||
return Err(Error::InternalUnsupported {
|
return Err(Error::InternalUnsupported {
|
||||||
summary: "Relationships to models with arguments aren't supported".into(),
|
summary: "Relationships to models with arguments aren't supported".into(),
|
||||||
});
|
});
|
||||||
@ -330,7 +330,7 @@ fn object_type_fields(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let target_object_type_representation =
|
let target_object_type_representation =
|
||||||
get_object_type_representation(gds, &model.data_type)?;
|
get_object_type_representation(gds, &model.model.data_type)?;
|
||||||
|
|
||||||
builder.conditional_namespaced(
|
builder.conditional_namespaced(
|
||||||
gql_schema::Field::<GDS>::new(
|
gql_schema::Field::<GDS>::new(
|
||||||
|
@ -94,10 +94,11 @@ pub struct ModelTargetSource {
|
|||||||
|
|
||||||
impl ModelTargetSource {
|
impl ModelTargetSource {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
model: &models::Model,
|
model: &resolved::ModelWithPermissions,
|
||||||
relationship: &resolved::Relationship,
|
relationship: &resolved::Relationship,
|
||||||
) -> Result<Option<Self>, schema::Error> {
|
) -> Result<Option<Self>, schema::Error> {
|
||||||
model
|
model
|
||||||
|
.model
|
||||||
.source
|
.source
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|model_source| Self::from_model_source(model_source, relationship))
|
.map(|model_source| Self::from_model_source(model_source, relationship))
|
||||||
|
Loading…
Reference in New Issue
Block a user