mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
Separate models
metadata resolve step (#519)
<!-- Thank you for submitting this PR! :) --> ## Description As per https://github.com/hasura/v3-engine/pull/483 and all PRs proceeding it, this moves the resolving of Models into a discreet metadata stage. The `resolve::model` module was a bit of a dumping ground, so I have tried to find more sensible homes for a lot of things, but some things remain (model permissions) and will be homed when sorting later stages. Functional no-op. V3_GIT_ORIGIN_REV_ID: b41bcc9f413a867f21dd72b5d7affee8d55e02df
This commit is contained in:
parent
0eb062d168
commit
8a9bfaaa6b
@ -50,10 +50,10 @@ pub struct ModelSelection<'s> {
|
||||
pub(crate) fn model_selection_ir<'s>(
|
||||
selection_set: &normalized_ast::SelectionSet<'s, GDS>,
|
||||
data_type: &Qualified<CustomTypeName>,
|
||||
model_source: &'s resolved::model::ModelSource,
|
||||
model_source: &'s resolved::ModelSource,
|
||||
arguments: BTreeMap<String, ndc_models::Argument>,
|
||||
mut filter_clauses: ResolvedFilterExpression<'s>,
|
||||
permissions_predicate: &'s resolved::model::FilterPermission,
|
||||
permissions_predicate: &'s resolved::FilterPermission,
|
||||
limit: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
order_by: Option<ResolvedOrderBy<'s>>,
|
||||
@ -61,8 +61,8 @@ pub(crate) fn model_selection_ir<'s>(
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ModelSelection<'s>, error::Error> {
|
||||
match permissions_predicate {
|
||||
resolved::model::FilterPermission::AllowAll => {}
|
||||
resolved::model::FilterPermission::Filter(predicate) => {
|
||||
resolved::FilterPermission::AllowAll => {}
|
||||
resolved::FilterPermission::Filter(predicate) => {
|
||||
let permissions_predicate_relationship_paths = Vec::new();
|
||||
let mut permissions_predicate_relationships = BTreeMap::new();
|
||||
let processed_model_perdicate = permissions::process_model_predicate(
|
||||
|
@ -24,7 +24,7 @@ use super::selection_set::NDCRelationshipName;
|
||||
/// is not found, then an error will be thrown.
|
||||
pub(crate) fn get_select_filter_predicate<'s>(
|
||||
field_call: &normalized_ast::FieldCall<'s, GDS>,
|
||||
) -> Result<&'s resolved::model::FilterPermission, Error> {
|
||||
) -> Result<&'s resolved::FilterPermission, Error> {
|
||||
field_call
|
||||
.info
|
||||
.namespaced
|
||||
@ -75,14 +75,14 @@ pub(crate) fn get_argument_presets(
|
||||
}
|
||||
|
||||
pub(crate) fn process_model_predicate<'s>(
|
||||
model_predicate: &'s resolved::model::ModelPredicate,
|
||||
model_predicate: &'s resolved::ModelPredicate,
|
||||
session_variables: &SessionVariables,
|
||||
mut relationship_paths: Vec<NDCRelationshipName>,
|
||||
relationships: &mut BTreeMap<NDCRelationshipName, LocalModelRelationshipInfo<'s>>,
|
||||
usage_counts: &mut UsagesCounts,
|
||||
) -> Result<ndc_models::Expression, Error> {
|
||||
match model_predicate {
|
||||
resolved::model::ModelPredicate::UnaryFieldComparison {
|
||||
resolved::ModelPredicate::UnaryFieldComparison {
|
||||
field: _,
|
||||
ndc_column,
|
||||
operator,
|
||||
@ -91,7 +91,7 @@ pub(crate) fn process_model_predicate<'s>(
|
||||
*operator,
|
||||
&relationship_paths,
|
||||
)?),
|
||||
resolved::model::ModelPredicate::BinaryFieldComparison {
|
||||
resolved::ModelPredicate::BinaryFieldComparison {
|
||||
field: _,
|
||||
ndc_column,
|
||||
argument_type,
|
||||
@ -105,7 +105,7 @@ pub(crate) fn process_model_predicate<'s>(
|
||||
session_variables,
|
||||
&relationship_paths,
|
||||
)?),
|
||||
resolved::model::ModelPredicate::Not(predicate) => {
|
||||
resolved::ModelPredicate::Not(predicate) => {
|
||||
let expr = process_model_predicate(
|
||||
predicate,
|
||||
session_variables,
|
||||
@ -117,7 +117,7 @@ pub(crate) fn process_model_predicate<'s>(
|
||||
expression: Box::new(expr),
|
||||
})
|
||||
}
|
||||
resolved::model::ModelPredicate::And(predicates) => {
|
||||
resolved::ModelPredicate::And(predicates) => {
|
||||
let exprs = predicates
|
||||
.iter()
|
||||
.map(|p| {
|
||||
@ -132,7 +132,7 @@ pub(crate) fn process_model_predicate<'s>(
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
Ok(ndc_models::Expression::And { expressions: exprs })
|
||||
}
|
||||
resolved::model::ModelPredicate::Or(predicates) => {
|
||||
resolved::ModelPredicate::Or(predicates) => {
|
||||
let exprs = predicates
|
||||
.iter()
|
||||
.map(|p| {
|
||||
@ -147,7 +147,7 @@ pub(crate) fn process_model_predicate<'s>(
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
Ok(ndc_models::Expression::Or { expressions: exprs })
|
||||
}
|
||||
resolved::model::ModelPredicate::Relationship {
|
||||
resolved::ModelPredicate::Relationship {
|
||||
relationship_info,
|
||||
predicate,
|
||||
} => {
|
||||
|
@ -169,7 +169,7 @@ fn generate_type_field_ir<'n, 's>(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn generate_model_rootfield_ir<'n, 's>(
|
||||
type_name: &ast::TypeName,
|
||||
source: &'s Option<resolved::model::ModelSource>,
|
||||
source: &'s Option<resolved::ModelSource>,
|
||||
data_type: &subgraph::Qualified<CustomTypeName>,
|
||||
kind: &RootFieldKind,
|
||||
field: &'n gql::normalized_ast::Field<'s, GDS>,
|
||||
|
@ -38,7 +38,7 @@ pub struct EntitySelect<'n, 's> {
|
||||
fn get_entity_namespace_typename_mappings<'s>(
|
||||
field_call: &normalized_ast::FieldCall<'s, GDS>,
|
||||
) -> Result<
|
||||
&'s HashMapWithJsonKey<Qualified<CustomTypeName>, resolved::model::FilterPermission>,
|
||||
&'s HashMapWithJsonKey<Qualified<CustomTypeName>, resolved::FilterPermission>,
|
||||
error::Error,
|
||||
> {
|
||||
field_call
|
||||
@ -115,7 +115,7 @@ pub(crate) fn entities_ir<'n, 's>(
|
||||
// Get the permissions for the typename
|
||||
let typename_permissions: &'s HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
resolved::model::FilterPermission,
|
||||
resolved::FilterPermission,
|
||||
> = &get_entity_namespace_typename_mappings(field_call)?.0;
|
||||
let typename_mapping = typename_mappings.get(&typename).ok_or(
|
||||
error::InternalDeveloperError::TypenameMappingNotFound {
|
||||
|
@ -42,7 +42,7 @@ pub struct NodeSelect<'n, 's> {
|
||||
fn get_relay_node_namespace_typename_mappings<'s>(
|
||||
field_call: &normalized_ast::FieldCall<'s, GDS>,
|
||||
) -> Result<
|
||||
&'s HashMapWithJsonKey<Qualified<CustomTypeName>, resolved::model::FilterPermission>,
|
||||
&'s HashMapWithJsonKey<Qualified<CustomTypeName>, resolved::FilterPermission>,
|
||||
error::Error,
|
||||
> {
|
||||
field_call
|
||||
@ -89,10 +89,8 @@ pub(crate) fn relay_node_ir<'n, 's>(
|
||||
decoding_error: e.to_string(),
|
||||
})?;
|
||||
let global_id: GlobalID = serde_json::from_slice(decoded_id_value.as_slice())?;
|
||||
let typename_permissions: &'s HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
resolved::model::FilterPermission,
|
||||
> = &get_relay_node_namespace_typename_mappings(field_call)?.0;
|
||||
let typename_permissions: &'s HashMap<Qualified<CustomTypeName>, resolved::FilterPermission> =
|
||||
&get_relay_node_namespace_typename_mappings(field_call)?.0;
|
||||
let typename_mapping = typename_mappings.get(&global_id.typename).ok_or(
|
||||
error::InternalDeveloperError::TypenameMappingNotFound {
|
||||
type_name: global_id.typename.clone(),
|
||||
|
@ -46,7 +46,7 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
|
||||
field: &'n normalized_ast::Field<'s, GDS>,
|
||||
field_call: &'n normalized_ast::FieldCall<'s, GDS>,
|
||||
data_type: &Qualified<open_dds::types::CustomTypeName>,
|
||||
model_source: &'s resolved::model::ModelSource,
|
||||
model_source: &'s resolved::ModelSource,
|
||||
session_variables: &SessionVariables,
|
||||
model_name: &'s Qualified<open_dds::models::ModelName>,
|
||||
) -> Result<ModelSelectMany<'n, 's>, error::Error> {
|
||||
|
@ -45,7 +45,7 @@ pub(crate) fn select_one_generate_ir<'n, 's>(
|
||||
field: &'n normalized_ast::Field<'s, GDS>,
|
||||
field_call: &'s normalized_ast::FieldCall<'s, GDS>,
|
||||
data_type: &Qualified<open_dds::types::CustomTypeName>,
|
||||
model_source: &'s resolved::model::ModelSource,
|
||||
model_source: &'s resolved::ModelSource,
|
||||
session_variables: &SessionVariables,
|
||||
model_name: &'s Qualified<open_dds::models::ModelName>,
|
||||
) -> Result<ModelSelectOne<'n, 's>, error::Error> {
|
||||
|
@ -18,4 +18,8 @@ pub use stages::boolean_expressions::{
|
||||
BooleanExpressionInfo, ComparisonExpressionInfo, ObjectBooleanExpressionType,
|
||||
};
|
||||
pub use stages::data_connector_type_mappings::{FieldMapping, TypeMapping};
|
||||
pub use stages::models::{
|
||||
FilterPermission, Model, ModelOrderByExpression, ModelPredicate, ModelSource,
|
||||
SelectManyGraphQlDefinition, SelectUniqueGraphQlDefinition,
|
||||
};
|
||||
pub use stages::resolve;
|
||||
|
@ -1,13 +1,15 @@
|
||||
use crate::metadata::resolved::error::{Error, TypeError, TypeMappingValidationError};
|
||||
use crate::metadata::resolved::error::{
|
||||
Error, TypeError, TypeMappingValidationError, TypePredicateError,
|
||||
};
|
||||
use crate::metadata::resolved::model::resolve_ndc_type;
|
||||
use crate::metadata::resolved::ndc_validation;
|
||||
use crate::metadata::resolved::permission::ValueExpression;
|
||||
use crate::metadata::resolved::stages::{
|
||||
boolean_expressions, data_connector_scalar_types, data_connector_type_mappings, scalar_types,
|
||||
type_permissions,
|
||||
boolean_expressions, data_connector_scalar_types, data_connector_type_mappings, models,
|
||||
scalar_types, type_permissions,
|
||||
};
|
||||
use crate::metadata::resolved::subgraph::{ArgumentInfo, Qualified};
|
||||
|
||||
use crate::metadata::resolved::permission::ValueExpression;
|
||||
use crate::metadata::resolved::subgraph::QualifiedTypeReference;
|
||||
use crate::metadata::resolved::subgraph::{QualifiedBaseType, QualifiedTypeReference};
|
||||
use crate::metadata::resolved::types::{
|
||||
get_object_type_for_boolean_expression, get_type_representation, unwrap_custom_type_name,
|
||||
TypeMappingToCollect, TypeRepresentation,
|
||||
@ -15,7 +17,9 @@ use crate::metadata::resolved::types::{
|
||||
use indexmap::IndexMap;
|
||||
use ndc_models;
|
||||
use open_dds::arguments::ArgumentName;
|
||||
use open_dds::types::CustomTypeName;
|
||||
use open_dds::data_connector::DataConnectorName;
|
||||
use open_dds::permissions;
|
||||
use open_dds::types::{CustomTypeName, FieldName, OperatorName};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use thiserror::Error;
|
||||
@ -223,7 +227,7 @@ pub(crate) fn resolve_value_expression_for_argument(
|
||||
},
|
||||
})?;
|
||||
|
||||
let resolved_model_predicate = super::model::resolve_model_predicate_with_type(
|
||||
let resolved_model_predicate = resolve_model_predicate_with_type(
|
||||
bool_exp,
|
||||
base_type,
|
||||
data_connector_field_mappings,
|
||||
@ -239,3 +243,210 @@ pub(crate) fn resolve_value_expression_for_argument(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// a simplified version of resolve_model_predicate that only requires a type rather than an entire
|
||||
/// Model for context. It skips relationships for simplicity, this should be simple enough to
|
||||
/// re-add in future. Because this function takes the `data_connector_field_mappings` as an input,
|
||||
/// many of the errors thrown in `resolve_model_predicate` are pushed out.
|
||||
pub(crate) fn resolve_model_predicate_with_type(
|
||||
model_predicate: &permissions::ModelPredicate,
|
||||
type_name: &Qualified<CustomTypeName>,
|
||||
data_connector_field_mappings: &BTreeMap<FieldName, data_connector_type_mappings::FieldMapping>,
|
||||
data_connector_name: &Qualified<DataConnectorName>,
|
||||
subgraph: &str,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
fields: &IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
||||
) -> 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)
|
||||
|
||||
// Determine field_mapping for the predicate field
|
||||
let field_mapping = data_connector_field_mappings.get(field).ok_or_else(|| {
|
||||
Error::TypePredicateError {
|
||||
type_predicate_error: TypePredicateError::UnknownFieldInTypePredicate {
|
||||
field_name: field.clone(),
|
||||
type_name: type_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(data_connector_name)
|
||||
.ok_or(Error::TypePredicateError {
|
||||
type_predicate_error: TypePredicateError::UnknownTypeDataConnector {
|
||||
type_name: type_name.clone(),
|
||||
data_connector: 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::TypePredicateError {
|
||||
type_predicate_error: TypePredicateError::UnsupportedFieldInTypePredicate {
|
||||
field_name: field.clone(),
|
||||
type_name: type_name.clone(),
|
||||
},
|
||||
})?;
|
||||
|
||||
let (resolved_operator, argument_type) = resolve_binary_operator_for_type(
|
||||
operator,
|
||||
type_name,
|
||||
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(
|
||||
_inner_model_predicate,
|
||||
) => Err(Error::TypePredicateError {
|
||||
type_predicate_error: TypePredicateError::NestedPredicateInTypePredicate {
|
||||
type_name: type_name.clone(),
|
||||
},
|
||||
}),
|
||||
}?;
|
||||
|
||||
Ok(models::ModelPredicate::BinaryFieldComparison {
|
||||
field: field.clone(),
|
||||
ndc_column: field_mapping.column.clone(),
|
||||
operator: resolved_operator,
|
||||
argument_type,
|
||||
value: value_expression,
|
||||
})
|
||||
}
|
||||
permissions::ModelPredicate::FieldIsNull(permissions::FieldIsNullPredicate { field }) => {
|
||||
// Determine field_mapping for the predicate field
|
||||
let field_mapping = data_connector_field_mappings.get(field).ok_or_else(|| {
|
||||
Error::TypePredicateError {
|
||||
type_predicate_error: TypePredicateError::UnknownFieldInTypePredicate {
|
||||
field_name: field.clone(),
|
||||
type_name: type_name.clone(),
|
||||
},
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(models::ModelPredicate::UnaryFieldComparison {
|
||||
field: field.clone(),
|
||||
ndc_column: field_mapping.column.clone(),
|
||||
operator: ndc_models::UnaryComparisonOperator::IsNull,
|
||||
})
|
||||
}
|
||||
permissions::ModelPredicate::Relationship(permissions::RelationshipPredicate {
|
||||
..
|
||||
}) => Err(Error::UnsupportedFeature {
|
||||
message: "relationships not supported in type predicates".to_string(),
|
||||
}),
|
||||
permissions::ModelPredicate::Not(predicate) => {
|
||||
let resolved_predicate = resolve_model_predicate_with_type(
|
||||
predicate,
|
||||
type_name,
|
||||
data_connector_field_mappings,
|
||||
data_connector_name,
|
||||
subgraph,
|
||||
data_connectors,
|
||||
fields,
|
||||
)?;
|
||||
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_with_type(
|
||||
predicate,
|
||||
type_name,
|
||||
data_connector_field_mappings,
|
||||
data_connector_name,
|
||||
subgraph,
|
||||
data_connectors,
|
||||
fields,
|
||||
)?);
|
||||
}
|
||||
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_with_type(
|
||||
predicate,
|
||||
type_name,
|
||||
data_connector_field_mappings,
|
||||
data_connector_name,
|
||||
subgraph,
|
||||
data_connectors,
|
||||
fields,
|
||||
)?);
|
||||
}
|
||||
Ok(models::ModelPredicate::Or(resolved_predicates))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn resolve_binary_operator_for_type(
|
||||
operator: &OperatorName,
|
||||
type_name: &Qualified<CustomTypeName>,
|
||||
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::TypePredicateError {
|
||||
type_predicate_error: TypePredicateError::UnknownFieldInTypePredicate {
|
||||
field_name: field_name.clone(),
|
||||
type_name: type_name.clone(),
|
||||
},
|
||||
})?;
|
||||
let comparison_operator_definition = &ndc_scalar_type
|
||||
.comparison_operators
|
||||
.get(&operator.0)
|
||||
.ok_or_else(|| Error::TypePredicateError {
|
||||
type_predicate_error: TypePredicateError::InvalidOperatorInTypePredicate {
|
||||
type_name: type_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)?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,20 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use hasura_authn_core::Role;
|
||||
use indexmap::IndexMap;
|
||||
use lang_graphql::ast::common as ast;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use open_dds::{commands::CommandName, models::ModelName, types::CustomTypeName};
|
||||
|
||||
use crate::metadata::resolved::command;
|
||||
|
||||
use crate::metadata::resolved::error::Error;
|
||||
use crate::metadata::resolved::model::{
|
||||
resolve_model, resolve_model_graphql_api, resolve_model_select_permissions,
|
||||
resolve_model_source, Model,
|
||||
};
|
||||
use crate::metadata::resolved::model::resolve_model_select_permissions;
|
||||
use crate::metadata::resolved::relationship::resolve_relationship;
|
||||
use crate::metadata::resolved::subgraph::Qualified;
|
||||
|
||||
use crate::metadata::resolved::stages::{
|
||||
boolean_expressions, data_connector_scalar_types, data_connector_type_mappings, graphql_config,
|
||||
scalar_types, type_permissions,
|
||||
models, scalar_types, type_permissions,
|
||||
};
|
||||
|
||||
/// Resolved and validated metadata for a project. Used internally in the v3 server.
|
||||
@ -28,7 +23,7 @@ pub struct Metadata {
|
||||
pub object_types:
|
||||
HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
pub scalar_types: HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
pub models: IndexMap<Qualified<ModelName>, Model>,
|
||||
pub models: IndexMap<Qualified<ModelName>, models::Model>,
|
||||
pub commands: IndexMap<Qualified<CommandName>, command::Command>,
|
||||
pub boolean_expression_types:
|
||||
HashMap<Qualified<CustomTypeName>, boolean_expressions::ObjectBooleanExpressionType>,
|
||||
@ -42,9 +37,8 @@ pub struct Metadata {
|
||||
pub fn resolve_metadata(
|
||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
mut existing_graphql_types: HashSet<ast::TypeName>,
|
||||
mut global_id_enabled_types: HashMap<Qualified<CustomTypeName>, Vec<Qualified<ModelName>>>,
|
||||
mut apollo_federation_entity_enabled_types: HashMap<
|
||||
global_id_enabled_types: HashMap<Qualified<CustomTypeName>, Vec<Qualified<ModelName>>>,
|
||||
apollo_federation_entity_enabled_types: HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
Option<Qualified<open_dds::models::ModelName>>,
|
||||
>,
|
||||
@ -56,22 +50,8 @@ pub fn resolve_metadata(
|
||||
boolean_expressions::ObjectBooleanExpressionType,
|
||||
>,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
mut models: IndexMap<Qualified<ModelName>, models::Model>,
|
||||
) -> Result<Metadata, Error> {
|
||||
// resolve models
|
||||
// TODO: validate types
|
||||
let mut models = resolve_models(
|
||||
metadata_accessor,
|
||||
data_connectors,
|
||||
data_connector_type_mappings,
|
||||
&object_types,
|
||||
scalar_types,
|
||||
&mut existing_graphql_types,
|
||||
&mut global_id_enabled_types,
|
||||
&mut apollo_federation_entity_enabled_types,
|
||||
boolean_expression_types,
|
||||
graphql_config,
|
||||
)?;
|
||||
|
||||
// To check if global_id_fields are defined in object type but no model has global_id_source set to true:
|
||||
// - Throw an error if no model with globalIdSource:true is found for the object type.
|
||||
for (object_type, model_name_list) in global_id_enabled_types {
|
||||
@ -149,7 +129,7 @@ pub fn resolve_metadata(
|
||||
/// Gather all roles from various permission objects.
|
||||
fn collect_all_roles(
|
||||
object_types: &HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
models: &IndexMap<Qualified<ModelName>, Model>,
|
||||
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
||||
commands: &IndexMap<Qualified<CommandName>, command::Command>,
|
||||
) -> Vec<Role> {
|
||||
let mut roles = Vec::new();
|
||||
@ -228,94 +208,6 @@ fn resolve_commands(
|
||||
Ok(commands)
|
||||
}
|
||||
|
||||
/// resolve models
|
||||
fn resolve_models(
|
||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
||||
object_types: &HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
scalar_types: &HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
existing_graphql_types: &mut HashSet<ast::TypeName>,
|
||||
global_id_enabled_types: &mut HashMap<Qualified<CustomTypeName>, Vec<Qualified<ModelName>>>,
|
||||
apollo_federation_entity_enabled_types: &mut HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
Option<Qualified<open_dds::models::ModelName>>,
|
||||
>,
|
||||
boolean_expression_types: &HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
boolean_expressions::ObjectBooleanExpressionType,
|
||||
>,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
) -> Result<IndexMap<Qualified<ModelName>, Model>, Error> {
|
||||
// resolve models
|
||||
// TODO: validate types
|
||||
let mut models = IndexMap::new();
|
||||
let mut global_id_models = HashMap::new();
|
||||
|
||||
for open_dds::accessor::QualifiedObject {
|
||||
subgraph,
|
||||
object: model,
|
||||
} in &metadata_accessor.models
|
||||
{
|
||||
let mut resolved_model = resolve_model(
|
||||
subgraph,
|
||||
model,
|
||||
object_types,
|
||||
global_id_enabled_types,
|
||||
apollo_federation_entity_enabled_types,
|
||||
boolean_expression_types,
|
||||
)?;
|
||||
if resolved_model.global_id_source.is_some() {
|
||||
match global_id_models.insert(
|
||||
resolved_model.data_type.clone(),
|
||||
resolved_model.name.clone(),
|
||||
) {
|
||||
None => {}
|
||||
Some(duplicate_model_name) => {
|
||||
return Err(Error::DuplicateModelGlobalIdSource {
|
||||
model_1: resolved_model.name,
|
||||
model_2: duplicate_model_name,
|
||||
object_type: resolved_model.data_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(model_source) = &model.source {
|
||||
resolve_model_source(
|
||||
model_source,
|
||||
&mut resolved_model,
|
||||
subgraph,
|
||||
data_connectors,
|
||||
object_types,
|
||||
scalar_types,
|
||||
data_connector_type_mappings,
|
||||
boolean_expression_types,
|
||||
)?;
|
||||
}
|
||||
if let Some(model_graphql_definition) = &model.graphql {
|
||||
resolve_model_graphql_api(
|
||||
model_graphql_definition,
|
||||
&mut resolved_model,
|
||||
existing_graphql_types,
|
||||
data_connectors,
|
||||
&model.description,
|
||||
graphql_config,
|
||||
)?;
|
||||
}
|
||||
let qualified_model_name = Qualified::new(subgraph.to_string(), model.name.clone());
|
||||
if models
|
||||
.insert(qualified_model_name.clone(), resolved_model)
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::DuplicateModelDefinition {
|
||||
name: qualified_model_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(models)
|
||||
}
|
||||
|
||||
/// resolve relationships
|
||||
/// returns updated `types` value
|
||||
fn resolve_relationships(
|
||||
@ -325,7 +217,7 @@ fn resolve_relationships(
|
||||
Qualified<CustomTypeName>,
|
||||
type_permissions::ObjectTypeWithPermissions,
|
||||
>,
|
||||
models: &IndexMap<Qualified<ModelName>, Model>,
|
||||
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
||||
commands: &IndexMap<Qualified<CommandName>, command::Command>,
|
||||
) -> Result<HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>, Error>
|
||||
{
|
||||
@ -423,7 +315,7 @@ fn resolve_model_permissions(
|
||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
object_types: &HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
models: &mut IndexMap<Qualified<ModelName>, Model>,
|
||||
models: &mut IndexMap<Qualified<ModelName>, models::Model>,
|
||||
boolean_expression_types: &HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
boolean_expressions::ObjectBooleanExpressionType,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
use crate::metadata::resolved::stages::models;
|
||||
use ndc_models;
|
||||
use open_dds::{
|
||||
commands::{CommandName, DataConnectorCommand, FunctionName, ProcedureName},
|
||||
@ -9,7 +10,6 @@ use thiserror::Error;
|
||||
|
||||
use super::{
|
||||
command::Command,
|
||||
model::Model,
|
||||
subgraph::{Qualified, QualifiedBaseType, QualifiedTypeName, QualifiedTypeReference},
|
||||
};
|
||||
|
||||
@ -131,7 +131,7 @@ fn get_underlying_type_name(output_type: &QualifiedTypeReference) -> &QualifiedT
|
||||
|
||||
pub fn validate_ndc(
|
||||
model_name: &Qualified<ModelName>,
|
||||
model: &Model,
|
||||
model: &models::Model,
|
||||
schema: &ndc_models::SchemaResponse,
|
||||
) -> std::result::Result<(), NDCValidationError> {
|
||||
let model_source = match &model.source {
|
||||
|
@ -1,9 +1,9 @@
|
||||
use super::model::ModelPredicate;
|
||||
use crate::metadata::resolved::stages::models;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ValueExpression {
|
||||
Literal(serde_json::Value),
|
||||
SessionVariable(open_dds::session_variables::SessionVariable),
|
||||
BooleanExpression(Box<ModelPredicate>),
|
||||
BooleanExpression(Box<models::ModelPredicate>),
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
use super::command::Command;
|
||||
|
||||
use super::error::{Error, RelationshipError};
|
||||
use super::model::get_ndc_column_for_comparison;
|
||||
use super::model::Model;
|
||||
use super::stages::{data_connector_scalar_types, data_connectors};
|
||||
use super::stages::{data_connector_scalar_types, data_connectors, models, type_permissions};
|
||||
use super::subgraph::Qualified;
|
||||
use super::subgraph::QualifiedTypeReference;
|
||||
use super::types::mk_name;
|
||||
@ -13,7 +11,6 @@ use lang_graphql::ast::common as ast;
|
||||
use open_dds::arguments::ArgumentName;
|
||||
use open_dds::commands::CommandName;
|
||||
|
||||
use crate::metadata::resolved::stages::type_permissions;
|
||||
use open_dds::models::ModelName;
|
||||
use open_dds::relationships::{
|
||||
self, FieldAccess, RelationshipName, RelationshipType, RelationshipV1,
|
||||
@ -155,7 +152,7 @@ fn resolve_relationship_mappings_model(
|
||||
relationship: &RelationshipV1,
|
||||
source_type_name: &Qualified<CustomTypeName>,
|
||||
source_type: &type_permissions::ObjectTypeWithPermissions,
|
||||
target_model: &Model,
|
||||
target_model: &models::Model,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
) -> Result<Vec<RelationshipModelMapping>, Error> {
|
||||
let mut resolved_relationship_mappings = Vec::new();
|
||||
@ -219,7 +216,7 @@ fn resolve_relationship_mappings_model(
|
||||
.source
|
||||
.as_ref()
|
||||
.map(|target_model_source| {
|
||||
get_ndc_column_for_comparison(
|
||||
models::get_ndc_column_for_comparison(
|
||||
&target_model.name,
|
||||
&target_model.data_type,
|
||||
target_model_source,
|
||||
@ -382,7 +379,7 @@ fn get_relationship_capabilities(
|
||||
pub fn resolve_relationship(
|
||||
relationship: &RelationshipV1,
|
||||
subgraph: &str,
|
||||
models: &IndexMap<Qualified<ModelName>, Model>,
|
||||
models: &IndexMap<Qualified<ModelName>, models::Model>,
|
||||
commands: &IndexMap<Qualified<CommandName>, Command>,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
source_type: &type_permissions::ObjectTypeWithPermissions,
|
||||
|
@ -5,6 +5,7 @@ use crate::metadata::resolved::stages::{
|
||||
scalar_types, type_permissions,
|
||||
};
|
||||
|
||||
use crate::metadata::resolved::model::resolve_ndc_type;
|
||||
use crate::metadata::resolved::subgraph::Qualified;
|
||||
use crate::metadata::resolved::types::{
|
||||
collect_type_mapping_for_source, mk_name, store_new_graphql_type, TypeMappingToCollect,
|
||||
@ -317,7 +318,7 @@ pub fn resolve_boolean_expression_info(
|
||||
{
|
||||
operators.insert(
|
||||
op_name.clone(),
|
||||
crate::metadata::resolved::model::resolve_ndc_type(
|
||||
resolve_ndc_type(
|
||||
data_connector_name,
|
||||
&get_argument_type(op_definition, &field_mapping.column_type),
|
||||
scalar_types,
|
||||
|
@ -24,7 +24,7 @@ pub(crate) fn resolve(
|
||||
) -> Result<DataConnectorTypeMappingsOutput, Error> {
|
||||
let mut data_connector_type_mappings = DataConnectorTypeMappings::new();
|
||||
let mut object_types = HashMap::new();
|
||||
let mut existing_graphql_types = HashSet::new();
|
||||
let mut graphql_types = HashSet::new();
|
||||
let mut global_id_enabled_types = HashMap::new();
|
||||
let mut apollo_federation_entity_enabled_types = HashMap::new();
|
||||
|
||||
@ -38,7 +38,7 @@ pub(crate) fn resolve(
|
||||
|
||||
let resolved_object_type = resolve_object_type(
|
||||
object_type_definition,
|
||||
&mut existing_graphql_types,
|
||||
&mut graphql_types,
|
||||
&qualified_object_type_name,
|
||||
subgraph,
|
||||
&mut global_id_enabled_types,
|
||||
@ -85,7 +85,7 @@ pub(crate) fn resolve(
|
||||
Ok(DataConnectorTypeMappingsOutput {
|
||||
data_connector_type_mappings,
|
||||
object_types,
|
||||
existing_graphql_types,
|
||||
graphql_types,
|
||||
global_id_enabled_types,
|
||||
apollo_federation_entity_enabled_types,
|
||||
})
|
||||
|
@ -80,7 +80,7 @@ impl DataConnectorTypeMappings {
|
||||
|
||||
/// output of `data_connector_type_mappings` step
|
||||
pub struct DataConnectorTypeMappingsOutput {
|
||||
pub existing_graphql_types: HashSet<ast::TypeName>,
|
||||
pub graphql_types: HashSet<ast::TypeName>,
|
||||
pub global_id_enabled_types: HashMap<Qualified<CustomTypeName>, Vec<Qualified<ModelName>>>,
|
||||
pub apollo_federation_entity_enabled_types:
|
||||
HashMap<Qualified<CustomTypeName>, Option<Qualified<open_dds::models::ModelName>>>,
|
||||
|
@ -4,6 +4,7 @@ pub mod data_connector_scalar_types;
|
||||
pub mod data_connector_type_mappings;
|
||||
pub mod data_connectors;
|
||||
pub mod graphql_config;
|
||||
pub mod models;
|
||||
pub mod scalar_types;
|
||||
pub mod type_permissions;
|
||||
|
||||
@ -24,7 +25,7 @@ pub fn resolve(metadata: open_dds::Metadata) -> Result<Metadata, Error> {
|
||||
|
||||
let data_connector_type_mappings::DataConnectorTypeMappingsOutput {
|
||||
data_connector_type_mappings,
|
||||
existing_graphql_types,
|
||||
graphql_types,
|
||||
global_id_enabled_types,
|
||||
apollo_federation_entity_enabled_types,
|
||||
object_types,
|
||||
@ -33,7 +34,7 @@ pub fn resolve(metadata: open_dds::Metadata) -> Result<Metadata, Error> {
|
||||
let scalar_types::ScalarTypesOutput {
|
||||
scalar_types,
|
||||
graphql_types,
|
||||
} = scalar_types::resolve(&metadata_accessor, &existing_graphql_types)?;
|
||||
} = scalar_types::resolve(&metadata_accessor, &graphql_types)?;
|
||||
|
||||
let data_connector_scalar_types::DataConnectorWithScalarsOutput {
|
||||
data_connectors,
|
||||
@ -61,10 +62,27 @@ pub fn resolve(metadata: open_dds::Metadata) -> Result<Metadata, Error> {
|
||||
&graphql_config,
|
||||
)?;
|
||||
|
||||
let models::ModelsOutput {
|
||||
models,
|
||||
global_id_enabled_types,
|
||||
apollo_federation_entity_enabled_types,
|
||||
graphql_types: _graphql_types,
|
||||
} = models::resolve(
|
||||
&metadata_accessor,
|
||||
&data_connectors,
|
||||
&data_connector_type_mappings,
|
||||
&graphql_types,
|
||||
&global_id_enabled_types,
|
||||
&apollo_federation_entity_enabled_types,
|
||||
&object_types_with_permissions,
|
||||
&scalar_types,
|
||||
&boolean_expression_types,
|
||||
&graphql_config,
|
||||
)?;
|
||||
|
||||
resolve_metadata(
|
||||
&metadata_accessor,
|
||||
&graphql_config,
|
||||
graphql_types,
|
||||
global_id_enabled_types,
|
||||
apollo_federation_entity_enabled_types,
|
||||
&data_connector_type_mappings,
|
||||
@ -72,5 +90,6 @@ pub fn resolve(metadata: open_dds::Metadata) -> Result<Metadata, Error> {
|
||||
&scalar_types,
|
||||
&boolean_expression_types,
|
||||
&data_connectors,
|
||||
models,
|
||||
)
|
||||
}
|
||||
|
832
v3/crates/engine/src/metadata/resolved/stages/models/mod.rs
Normal file
832
v3/crates/engine/src/metadata/resolved/stages/models/mod.rs
Normal file
@ -0,0 +1,832 @@
|
||||
pub use types::{
|
||||
FilterPermission, LimitFieldGraphqlConfig, Model, ModelGraphQlApi,
|
||||
ModelGraphqlApiArgumentsConfig, ModelOrderByExpression, ModelPredicate, ModelSource,
|
||||
ModelsOutput, NDCFieldSourceMapping, OffsetFieldGraphqlConfig, OrderByExpressionInfo,
|
||||
SelectManyGraphQlDefinition, SelectPermission, SelectUniqueGraphQlDefinition,
|
||||
UniqueIdentifierField,
|
||||
};
|
||||
mod types;
|
||||
|
||||
use crate::metadata::resolved::argument::get_argument_mappings;
|
||||
use crate::metadata::resolved::error::{BooleanExpressionError, Error, GraphqlConfigError};
|
||||
use crate::metadata::resolved::ndc_validation;
|
||||
|
||||
use crate::metadata::resolved::stages::{
|
||||
boolean_expressions, data_connector_scalar_types, data_connector_type_mappings,
|
||||
data_connectors, graphql_config, scalar_types, type_permissions,
|
||||
};
|
||||
use crate::metadata::resolved::subgraph::{mk_qualified_type_reference, ArgumentInfo, Qualified};
|
||||
use crate::metadata::resolved::types::{
|
||||
collect_type_mapping_for_source, NdcColumnForComparison, TypeMappingToCollect,
|
||||
};
|
||||
use crate::metadata::resolved::types::{mk_name, store_new_graphql_type};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use lang_graphql::ast::common::{self as ast};
|
||||
|
||||
use open_dds::{
|
||||
models::{
|
||||
self, EnableAllOrSpecific, ModelGraphQlDefinition, ModelName, ModelV1, OrderableField,
|
||||
},
|
||||
types::{CustomTypeName, FieldName},
|
||||
};
|
||||
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::iter;
|
||||
|
||||
/// resolve models
|
||||
pub fn resolve(
|
||||
metadata_accessor: &open_dds::accessor::MetadataAccessor,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
||||
existing_graphql_types: &HashSet<ast::TypeName>,
|
||||
global_id_enabled_types: &HashMap<Qualified<CustomTypeName>, Vec<Qualified<ModelName>>>,
|
||||
apollo_federation_entity_enabled_types: &HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
Option<Qualified<open_dds::models::ModelName>>,
|
||||
>,
|
||||
object_types: &HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
scalar_types: &HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
boolean_expression_types: &HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
boolean_expressions::ObjectBooleanExpressionType,
|
||||
>,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
) -> Result<ModelsOutput, Error> {
|
||||
// resolve models
|
||||
// TODO: validate types
|
||||
let mut models = IndexMap::new();
|
||||
let mut global_id_models = HashMap::new();
|
||||
let mut graphql_types = existing_graphql_types.clone();
|
||||
let mut global_id_enabled_types = global_id_enabled_types.clone();
|
||||
let mut apollo_federation_entity_enabled_types = apollo_federation_entity_enabled_types.clone();
|
||||
|
||||
for open_dds::accessor::QualifiedObject {
|
||||
subgraph,
|
||||
object: model,
|
||||
} in &metadata_accessor.models
|
||||
{
|
||||
let mut resolved_model = resolve_model(
|
||||
subgraph,
|
||||
model,
|
||||
object_types,
|
||||
&mut global_id_enabled_types,
|
||||
&mut apollo_federation_entity_enabled_types,
|
||||
boolean_expression_types,
|
||||
)?;
|
||||
if resolved_model.global_id_source.is_some() {
|
||||
match global_id_models.insert(
|
||||
resolved_model.data_type.clone(),
|
||||
resolved_model.name.clone(),
|
||||
) {
|
||||
None => {}
|
||||
Some(duplicate_model_name) => {
|
||||
return Err(Error::DuplicateModelGlobalIdSource {
|
||||
model_1: resolved_model.name,
|
||||
model_2: duplicate_model_name,
|
||||
object_type: resolved_model.data_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(model_source) = &model.source {
|
||||
resolve_model_source(
|
||||
model_source,
|
||||
&mut resolved_model,
|
||||
subgraph,
|
||||
data_connectors,
|
||||
object_types,
|
||||
scalar_types,
|
||||
data_connector_type_mappings,
|
||||
boolean_expression_types,
|
||||
)?;
|
||||
}
|
||||
if let Some(model_graphql_definition) = &model.graphql {
|
||||
resolve_model_graphql_api(
|
||||
model_graphql_definition,
|
||||
&mut resolved_model,
|
||||
&mut graphql_types,
|
||||
data_connectors,
|
||||
&model.description,
|
||||
graphql_config,
|
||||
)?;
|
||||
}
|
||||
let qualified_model_name = Qualified::new(subgraph.to_string(), model.name.clone());
|
||||
if models
|
||||
.insert(qualified_model_name.clone(), resolved_model)
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::DuplicateModelDefinition {
|
||||
name: qualified_model_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(ModelsOutput {
|
||||
models,
|
||||
graphql_types,
|
||||
apollo_federation_entity_enabled_types,
|
||||
global_id_enabled_types,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_filter_expression_type(
|
||||
model: &ModelV1,
|
||||
model_data_type: &Qualified<CustomTypeName>,
|
||||
subgraph: &str,
|
||||
boolean_expression_types: &HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
boolean_expressions::ObjectBooleanExpressionType,
|
||||
>,
|
||||
) -> Result<Option<boolean_expressions::ObjectBooleanExpressionType>, Error> {
|
||||
model
|
||||
.filter_expression_type
|
||||
.as_ref()
|
||||
.map(|filter_expression_type| {
|
||||
let boolean_expression_type_name =
|
||||
Qualified::new(subgraph.to_string(), filter_expression_type.clone());
|
||||
let boolean_expression_type = boolean_expression_types
|
||||
.get(&boolean_expression_type_name)
|
||||
.ok_or_else(|| {
|
||||
Error::from(
|
||||
BooleanExpressionError::UnknownBooleanExpressionTypeInModel {
|
||||
name: boolean_expression_type_name.clone(),
|
||||
model: Qualified::new(subgraph.to_string(), model.name.clone()),
|
||||
},
|
||||
)
|
||||
})?;
|
||||
if boolean_expression_type.object_type != *model_data_type {
|
||||
return Err(Error::from(
|
||||
BooleanExpressionError::BooleanExpressionTypeForInvalidObjectTypeInModel {
|
||||
name: boolean_expression_type_name.clone(),
|
||||
boolean_expression_object_type: boolean_expression_type.object_type.clone(),
|
||||
model: Qualified::new(subgraph.to_string(), model.name.clone()),
|
||||
model_object_type: model_data_type.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
// This is also checked in resolve_model_graphql_api, but we want to disallow this even
|
||||
// if the model is not used in the graphql layer.
|
||||
if model.source.is_none() {
|
||||
return Err(Error::CannotUseFilterExpressionsWithoutSource {
|
||||
model: Qualified::new(subgraph.to_string(), model.name.clone()),
|
||||
});
|
||||
// TODO: Compatibility of model source and the boolean expression type is checked in
|
||||
// resolve_model_source. Figure out a way to make this logic not scattered.
|
||||
}
|
||||
Ok(boolean_expression_type.clone())
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
fn resolve_orderable_fields(
|
||||
model: &ModelV1,
|
||||
type_fields: &IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
||||
) -> Result<Vec<OrderableField>, Error> {
|
||||
for field in &model.orderable_fields {
|
||||
// Check for unknown orderable field
|
||||
if !type_fields.contains_key(&field.field_name) {
|
||||
return Err(Error::UnknownFieldInOrderableFields {
|
||||
model_name: model.name.clone(),
|
||||
field_name: field.field_name.clone(),
|
||||
});
|
||||
}
|
||||
match &field.order_by_directions {
|
||||
EnableAllOrSpecific::EnableAll(true) => {}
|
||||
_ => {
|
||||
return Err(Error::UnsupportedFeature {
|
||||
message: "Field level order by configuration is not fully supported yet. Please use \"enableAll\":true.".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Model orderable fields should have all type fields
|
||||
if model.orderable_fields.len() != type_fields.len() {
|
||||
return Err(Error::UnsupportedFeature {
|
||||
message: "Field level order by configuration is not fully supported yet. Please add all fields in orderable_fields.".to_string(),
|
||||
});
|
||||
}
|
||||
Ok(model.orderable_fields.clone())
|
||||
}
|
||||
|
||||
fn resolve_model(
|
||||
subgraph: &str,
|
||||
model: &ModelV1,
|
||||
object_types: &HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
global_id_enabled_types: &mut HashMap<Qualified<CustomTypeName>, Vec<Qualified<ModelName>>>,
|
||||
apollo_federation_entity_enabled_types: &mut HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
Option<Qualified<ModelName>>,
|
||||
>,
|
||||
boolean_expression_types: &HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
boolean_expressions::ObjectBooleanExpressionType,
|
||||
>,
|
||||
) -> Result<Model, Error> {
|
||||
let qualified_object_type_name =
|
||||
Qualified::new(subgraph.to_string(), model.object_type.to_owned());
|
||||
let qualified_model_name = Qualified::new(subgraph.to_string(), model.name.clone());
|
||||
let object_type_representation = get_model_object_type_representation(
|
||||
object_types,
|
||||
&qualified_object_type_name,
|
||||
&qualified_model_name,
|
||||
)?;
|
||||
let mut global_id_source = None;
|
||||
if model.global_id_source {
|
||||
// Check if there are any global fields present in the related
|
||||
// object type, if the model is marked as a global source.
|
||||
if object_type_representation
|
||||
.object_type
|
||||
.global_id_fields
|
||||
.is_empty()
|
||||
{
|
||||
return Err(Error::NoGlobalFieldsPresentInGlobalIdSource {
|
||||
type_name: qualified_object_type_name,
|
||||
model_name: model.name.clone(),
|
||||
});
|
||||
}
|
||||
if !model.arguments.is_empty() {
|
||||
return Err(Error::ModelWithArgumentsAsGlobalIdSource {
|
||||
model_name: qualified_model_name,
|
||||
});
|
||||
}
|
||||
// model has `global_id_source`; insert into the hashmap of `global_id_enabled_types`
|
||||
match global_id_enabled_types.get_mut(&qualified_object_type_name) {
|
||||
None => {
|
||||
// this shouldn't happen; but for some reason the object type
|
||||
// containing globalIdFields is not inserted. Insert it now
|
||||
global_id_enabled_types.insert(
|
||||
qualified_object_type_name.clone(),
|
||||
vec![qualified_model_name.clone()],
|
||||
);
|
||||
}
|
||||
Some(model_names) => {
|
||||
model_names.push(qualified_model_name.clone());
|
||||
}
|
||||
}
|
||||
global_id_source = Some(NDCFieldSourceMapping {
|
||||
ndc_mapping: HashMap::new(),
|
||||
});
|
||||
};
|
||||
let mut apollo_federation_key_source = None;
|
||||
if model
|
||||
.graphql
|
||||
.as_ref()
|
||||
.and_then(|g| g.apollo_federation.as_ref().map(|a| a.entity_source))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
// Check if there are any apollo federation keys present in the related
|
||||
// object type, if the model is marked as an apollo federation entity source.
|
||||
if object_type_representation
|
||||
.object_type
|
||||
.apollo_federation_config
|
||||
.is_some()
|
||||
{
|
||||
if !model.arguments.is_empty() {
|
||||
return Err(Error::ModelWithArgumentsAsApolloFederationEntitySource {
|
||||
model_name: qualified_model_name,
|
||||
});
|
||||
}
|
||||
// model has `apollo_federation_entity_source`; insert into the hashmap of
|
||||
// `apollo_federation_entity_enabled_types`
|
||||
match apollo_federation_entity_enabled_types.get_mut(&qualified_object_type_name) {
|
||||
None => {
|
||||
// the model's graphql configuration has `apollo_federation.entitySource` but the object type
|
||||
// of the model doesn't have any apollo federation keys
|
||||
return Err(Error::NoKeysFieldsPresentInEntitySource {
|
||||
type_name: qualified_object_type_name,
|
||||
model_name: model.name.clone(),
|
||||
});
|
||||
}
|
||||
Some(type_name) => {
|
||||
match type_name {
|
||||
None => {
|
||||
*type_name = Some(qualified_model_name.clone());
|
||||
}
|
||||
// Multiple models are marked as apollo federation entity source
|
||||
Some(_) => {
|
||||
return Err(Error::MultipleEntitySourcesForType {
|
||||
type_name: qualified_object_type_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apollo_federation_key_source = Some(NDCFieldSourceMapping {
|
||||
ndc_mapping: HashMap::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut arguments = IndexMap::new();
|
||||
for argument in &model.arguments {
|
||||
if arguments
|
||||
.insert(
|
||||
argument.name.clone(),
|
||||
ArgumentInfo {
|
||||
argument_type: mk_qualified_type_reference(&argument.argument_type, subgraph),
|
||||
description: argument.description.clone(),
|
||||
},
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::DuplicateModelArgumentDefinition {
|
||||
model_name: qualified_model_name,
|
||||
argument_name: argument.name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let filter_expression_type = resolve_filter_expression_type(
|
||||
model,
|
||||
&qualified_object_type_name,
|
||||
subgraph,
|
||||
boolean_expression_types,
|
||||
)?;
|
||||
|
||||
Ok(Model {
|
||||
name: qualified_model_name,
|
||||
data_type: qualified_object_type_name,
|
||||
type_fields: object_type_representation.object_type.fields.clone(),
|
||||
global_id_fields: object_type_representation
|
||||
.object_type
|
||||
.global_id_fields
|
||||
.clone(),
|
||||
arguments,
|
||||
graphql_api: ModelGraphQlApi::default(),
|
||||
source: None,
|
||||
select_permissions: None,
|
||||
global_id_source,
|
||||
apollo_federation_key_source,
|
||||
filter_expression_type,
|
||||
orderable_fields: resolve_orderable_fields(
|
||||
model,
|
||||
&object_type_representation.object_type.fields,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_model_graphql_api(
|
||||
model_graphql_definition: &ModelGraphQlDefinition,
|
||||
model: &mut Model,
|
||||
existing_graphql_types: &mut HashSet<ast::TypeName>,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
model_description: &Option<String>,
|
||||
graphql_config: &graphql_config::GraphqlConfig,
|
||||
) -> Result<(), Error> {
|
||||
let model_name = &model.name;
|
||||
for select_unique in &model_graphql_definition.select_uniques {
|
||||
let mut unique_identifier_fields = IndexMap::new();
|
||||
for field_name in &select_unique.unique_identifier {
|
||||
let field_type = &model
|
||||
.type_fields
|
||||
.get(field_name)
|
||||
.ok_or_else(|| Error::UnknownFieldInUniqueIdentifier {
|
||||
model_name: model_name.clone(),
|
||||
field_name: field_name.clone(),
|
||||
})?
|
||||
.field_type;
|
||||
let ndc_column = model
|
||||
.source
|
||||
.as_ref()
|
||||
.map(|model_source| {
|
||||
get_ndc_column_for_comparison(
|
||||
&model.name,
|
||||
&model.data_type,
|
||||
model_source,
|
||||
field_name,
|
||||
data_connectors,
|
||||
|| "the unique identifier for select unique".to_string(),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let unique_identifier_field = UniqueIdentifierField {
|
||||
field_type: field_type.clone(),
|
||||
ndc_column,
|
||||
};
|
||||
if unique_identifier_fields
|
||||
.insert(field_name.clone(), unique_identifier_field)
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::DuplicateFieldInUniqueIdentifier {
|
||||
model_name: model_name.clone(),
|
||||
field_name: field_name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
let select_unique_field_name = mk_name(&select_unique.query_root_field.0)?;
|
||||
let select_unique_description = if select_unique.description.is_some() {
|
||||
select_unique.description.clone()
|
||||
} else {
|
||||
model_description.as_ref().map(|description| {
|
||||
format!(
|
||||
"Selects a single object from the model. Model description: {}",
|
||||
description
|
||||
)
|
||||
})
|
||||
};
|
||||
model
|
||||
.graphql_api
|
||||
.select_uniques
|
||||
.push(SelectUniqueGraphQlDefinition {
|
||||
query_root_field: select_unique_field_name,
|
||||
unique_identifier: unique_identifier_fields,
|
||||
description: select_unique_description,
|
||||
deprecated: select_unique.deprecated.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
model.graphql_api.order_by_expression = model
|
||||
.source
|
||||
.as_ref()
|
||||
.map(
|
||||
|model_source: &ModelSource| -> Result<Option<ModelOrderByExpression>, Error> {
|
||||
let order_by_expression_type_name = match &model_graphql_definition
|
||||
.order_by_expression_type
|
||||
{
|
||||
None => Ok(None),
|
||||
Some(type_name) => mk_name(type_name.0.as_str()).map(ast::TypeName).map(Some),
|
||||
}?;
|
||||
// TODO: (paritosh) should we check for conflicting graphql types for default order_by type name as well?
|
||||
store_new_graphql_type(
|
||||
existing_graphql_types,
|
||||
order_by_expression_type_name.as_ref(),
|
||||
)?;
|
||||
order_by_expression_type_name
|
||||
.map(|order_by_type_name| {
|
||||
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(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut order_by_fields = HashMap::new();
|
||||
for (field_name, field_mapping) in field_mappings.iter() {
|
||||
order_by_fields.insert(
|
||||
field_name.clone(),
|
||||
OrderByExpressionInfo {
|
||||
ndc_column: field_mapping.column.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
match &graphql_config.query.order_by_field_name {
|
||||
None => Err(Error::GraphqlConfigError {
|
||||
graphql_config_error:
|
||||
GraphqlConfigError::MissingOrderByInputFieldInGraphqlConfig,
|
||||
}),
|
||||
Some(order_by_field_name) => Ok(ModelOrderByExpression {
|
||||
data_connector_name: model_source.data_connector.name.clone(),
|
||||
order_by_type_name,
|
||||
order_by_fields,
|
||||
order_by_field_name: order_by_field_name.clone(),
|
||||
}),
|
||||
}
|
||||
})
|
||||
.transpose()
|
||||
},
|
||||
)
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
// record select_many root field
|
||||
model.graphql_api.select_many = match &model_graphql_definition.select_many {
|
||||
None => Ok(None),
|
||||
Some(gql_definition) => mk_name(&gql_definition.query_root_field.0).map(|f: ast::Name| {
|
||||
let select_many_description = if gql_definition.description.is_some() {
|
||||
gql_definition.description.clone()
|
||||
} else {
|
||||
model_description.as_ref().map(|description| {
|
||||
format!(
|
||||
"Selects multiple objects from the model. Model description: {}",
|
||||
description
|
||||
)
|
||||
})
|
||||
};
|
||||
Some(SelectManyGraphQlDefinition {
|
||||
query_root_field: f,
|
||||
description: select_many_description,
|
||||
deprecated: gql_definition.deprecated.clone(),
|
||||
})
|
||||
}),
|
||||
}?;
|
||||
|
||||
// record limit and offset field names
|
||||
model.graphql_api.limit_field =
|
||||
graphql_config
|
||||
.query
|
||||
.limit_field_name
|
||||
.as_ref()
|
||||
.map(|limit_field| LimitFieldGraphqlConfig {
|
||||
field_name: limit_field.clone(),
|
||||
});
|
||||
|
||||
model.graphql_api.offset_field =
|
||||
graphql_config
|
||||
.query
|
||||
.offset_field_name
|
||||
.as_ref()
|
||||
.map(|offset_field| OffsetFieldGraphqlConfig {
|
||||
field_name: offset_field.clone(),
|
||||
});
|
||||
|
||||
if model.arguments.is_empty() {
|
||||
if model_graphql_definition.arguments_input_type.is_some() {
|
||||
return Err(Error::UnnecessaryModelArgumentsGraphQlInputConfiguration {
|
||||
model_name: model_name.clone(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let arguments_input_type_name = match &model_graphql_definition.arguments_input_type {
|
||||
None => Ok(None),
|
||||
Some(type_name) => mk_name(type_name.0.as_str()).map(ast::TypeName).map(Some),
|
||||
}?;
|
||||
store_new_graphql_type(existing_graphql_types, arguments_input_type_name.as_ref())?;
|
||||
|
||||
if let Some(type_name) = arguments_input_type_name {
|
||||
let argument_input_field_name = graphql_config
|
||||
.query
|
||||
.arguments_field_name
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::GraphqlConfigError {
|
||||
graphql_config_error:
|
||||
GraphqlConfigError::MissingArgumentsInputFieldInGraphqlConfig,
|
||||
})?;
|
||||
model.graphql_api.arguments_input_config = Some(ModelGraphqlApiArgumentsConfig {
|
||||
field_name: argument_input_field_name.clone(),
|
||||
type_name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_model_source(
|
||||
model_source: &models::ModelSource,
|
||||
model: &mut Model,
|
||||
subgraph: &str,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
object_types: &HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
scalar_types: &HashMap<Qualified<CustomTypeName>, scalar_types::ScalarTypeRepresentation>,
|
||||
data_connector_type_mappings: &data_connector_type_mappings::DataConnectorTypeMappings,
|
||||
boolean_expression_types: &HashMap<
|
||||
Qualified<CustomTypeName>,
|
||||
boolean_expressions::ObjectBooleanExpressionType,
|
||||
>,
|
||||
) -> Result<(), Error> {
|
||||
if model.source.is_some() {
|
||||
return Err(Error::DuplicateModelSourceDefinition {
|
||||
model_name: model.name.clone(),
|
||||
});
|
||||
}
|
||||
let qualified_data_connector_name = Qualified::new(
|
||||
subgraph.to_string(),
|
||||
model_source.data_connector_name.clone(),
|
||||
);
|
||||
let data_connector_context = data_connectors
|
||||
.data_connectors_with_scalars
|
||||
.get(&qualified_data_connector_name)
|
||||
.ok_or_else(|| Error::UnknownModelDataConnector {
|
||||
model_name: model.name.clone(),
|
||||
data_connector: qualified_data_connector_name.clone(),
|
||||
})?;
|
||||
|
||||
let source_collection = data_connector_context
|
||||
.inner
|
||||
.schema
|
||||
.collections
|
||||
.iter()
|
||||
.find(|collection_info| collection_info.name == *model_source.collection)
|
||||
.ok_or_else(|| Error::UnknownModelCollection {
|
||||
model_name: model.name.clone(),
|
||||
data_connector: qualified_data_connector_name.clone(),
|
||||
collection: model_source.collection.clone(),
|
||||
})?;
|
||||
|
||||
// Get the mappings of arguments and any type mappings that need resolving from the arguments
|
||||
let (argument_mappings, argument_type_mappings_to_collect) = get_argument_mappings(
|
||||
&model.arguments,
|
||||
&model_source.argument_mapping,
|
||||
&source_collection.arguments,
|
||||
object_types,
|
||||
scalar_types,
|
||||
boolean_expression_types,
|
||||
)
|
||||
.map_err(|err| Error::ModelCollectionArgumentMappingError {
|
||||
data_connector_name: qualified_data_connector_name.clone(),
|
||||
model_name: model.name.clone(),
|
||||
collection_name: model_source.collection.clone(),
|
||||
error: err,
|
||||
})?;
|
||||
|
||||
// Collect type mappings.
|
||||
let mut type_mappings = BTreeMap::new();
|
||||
let source_collection_type_mapping_to_collect = TypeMappingToCollect {
|
||||
type_name: &model.data_type,
|
||||
ndc_object_type_name: source_collection.collection_type.as_str(),
|
||||
};
|
||||
for type_mapping_to_collect in iter::once(&source_collection_type_mapping_to_collect)
|
||||
.chain(argument_type_mappings_to_collect.iter())
|
||||
{
|
||||
collect_type_mapping_for_source(
|
||||
type_mapping_to_collect,
|
||||
data_connector_type_mappings,
|
||||
&qualified_data_connector_name,
|
||||
object_types,
|
||||
scalar_types,
|
||||
&mut type_mappings,
|
||||
)
|
||||
.map_err(|error| Error::ModelTypeMappingCollectionError {
|
||||
model_name: model.name.clone(),
|
||||
error,
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Some(filter_expression) = &model.filter_expression_type {
|
||||
if filter_expression.data_connector_name != qualified_data_connector_name {
|
||||
return Err(Error::DifferentDataConnectorInFilterExpression {
|
||||
model: model.name.clone(),
|
||||
model_data_connector: qualified_data_connector_name.clone(),
|
||||
filter_expression_type: filter_expression.name.clone(),
|
||||
filter_expression_data_connector: filter_expression.data_connector_name.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if filter_expression.data_connector_object_type != source_collection.collection_type {
|
||||
return Err(Error::DifferentDataConnectorObjectTypeInFilterExpression {
|
||||
model: model.name.clone(),
|
||||
model_data_connector_object_type: source_collection.collection_type.clone(),
|
||||
filter_expression_type: filter_expression.name.clone(),
|
||||
filter_expression_data_connector_object_type: filter_expression
|
||||
.data_connector_object_type
|
||||
.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let resolved_model_source = ModelSource {
|
||||
data_connector: data_connectors::DataConnectorLink::new(
|
||||
qualified_data_connector_name,
|
||||
data_connector_context.inner.url.clone(),
|
||||
data_connector_context.inner.headers,
|
||||
)?,
|
||||
collection: model_source.collection.clone(),
|
||||
type_mappings,
|
||||
argument_mappings,
|
||||
};
|
||||
|
||||
let model_object_type =
|
||||
get_model_object_type_representation(object_types, &model.data_type, &model.name)?;
|
||||
|
||||
if let Some(global_id_source) = &mut model.global_id_source {
|
||||
for global_id_field in &model_object_type.object_type.global_id_fields {
|
||||
global_id_source.ndc_mapping.insert(
|
||||
global_id_field.clone(),
|
||||
get_ndc_column_for_comparison(
|
||||
&model.name,
|
||||
&model.data_type,
|
||||
&resolved_model_source,
|
||||
global_id_field,
|
||||
data_connectors,
|
||||
|| format!("the global ID fields of type {}", model.data_type),
|
||||
)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(apollo_federation_key_source) = &mut model.apollo_federation_key_source {
|
||||
if let Some(apollo_federation_config) =
|
||||
&model_object_type.object_type.apollo_federation_config
|
||||
{
|
||||
for key in &apollo_federation_config.keys {
|
||||
for field in &key.fields {
|
||||
apollo_federation_key_source.ndc_mapping.insert(
|
||||
field.clone(),
|
||||
get_ndc_column_for_comparison(
|
||||
&model.name,
|
||||
&model.data_type,
|
||||
&resolved_model_source,
|
||||
field,
|
||||
data_connectors,
|
||||
|| {
|
||||
format!(
|
||||
"the apollo federation key fields of type {}",
|
||||
model.data_type
|
||||
)
|
||||
},
|
||||
)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.source = Some(resolved_model_source);
|
||||
ndc_validation::validate_ndc(&model.name, model, data_connector_context.inner.schema)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
type_permissions::ObjectTypeWithPermissions,
|
||||
>,
|
||||
data_type: &Qualified<CustomTypeName>,
|
||||
model_name: &Qualified<ModelName>,
|
||||
) -> Result<&'s type_permissions::ObjectTypeWithPermissions, 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(crate) fn get_ndc_column_for_comparison<F: Fn() -> String>(
|
||||
model_name: &Qualified<ModelName>,
|
||||
model_data_type: &Qualified<CustomTypeName>,
|
||||
model_source: &ModelSource,
|
||||
field: &FieldName,
|
||||
data_connectors: &data_connector_scalar_types::DataConnectorsWithScalars,
|
||||
comparison_location: F,
|
||||
) -> Result<NdcColumnForComparison, Error> {
|
||||
// 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 given field
|
||||
let field_mapping =
|
||||
field_mappings
|
||||
.get(field)
|
||||
.ok_or_else(|| Error::NoFieldMappingForComparedField {
|
||||
comparison_location: comparison_location(),
|
||||
field_name: field.clone(),
|
||||
model_name: model_name.clone(),
|
||||
})?;
|
||||
|
||||
// Determine ndc type of the field
|
||||
let field_ndc_type = &field_mapping.column_type;
|
||||
|
||||
// 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;
|
||||
// Determine whether the ndc type is a simple scalar and get scalar type info
|
||||
let (_field_ndc_type_scalar, scalar_type_info) =
|
||||
data_connector_scalar_types::get_simple_scalar(field_ndc_type.clone(), scalars)
|
||||
.ok_or_else(|| Error::UncomparableNonScalarFieldType {
|
||||
comparison_location: comparison_location(),
|
||||
field_name: field.clone(),
|
||||
model_name: model_name.clone(),
|
||||
})?;
|
||||
|
||||
let equal_operator = match scalar_type_info
|
||||
.comparison_operators
|
||||
.equal_operators
|
||||
.as_slice()
|
||||
{
|
||||
[] => {
|
||||
return Err(Error::NoEqualOperatorForComparedField {
|
||||
comparison_location: comparison_location(),
|
||||
field_name: field.clone(),
|
||||
model_name: model_name.clone(),
|
||||
});
|
||||
}
|
||||
[equal_operator] => equal_operator,
|
||||
_ => {
|
||||
return Err(Error::MultipleEqualOperatorsForComparedField {
|
||||
comparison_location: comparison_location(),
|
||||
field_name: field.clone(),
|
||||
model_name: model_name.clone(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Ok(NdcColumnForComparison {
|
||||
column: field_mapping.column.clone(),
|
||||
equal_operator: equal_operator.clone(),
|
||||
})
|
||||
}
|
162
v3/crates/engine/src/metadata/resolved/stages/models/types.rs
Normal file
162
v3/crates/engine/src/metadata/resolved/stages/models/types.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use crate::metadata::resolved::permission::ValueExpression;
|
||||
use crate::metadata::resolved::stages::{
|
||||
boolean_expressions, data_connector_type_mappings, data_connectors,
|
||||
};
|
||||
use crate::metadata::resolved::subgraph::{
|
||||
deserialize_qualified_btreemap, serialize_qualified_btreemap, ArgumentInfo, Qualified,
|
||||
QualifiedTypeReference,
|
||||
};
|
||||
use crate::metadata::resolved::types::NdcColumnForComparison;
|
||||
|
||||
use crate::schema::types::output_type::relationship::PredicateRelationshipAnnotation;
|
||||
use indexmap::IndexMap;
|
||||
use lang_graphql::ast::common::{self as ast, Name};
|
||||
use ndc_models;
|
||||
|
||||
use open_dds::types::Deprecated;
|
||||
use open_dds::{
|
||||
arguments::ArgumentName,
|
||||
data_connector::DataConnectorName,
|
||||
models::{ModelName, OrderableField},
|
||||
permissions::Role,
|
||||
types::{CustomTypeName, FieldName},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
pub struct ModelsOutput {
|
||||
pub models: IndexMap<Qualified<ModelName>, Model>,
|
||||
pub graphql_types: HashSet<ast::TypeName>,
|
||||
pub global_id_enabled_types: HashMap<Qualified<CustomTypeName>, Vec<Qualified<ModelName>>>,
|
||||
pub apollo_federation_entity_enabled_types:
|
||||
HashMap<Qualified<CustomTypeName>, Option<Qualified<open_dds::models::ModelName>>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct UniqueIdentifierField {
|
||||
pub field_type: QualifiedTypeReference,
|
||||
pub ndc_column: Option<NdcColumnForComparison>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SelectUniqueGraphQlDefinition {
|
||||
pub query_root_field: ast::Name,
|
||||
pub unique_identifier: IndexMap<FieldName, UniqueIdentifierField>,
|
||||
pub description: Option<String>,
|
||||
pub deprecated: Option<Deprecated>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SelectManyGraphQlDefinition {
|
||||
pub query_root_field: ast::Name,
|
||||
pub description: Option<String>,
|
||||
pub deprecated: Option<Deprecated>,
|
||||
}
|
||||
|
||||
// TODO: add support for aggregates
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct OrderByExpressionInfo {
|
||||
pub ndc_column: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ModelOrderByExpression {
|
||||
pub data_connector_name: Qualified<DataConnectorName>,
|
||||
pub order_by_type_name: ast::TypeName,
|
||||
pub order_by_fields: HashMap<FieldName, OrderByExpressionInfo>,
|
||||
pub order_by_field_name: ast::Name,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ModelGraphqlApiArgumentsConfig {
|
||||
pub field_name: Name,
|
||||
pub type_name: ast::TypeName,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct LimitFieldGraphqlConfig {
|
||||
pub field_name: Name,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct OffsetFieldGraphqlConfig {
|
||||
pub field_name: Name,
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)]
|
||||
pub struct ModelGraphQlApi {
|
||||
pub arguments_input_config: Option<ModelGraphqlApiArgumentsConfig>,
|
||||
pub select_uniques: Vec<SelectUniqueGraphQlDefinition>,
|
||||
pub select_many: Option<SelectManyGraphQlDefinition>,
|
||||
pub order_by_expression: Option<ModelOrderByExpression>,
|
||||
pub limit_field: Option<LimitFieldGraphqlConfig>,
|
||||
pub offset_field: Option<OffsetFieldGraphqlConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ModelSource {
|
||||
pub data_connector: data_connectors::DataConnectorLink,
|
||||
pub collection: String,
|
||||
#[serde(
|
||||
serialize_with = "serialize_qualified_btreemap",
|
||||
deserialize_with = "deserialize_qualified_btreemap"
|
||||
)]
|
||||
pub type_mappings:
|
||||
BTreeMap<Qualified<CustomTypeName>, data_connector_type_mappings::TypeMapping>,
|
||||
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)]
|
||||
pub struct Model {
|
||||
pub name: Qualified<ModelName>,
|
||||
pub data_type: Qualified<CustomTypeName>,
|
||||
pub type_fields: IndexMap<FieldName, data_connector_type_mappings::FieldDefinition>,
|
||||
pub global_id_fields: Vec<FieldName>,
|
||||
pub arguments: IndexMap<ArgumentName, ArgumentInfo>,
|
||||
pub graphql_api: ModelGraphQlApi,
|
||||
pub source: Option<ModelSource>,
|
||||
pub select_permissions: Option<HashMap<Role, SelectPermission>>,
|
||||
pub global_id_source: Option<NDCFieldSourceMapping>,
|
||||
pub apollo_federation_key_source: Option<NDCFieldSourceMapping>,
|
||||
pub filter_expression_type: Option<boolean_expressions::ObjectBooleanExpressionType>,
|
||||
pub orderable_fields: Vec<OrderableField>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NDCFieldSourceMapping {
|
||||
pub ndc_mapping: HashMap<FieldName, NdcColumnForComparison>,
|
||||
}
|
@ -14,7 +14,7 @@ use crate::metadata::resolved::subgraph::Qualified;
|
||||
/// arguments fields will live.
|
||||
pub fn get_model_arguments_input_field(
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
model: &resolved::model::Model,
|
||||
model: &resolved::Model,
|
||||
) -> Result<gql_schema::InputField<GDS>, crate::schema::Error> {
|
||||
model
|
||||
.graphql_api
|
||||
@ -49,7 +49,7 @@ pub fn get_model_arguments_input_field(
|
||||
pub fn build_model_argument_fields(
|
||||
gds: &GDS,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
model: &resolved::model::Model,
|
||||
model: &resolved::Model,
|
||||
) -> Result<
|
||||
BTreeMap<ast::Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||
crate::schema::Error,
|
||||
|
@ -82,7 +82,7 @@ pub fn build_order_by_enum_type_schema(
|
||||
pub fn get_order_by_expression_input_field(
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
model_name: Qualified<ModelName>,
|
||||
order_by_expression_info: &resolved::model::ModelOrderByExpression,
|
||||
order_by_expression_info: &resolved::ModelOrderByExpression,
|
||||
) -> gql_schema::InputField<GDS> {
|
||||
gql_schema::InputField::new(
|
||||
order_by_expression_info.order_by_field_name.clone(),
|
||||
|
@ -1,9 +1,8 @@
|
||||
use open_dds::types::{CustomTypeName, FieldName};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use crate::metadata::resolved::model::FilterPermission;
|
||||
use crate::metadata::resolved::permission::ValueExpression;
|
||||
use crate::metadata::resolved::stages::{data_connector_type_mappings, type_permissions};
|
||||
use crate::metadata::resolved::stages::{data_connector_type_mappings, models, type_permissions};
|
||||
use crate::metadata::resolved::subgraph::{Qualified, QualifiedTypeReference};
|
||||
use crate::metadata::resolved::types::{object_type_exists, unwrap_custom_type_name};
|
||||
use crate::metadata::resolved::{self};
|
||||
@ -14,7 +13,7 @@ use super::types::ArgumentNameAndPath;
|
||||
|
||||
/// Build namespace annotation for select permissions
|
||||
pub(crate) fn get_select_permissions_namespace_annotations(
|
||||
model: &resolved::model::Model,
|
||||
model: &models::Model,
|
||||
object_types: &HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
) -> Result<HashMap<Role, Option<types::NamespaceAnnotation>>, schema::Error> {
|
||||
let mut permissions: HashMap<Role, Option<types::NamespaceAnnotation>> = model
|
||||
@ -97,9 +96,9 @@ pub(crate) fn get_select_permissions_namespace_annotations(
|
||||
/// This is different from generating permissions for select_many etc,
|
||||
/// as we need to check the permissions of the arguments used in the selection.
|
||||
pub(crate) fn get_select_one_namespace_annotations(
|
||||
model: &resolved::model::Model,
|
||||
model: &models::Model,
|
||||
object_type_representation: &type_permissions::ObjectTypeWithPermissions,
|
||||
select_unique: &resolved::model::SelectUniqueGraphQlDefinition,
|
||||
select_unique: &models::SelectUniqueGraphQlDefinition,
|
||||
object_types: &HashMap<Qualified<CustomTypeName>, type_permissions::ObjectTypeWithPermissions>,
|
||||
) -> Result<HashMap<Role, Option<types::NamespaceAnnotation>>, schema::Error> {
|
||||
let select_permissions = get_select_permissions_namespace_annotations(model, object_types)?;
|
||||
@ -120,7 +119,7 @@ pub(crate) fn get_select_one_namespace_annotations(
|
||||
/// We need to check the permissions of the source and target fields
|
||||
/// in the relationship mappings.
|
||||
pub(crate) fn get_model_relationship_namespace_annotations(
|
||||
target_model: &resolved::model::Model,
|
||||
target_model: &models::Model,
|
||||
source_object_type_representation: &type_permissions::ObjectTypeWithPermissions,
|
||||
target_object_type_representation: &type_permissions::ObjectTypeWithPermissions,
|
||||
mappings: &[resolved::relationship::RelationshipModelMapping],
|
||||
@ -442,8 +441,8 @@ pub(crate) fn get_allowed_roles_for_field<'a>(
|
||||
/// Builds namespace annotations for the `node` field.
|
||||
pub(crate) fn get_node_field_namespace_permissions(
|
||||
object_type_representation: &type_permissions::ObjectTypeWithPermissions,
|
||||
model: &resolved::model::Model,
|
||||
) -> HashMap<Role, FilterPermission> {
|
||||
model: &models::Model,
|
||||
) -> HashMap<Role, resolved::FilterPermission> {
|
||||
let mut permissions = HashMap::new();
|
||||
|
||||
match &model.select_permissions {
|
||||
@ -481,8 +480,8 @@ pub(crate) fn get_node_field_namespace_permissions(
|
||||
/// Builds namespace annotations for the `_entities` field.
|
||||
pub(crate) fn get_entities_field_namespace_permissions(
|
||||
object_type_representation: &type_permissions::ObjectTypeWithPermissions,
|
||||
model: &resolved::model::Model,
|
||||
) -> HashMap<Role, FilterPermission> {
|
||||
model: &models::Model,
|
||||
) -> HashMap<Role, resolved::FilterPermission> {
|
||||
let mut permissions = HashMap::new();
|
||||
|
||||
match &model.select_permissions {
|
||||
|
@ -36,7 +36,7 @@ pub(crate) fn apollo_federation_field(
|
||||
) -> Result<ApolloFederationFieldOutput, crate::schema::Error> {
|
||||
let mut roles_type_permissions: HashMap<
|
||||
Role,
|
||||
HashMap<Qualified<CustomTypeName>, resolved::model::FilterPermission>,
|
||||
HashMap<Qualified<CustomTypeName>, resolved::FilterPermission>,
|
||||
> = HashMap::new();
|
||||
let mut typename_mappings = HashMap::new();
|
||||
for model in gds.metadata.models.values() {
|
||||
|
@ -53,7 +53,7 @@ pub(crate) fn relay_node_field(
|
||||
|
||||
let mut roles_type_permissions: HashMap<
|
||||
Role,
|
||||
HashMap<Qualified<CustomTypeName>, resolved::model::FilterPermission>,
|
||||
HashMap<Qualified<CustomTypeName>, resolved::FilterPermission>,
|
||||
> = HashMap::new();
|
||||
for model in gds.metadata.models.values() {
|
||||
if let Some(global_id_source) = &model.global_id_source {
|
||||
|
@ -22,7 +22,7 @@ use crate::schema::{
|
||||
/// limit, offset, order_by and where.
|
||||
pub(crate) fn generate_select_many_arguments(
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
model: &resolved::model::Model,
|
||||
model: &resolved::Model,
|
||||
) -> Result<
|
||||
BTreeMap<Name, gql_schema::Namespaced<GDS, gql_schema::InputField<GDS>>>,
|
||||
crate::schema::Error,
|
||||
@ -97,8 +97,8 @@ pub(crate) fn generate_select_many_arguments(
|
||||
pub(crate) fn select_many_field(
|
||||
gds: &GDS,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
model: &resolved::model::Model,
|
||||
select_many: &resolved::model::SelectManyGraphQlDefinition,
|
||||
model: &resolved::Model,
|
||||
select_many: &resolved::SelectManyGraphQlDefinition,
|
||||
parent_type: &ast::TypeName,
|
||||
) -> Result<
|
||||
(
|
||||
|
@ -21,8 +21,8 @@ use crate::schema::{
|
||||
pub(crate) fn select_one_field(
|
||||
gds: &GDS,
|
||||
builder: &mut gql_schema::Builder<GDS>,
|
||||
model: &resolved::model::Model,
|
||||
select_unique: &resolved::model::SelectUniqueGraphQlDefinition,
|
||||
model: &resolved::Model,
|
||||
select_unique: &resolved::SelectUniqueGraphQlDefinition,
|
||||
parent_type: &ast::TypeName,
|
||||
) -> Result<
|
||||
(
|
||||
|
@ -64,7 +64,7 @@ pub struct NodeFieldTypeNameMapping {
|
||||
pub type_name: Qualified<types::CustomTypeName>,
|
||||
// `model_source` is are optional because we allow building schema without specifying a data source
|
||||
// In such a case, `global_id_fields_ndc_mapping` will also be empty
|
||||
pub model_source: Option<resolved::model::ModelSource>,
|
||||
pub model_source: Option<resolved::ModelSource>,
|
||||
pub global_id_fields_ndc_mapping: HashMap<types::FieldName, NdcColumnForComparison>,
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ pub struct EntityFieldTypeNameMapping {
|
||||
pub type_name: Qualified<types::CustomTypeName>,
|
||||
// `model_source` is are optional because we allow building schema without specifying a data source
|
||||
// In such a case, `global_id_fields_ndc_mapping` will also be empty
|
||||
pub model_source: Option<resolved::model::ModelSource>,
|
||||
pub model_source: Option<resolved::ModelSource>,
|
||||
pub key_fields_ndc_mapping: HashMap<types::FieldName, NdcColumnForComparison>,
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ pub enum RootFieldAnnotation {
|
||||
},
|
||||
Model {
|
||||
data_type: Qualified<types::CustomTypeName>,
|
||||
source: Option<resolved::model::ModelSource>,
|
||||
source: Option<resolved::ModelSource>,
|
||||
// select_permissions: HashMap<Role, resolved::SelectPermission>,
|
||||
kind: RootFieldKind,
|
||||
name: Qualified<models::ModelName>,
|
||||
@ -307,7 +307,7 @@ pub enum NamespaceAnnotation {
|
||||
Command(ArgumentPresets),
|
||||
/// any filter and arguments for selecting from a model
|
||||
Model {
|
||||
filter: resolved::model::FilterPermission,
|
||||
filter: resolved::FilterPermission,
|
||||
argument_presets: ArgumentPresets,
|
||||
},
|
||||
/// The `NodeFieldTypeMappings` contains a Hashmap of typename to the filter permission.
|
||||
@ -315,13 +315,13 @@ pub enum NamespaceAnnotation {
|
||||
/// decoding, a typename will be obtained. We need to use that typename to look up the
|
||||
/// Hashmap to get the appropriate `resolved::model::FilterPermission`.
|
||||
NodeFieldTypeMappings(
|
||||
HashMapWithJsonKey<Qualified<types::CustomTypeName>, resolved::model::FilterPermission>,
|
||||
HashMapWithJsonKey<Qualified<types::CustomTypeName>, resolved::FilterPermission>,
|
||||
),
|
||||
/// `EntityTypeMappings` is similar to the `NodeFieldTypeMappings`. While executing the `_entities` field, the
|
||||
/// `representations` argument is used, which contains typename. We need to use that typename to look up the hashmap
|
||||
/// to get the appropriate `resolved::model::FilterPermission`.
|
||||
EntityTypeMappings(
|
||||
HashMapWithJsonKey<Qualified<types::CustomTypeName>, resolved::model::FilterPermission>,
|
||||
HashMapWithJsonKey<Qualified<types::CustomTypeName>, resolved::FilterPermission>,
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
metadata::resolved::{
|
||||
self,
|
||||
stages::models,
|
||||
subgraph::{
|
||||
deserialize_qualified_btreemap, serialize_qualified_btreemap, Qualified,
|
||||
QualifiedTypeReference,
|
||||
@ -87,13 +88,13 @@ pub struct PredicateRelationshipAnnotation {
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ModelTargetSource {
|
||||
pub(crate) model: resolved::model::ModelSource,
|
||||
pub(crate) model: models::ModelSource,
|
||||
pub(crate) capabilities: resolved::relationship::RelationshipCapabilities,
|
||||
}
|
||||
|
||||
impl ModelTargetSource {
|
||||
pub fn new(
|
||||
model: &resolved::model::Model,
|
||||
model: &models::Model,
|
||||
relationship: &resolved::relationship::Relationship,
|
||||
) -> Result<Option<Self>, schema::Error> {
|
||||
model
|
||||
@ -104,7 +105,7 @@ impl ModelTargetSource {
|
||||
}
|
||||
|
||||
pub fn from_model_source(
|
||||
model_source: &resolved::model::ModelSource,
|
||||
model_source: &models::ModelSource,
|
||||
relationship: &resolved::relationship::Relationship,
|
||||
) -> Result<Self, schema::Error> {
|
||||
Ok(Self {
|
||||
|
Loading…
Reference in New Issue
Block a user