use crate::{
fully_qualified_namespace,
graphql::{
extract_foreign_key_info, field_id, field_type_name,
inject_internal_types_into_document, is_list_type, list_field_type_name,
GraphQLSchema, GraphQLSchemaValidator, IdCol, BASE_SCHEMA,
},
join_table_name,
};
use async_graphql_parser::{
parse_schema,
types::{
EnumType, FieldDefinition, ObjectType, ServiceDocument, TypeDefinition, TypeKind,
TypeSystemDefinition, UnionType,
},
};
use async_graphql_value::ConstValue;
use std::collections::{BTreeMap, HashMap, HashSet};
use thiserror::Error;
use super::check_for_directive;
pub type ParsedResult<T> = Result<T, ParsedError>;
#[derive(Error, Debug)]
pub enum ParsedError {
#[error("Generic error")]
Generic,
#[error("GraphQL parser error: {0:?}")]
ParseError(#[from] async_graphql_parser::Error),
#[error("This TypeKind is unsupported.")]
UnsupportedTypeKind,
#[error("List types are unsupported.")]
ListTypesUnsupported,
#[error("Inconsistent use of virtual union types. {0:?}")]
InconsistentVirtualUnion(String),
#[error("Union member not found in parsed TypeDefintions. {0:?}")]
UnionMemberNotFound(String),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct JoinTableMeta {
parent: JoinTableRelation,
child: JoinTableRelation,
}
impl JoinTableMeta {
pub fn parent(&self) -> &JoinTableRelation {
&self.parent
}
pub fn child(&self) -> &JoinTableRelation {
&self.child
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct JoinTableRelation {
pub relation_type: JoinTableRelationType,
pub typedef_name: String,
pub column_name: String,
pub child_position: Option<usize>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum JoinTableRelationType {
Parent,
Child,
}
impl JoinTableMeta {
pub fn new(
parent_typedef_name: &str,
parent_column_name: &str,
child_typedef_name: &str,
child_column_name: &str,
child_position: Option<usize>,
) -> Self {
Self {
parent: JoinTableRelation {
relation_type: JoinTableRelationType::Parent,
typedef_name: parent_typedef_name.to_string(),
column_name: parent_column_name.to_string(),
child_position,
},
child: JoinTableRelation {
relation_type: JoinTableRelationType::Child,
typedef_name: child_typedef_name.to_string(),
column_name: child_column_name.to_string(),
child_position: None,
},
}
}
pub fn table_name(&self) -> String {
join_table_name(&self.parent_table_name(), &self.child_table_name())
}
pub fn parent_table_name(&self) -> String {
self.parent.typedef_name.to_lowercase()
}
pub fn parent_column_name(&self) -> String {
self.parent.column_name.clone()
}
pub fn child_table_name(&self) -> String {
self.child.typedef_name.to_lowercase()
}
pub fn child_column_name(&self) -> String {
self.child.column_name.clone()
}
}
pub fn build_schema_types_set(
ast: &ServiceDocument,
) -> (HashSet<String>, HashSet<String>) {
let types: HashSet<String> = ast
.definitions
.iter()
.filter_map(|def| {
if let TypeSystemDefinition::Type(typ) = def {
Some(&typ.node)
} else {
None
}
})
.map(|t| t.name.to_string())
.collect();
let directives = ast
.definitions
.iter()
.filter_map(|def| {
if let TypeSystemDefinition::Directive(dir) = def {
Some(dir.node.name.to_string())
} else {
None
}
})
.collect();
(types, directives)
}
#[derive(Debug, Clone)]
pub struct OrderedField(pub FieldDefinition, pub usize);
#[derive(Debug, Clone)]
pub struct ParsedGraphQLSchema {
namespace: String,
identifier: String,
type_names: HashSet<String>,
typedef_names_to_types: HashMap<String, String>,
objects: HashMap<String, ObjectType>,
unions: HashMap<String, TypeDefinition>,
enum_names: HashSet<String>,
union_names: HashSet<String>,
object_field_mappings: HashMap<String, BTreeMap<String, String>>,
virtual_type_names: HashSet<String>,
parsed_typedef_names: HashSet<String>,
field_type_mappings: HashMap<String, String>,
scalar_names: HashSet<String>,
field_type_optionality: HashMap<String, bool>,
field_defs: HashMap<String, (FieldDefinition, String)>,
foreign_key_mappings: HashMap<String, HashMap<String, (String, String)>>,
type_defs: HashMap<String, TypeDefinition>,
list_field_types: HashSet<String>,
list_type_defs: HashMap<String, TypeDefinition>,
join_table_meta: HashMap<String, Vec<JoinTableMeta>>,
object_ordered_fields: HashMap<String, Vec<OrderedField>>,
version: String,
internal_types: HashSet<String>,
}
impl Default for ParsedGraphQLSchema {
fn default() -> Self {
Self {
namespace: "".to_string(),
identifier: "".to_string(),
type_names: HashSet::new(),
typedef_names_to_types: HashMap::new(),
enum_names: HashSet::new(),
union_names: HashSet::new(),
objects: HashMap::new(),
virtual_type_names: HashSet::new(),
parsed_typedef_names: HashSet::new(),
field_type_mappings: HashMap::new(),
object_field_mappings: HashMap::new(),
scalar_names: HashSet::new(),
field_defs: HashMap::new(),
field_type_optionality: HashMap::new(),
foreign_key_mappings: HashMap::new(),
type_defs: HashMap::new(),
list_field_types: HashSet::new(),
list_type_defs: HashMap::new(),
unions: HashMap::new(),
join_table_meta: HashMap::new(),
object_ordered_fields: HashMap::new(),
version: String::default(),
internal_types: HashSet::new(),
}
}
}
impl ParsedGraphQLSchema {
pub fn new(
namespace: &str,
identifier: &str,
schema: Option<&GraphQLSchema>,
) -> ParsedResult<Self> {
let base_type_names = {
let base_ast = parse_schema(BASE_SCHEMA)?;
let mut base_decoder = SchemaDecoder::new();
base_decoder.decode_service_document(base_ast)?;
base_decoder.parsed_graphql_schema.type_names
};
let mut decoder = SchemaDecoder::new();
if let Some(schema) = schema {
let mut ast = parse_schema(schema.schema())?;
ast = inject_internal_types_into_document(ast, &base_type_names);
decoder.decode_service_document(ast)?;
decoder.parsed_graphql_schema.namespace = namespace.to_string();
decoder.parsed_graphql_schema.identifier = identifier.to_string();
decoder.parsed_graphql_schema.version = schema.version.clone();
};
let mut result = decoder.get_parsed_schema();
result.type_names.extend(base_type_names.clone());
result.scalar_names.extend(base_type_names);
Ok(result)
}
pub fn namespace(&self) -> &str {
&self.namespace
}
pub fn identifier(&self) -> &str {
&self.identifier
}
pub fn objects(&self) -> &HashMap<String, ObjectType> {
&self.objects
}
pub fn field_type_mappings(&self) -> &HashMap<String, String> {
&self.field_type_mappings
}
pub fn field_type_optionality(&self) -> &HashMap<String, bool> {
&self.field_type_optionality
}
pub fn type_defs(&self) -> &HashMap<String, TypeDefinition> {
&self.type_defs
}
pub fn field_defs(&self) -> &HashMap<String, (FieldDefinition, String)> {
&self.field_defs
}
pub fn foreign_key_mappings(
&self,
) -> &HashMap<String, HashMap<String, (String, String)>> {
&self.foreign_key_mappings
}
pub fn object_field_mappings(&self) -> &HashMap<String, BTreeMap<String, String>> {
&self.object_field_mappings
}
pub fn join_table_meta(&self) -> &HashMap<String, Vec<JoinTableMeta>> {
&self.join_table_meta
}
pub fn object_ordered_fields(&self) -> &HashMap<String, Vec<OrderedField>> {
&self.object_ordered_fields
}
pub fn scalar_type_for(&self, f: &FieldDefinition) -> String {
let typ_name = list_field_type_name(f);
if self.is_list_field_type(&typ_name) {
let typ_name = field_type_name(f);
if self.is_possible_foreign_key(&typ_name) {
let (ref_coltype, _ref_colname, _ref_tablename) =
extract_foreign_key_info(f, &self.field_type_mappings);
return ref_coltype;
} else if self.is_virtual_typedef(&typ_name) {
return "Json".to_string();
} else if self.is_enum_typedef(&typ_name) {
return "String".to_string();
} else {
return typ_name;
}
}
if self.is_possible_foreign_key(&typ_name) {
let (ref_coltype, _ref_colname, _ref_tablename) =
extract_foreign_key_info(f, &self.field_type_mappings);
return ref_coltype;
}
if self.is_virtual_typedef(&typ_name) {
return "Json".to_string();
}
if self.is_enum_typedef(&typ_name) {
return "String".to_string();
}
typ_name
}
pub fn get_union(&self, name: &str) -> Option<&TypeDefinition> {
self.unions.get(name)
}
pub fn storage_backed_typedefs(&self) -> Vec<(&String, &TypeDefinition)> {
self.type_defs
.iter()
.filter(|(_, t)| {
!matches!(&t.kind, TypeKind::Enum(_))
&& !self.is_internal_typedef(t.name.node.as_str())
})
.collect()
}
pub fn is_possible_foreign_key(&self, name: &str) -> bool {
self.parsed_typedef_names.contains(name)
&& !self.scalar_names.contains(name)
&& !self.is_enum_typedef(name)
&& !self.is_virtual_typedef(name)
&& !self.is_internal_typedef(name)
}
pub fn is_virtual_typedef(&self, name: &str) -> bool {
self.virtual_type_names.contains(name) && !self.is_enum_typedef(name)
}
pub fn is_enum_typedef(&self, name: &str) -> bool {
self.enum_names.contains(name)
}
pub fn is_list_field_type(&self, name: &str) -> bool {
self.list_field_types.contains(name)
}
pub fn is_list_typedef(&self, name: &str) -> bool {
self.list_type_defs.contains_key(name)
}
pub fn is_union_typedef(&self, name: &str) -> bool {
self.union_names.contains(name)
}
pub fn is_internal_typedef(&self, name: &str) -> bool {
self.internal_types.contains(name)
}
fn field_type(&self, cond: &str, name: &str) -> Option<&String> {
match self.object_field_mappings().get(cond) {
Some(fieldset) => fieldset.get(name),
_ => {
let tablename = cond.replace(['[', ']', '!'], "");
match self.object_field_mappings().get(&tablename) {
Some(fieldset) => fieldset.get(name),
_ => None,
}
}
}
}
fn typedef_type(&self, name: &str) -> Option<&String> {
self.typedef_names_to_types.get(name)
}
pub fn graphql_type(&self, cond: Option<&String>, name: &str) -> Option<&String> {
match cond {
Some(c) => self.field_type(c, name),
None => self.typedef_type(name),
}
}
pub fn has_type(&self, name: &str) -> bool {
self.type_names.contains(name)
}
pub fn fully_qualified_namespace(&self) -> String {
fully_qualified_namespace(&self.namespace, &self.identifier)
}
pub fn version(&self) -> &str {
&self.version
}
}
#[derive(Default)]
struct SchemaDecoder {
parsed_graphql_schema: ParsedGraphQLSchema,
}
impl SchemaDecoder {
fn new() -> Self {
Self {
..Default::default()
}
}
fn get_parsed_schema(self) -> ParsedGraphQLSchema {
self.parsed_graphql_schema
}
fn decode_service_document(&mut self, ast: ServiceDocument) -> ParsedResult<()> {
for def in ast.definitions.iter() {
self.decode_type_system_definifion(def)?;
}
self.build_typedef_names_to_types();
Ok(())
}
fn decode_type_system_definifion(
&mut self,
def: &TypeSystemDefinition,
) -> ParsedResult<()> {
if let TypeSystemDefinition::Type(t) = def {
let name = t.node.name.to_string();
let node = t.node.clone();
self.parsed_graphql_schema.type_names.insert(name.clone());
self.parsed_graphql_schema
.type_defs
.insert(name.clone(), node.clone());
match &t.node.kind {
TypeKind::Object(o) => self.decode_object_type(name, node, o),
TypeKind::Enum(e) => self.decode_enum_type(name, e),
TypeKind::Union(u) => self.decode_union_type(name, node, u),
TypeKind::Scalar => {
self.parsed_graphql_schema.scalar_names.insert(name.clone());
}
_ => {
return Err(ParsedError::UnsupportedTypeKind);
}
}
}
Ok(())
}
fn decode_enum_type(&mut self, name: String, e: &EnumType) {
self.parsed_graphql_schema
.virtual_type_names
.insert(name.clone());
self.parsed_graphql_schema.enum_names.insert(name.clone());
for val in &e.values {
let val_name = &val.node.value.to_string();
let val_id = format!("{}.{val_name}", name.clone());
self.parsed_graphql_schema
.object_field_mappings
.entry(name.clone())
.or_default()
.insert(val_name.to_string(), name.clone());
self.parsed_graphql_schema
.field_type_mappings
.insert(val_id, name.to_string());
}
}
fn decode_union_type(
&mut self,
union_name: String,
node: TypeDefinition,
u: &UnionType,
) {
GraphQLSchemaValidator::check_disallowed_graphql_typedef_name(&union_name);
self.parsed_graphql_schema
.parsed_typedef_names
.insert(union_name.clone());
self.parsed_graphql_schema
.unions
.insert(union_name.clone(), node.clone());
self.parsed_graphql_schema
.union_names
.insert(union_name.clone());
GraphQLSchemaValidator::check_derived_union_virtuality_is_well_formed(
&node,
&mut self.parsed_graphql_schema.virtual_type_names,
);
let mut processed_fields = HashSet::new();
let mut child_position = 0;
let mut union_member_field_types = HashMap::new();
u.members.iter().for_each(|m| {
let member_name = m.node.to_string();
if let Some(name) = self
.parsed_graphql_schema
.virtual_type_names
.get(&member_name)
{
self.parsed_graphql_schema
.virtual_type_names
.insert(name.to_owned());
}
if self
.parsed_graphql_schema
.join_table_meta
.contains_key(&member_name)
{
self.parsed_graphql_schema
.join_table_meta
.remove(&member_name);
}
let member_obj = self
.parsed_graphql_schema
.objects
.get(&member_name)
.expect("Union member not found in parsed TypeDefinitions.");
member_obj.fields.iter().for_each(|f| {
let ftype = field_type_name(&f.node);
let field_id = field_id(&union_name, &f.node.name.to_string());
union_member_field_types
.entry(field_id.clone())
.or_insert(HashSet::new())
.insert(ftype.clone());
GraphQLSchemaValidator::derived_field_type_is_consistent(
&union_name,
&f.node.name.to_string(),
union_member_field_types.get(&field_id).unwrap(),
);
if processed_fields.contains(&field_id) {
return;
}
processed_fields.insert(field_id.clone());
if self
.parsed_graphql_schema
.parsed_typedef_names
.contains(&ftype)
&& !self.parsed_graphql_schema.scalar_names.contains(&ftype)
&& !self.parsed_graphql_schema.enum_names.contains(&ftype)
&& !self
.parsed_graphql_schema
.virtual_type_names
.contains(&ftype)
&& !self.parsed_graphql_schema.internal_types.contains(&ftype)
{
let (_ref_coltype, ref_colname, ref_tablename) =
extract_foreign_key_info(
&f.node,
&self.parsed_graphql_schema.field_type_mappings,
);
if is_list_type(&f.node) {
self.parsed_graphql_schema
.join_table_meta
.entry(union_name.clone())
.or_default()
.push(JoinTableMeta::new(
&union_name.to_lowercase(),
IdCol::to_lowercase_str(),
&ref_tablename,
&ref_colname,
Some(child_position),
));
}
}
child_position += 1;
});
});
u.members.iter().for_each(|m| {
let member_name = m.node.to_string();
let member_obj =
self.parsed_graphql_schema
.objects
.get(&member_name)
.unwrap_or_else(|| {
panic!(
"Union member not found in parsed TypeDefinitions: {member_name}")
});
member_obj.fields.iter().for_each(|f| {
let fid = field_id(&union_name, &f.node.name.to_string());
self.parsed_graphql_schema
.field_defs
.insert(fid.clone(), (f.node.clone(), member_name.clone()));
self.parsed_graphql_schema
.field_type_mappings
.insert(fid.clone(), field_type_name(&f.node));
self.parsed_graphql_schema
.object_field_mappings
.entry(union_name.clone())
.or_default()
.insert(f.node.name.to_string(), field_type_name(&f.node));
self.parsed_graphql_schema
.field_type_optionality
.insert(fid, f.node.ty.node.nullable);
});
});
}
fn decode_object_type(
&mut self,
obj_name: String,
node: TypeDefinition,
o: &ObjectType,
) {
GraphQLSchemaValidator::check_disallowed_graphql_typedef_name(&obj_name);
let is_internal = check_for_directive(&node.directives, "internal");
let is_entity = check_for_directive(&node.directives, "entity");
if is_internal {
self.parsed_graphql_schema
.internal_types
.insert(obj_name.clone());
}
if !is_entity && !is_internal {
println!("Skipping TypeDefinition '{obj_name}', which is not marked with an @entity directive.");
return;
}
self.parsed_graphql_schema
.objects
.insert(obj_name.clone(), o.clone());
self.parsed_graphql_schema
.parsed_typedef_names
.insert(obj_name.clone());
let is_virtual = node
.directives
.iter()
.flat_map(|d| d.node.arguments.clone())
.any(|t| t.0.node == "virtual" && t.1.node == ConstValue::Boolean(true));
if is_virtual {
self.parsed_graphql_schema
.virtual_type_names
.insert(obj_name.clone());
GraphQLSchemaValidator::virtual_type_has_no_id_field(o, &obj_name);
}
let mut m2m_field_count = 0;
let mut field_mapping = BTreeMap::new();
for (i, field) in o.fields.iter().enumerate() {
GraphQLSchemaValidator::id_field_is_type_id(&field.node, &obj_name);
let field_name = field.node.name.to_string();
let field_typ_name = field.node.ty.to_string();
let fid = field_id(&obj_name, &field_name);
GraphQLSchemaValidator::ensure_fielddef_is_not_nested_list(&field.node);
self.parsed_graphql_schema
.object_ordered_fields
.entry(obj_name.clone())
.or_default()
.push(OrderedField(field.node.clone(), i));
if is_list_type(&field.node) {
self.parsed_graphql_schema
.list_field_types
.insert(field_typ_name.replace('!', ""));
self.parsed_graphql_schema
.list_type_defs
.insert(obj_name.clone(), node.clone());
}
let ftype = field_type_name(&field.node);
if self
.parsed_graphql_schema
.parsed_typedef_names
.contains(&field_type_name(&field.node))
&& !self.parsed_graphql_schema.scalar_names.contains(&ftype)
&& !self.parsed_graphql_schema.enum_names.contains(&ftype)
&& !self
.parsed_graphql_schema
.virtual_type_names
.contains(&ftype)
&& !self.parsed_graphql_schema.internal_types.contains(&ftype)
&& !is_internal
{
GraphQLSchemaValidator::foreign_key_field_contains_no_unique_directive(
&field.node,
&obj_name,
);
let (ref_coltype, ref_colname, ref_tablename) = extract_foreign_key_info(
&field.node,
&self.parsed_graphql_schema.field_type_mappings,
);
if is_list_type(&field.node) {
GraphQLSchemaValidator::m2m_fk_field_ref_col_is_id(
&field.node,
&obj_name,
&ref_coltype,
&ref_colname,
);
m2m_field_count += 1;
GraphQLSchemaValidator::verify_m2m_relationship_count(
&obj_name,
m2m_field_count,
);
self.parsed_graphql_schema
.join_table_meta
.entry(obj_name.clone())
.or_default()
.push(JoinTableMeta::new(
&obj_name.to_lowercase(),
IdCol::to_lowercase_str(),
&ref_tablename,
&ref_colname,
Some(i),
));
}
let fk = self
.parsed_graphql_schema
.foreign_key_mappings
.get_mut(&obj_name.to_lowercase());
match fk {
Some(fks_for_field) => {
fks_for_field.insert(
field.node.name.to_string(),
(
field_type_name(&field.node).to_lowercase(),
ref_colname.clone(),
),
);
}
None => {
let fks_for_field = HashMap::from([(
field.node.name.to_string(),
(
field_type_name(&field.node).to_lowercase(),
ref_colname.clone(),
),
)]);
self.parsed_graphql_schema
.foreign_key_mappings
.insert(obj_name.to_lowercase(), fks_for_field);
}
}
}
let field_typ_name = field_type_name(&field.node);
self.parsed_graphql_schema
.parsed_typedef_names
.insert(field_name.clone());
field_mapping.insert(field_name, field_typ_name.clone());
self.parsed_graphql_schema
.field_type_optionality
.insert(fid.clone(), field.node.ty.node.nullable);
self.parsed_graphql_schema
.field_type_mappings
.insert(fid.clone(), field_typ_name);
self.parsed_graphql_schema
.field_defs
.insert(fid, (field.node.clone(), obj_name.clone()));
}
self.parsed_graphql_schema
.object_field_mappings
.insert(obj_name, field_mapping);
}
fn build_typedef_names_to_types(&mut self) {
self.parsed_graphql_schema.typedef_names_to_types = self
.parsed_graphql_schema
.type_defs
.iter()
.filter(|(_, t)| !matches!(&t.kind, TypeKind::Enum(_)))
.collect::<Vec<(&String, &TypeDefinition)>>()
.into_iter()
.fold(HashMap::new(), |mut acc, (k, _)| {
acc.insert(k.to_lowercase(), k.clone());
acc
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parser_caches_all_related_typedefs_when_instantiated() {
let schema = r#"
enum AccountLabel {
PRIMARY
SECONDARY
}
type Account @entity {
id: ID!
address: Address!
label: AccountLabel
}
type User @entity {
id: ID!
account: Account!
username: String!
}
type Loser @entity {
id: ID!
account: Account!
age: U64!
}
type Metadata @entity(virtual: true) {
count: U64!
}
union Person = User | Loser
type Wallet @entity {
id: ID!
accounts: [Account!]!
}
type Safe @entity {
id: ID!
account: [Account!]!
}
type Vault @entity {
id: ID!
label: String!
user: [User!]!
}
union Storage = Safe | Vault
"#;
let parsed = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
);
assert!(parsed.is_ok());
let parsed = parsed.unwrap();
assert!(parsed.has_type("Account"));
assert!(parsed.has_type("User"));
assert!(parsed.is_possible_foreign_key("Account"));
assert!(parsed.is_virtual_typedef("Metadata"));
assert!(parsed.is_enum_typedef("AccountLabel"));
assert!(parsed
.field_type_optionality()
.contains_key("Account.label"));
assert!(parsed.is_union_typedef("Person"));
assert!(parsed.is_list_typedef("Wallet"));
assert_eq!(parsed.join_table_meta().len(), 2);
assert_eq!(
parsed.join_table_meta().get("Wallet").unwrap()[0],
JoinTableMeta::new("wallet", "id", "account", "id", Some(1))
);
assert!(!parsed.join_table_meta().contains_key("Safe"));
assert!(!parsed.join_table_meta().contains_key("Vault"));
assert!(parsed.join_table_meta().contains_key("Storage"));
assert!(parsed.join_table_meta().get("Storage").unwrap().len() == 2);
assert_eq!(
parsed.join_table_meta().get("Storage").unwrap()[0],
JoinTableMeta::new("storage", "id", "account", "id", Some(1))
);
assert_eq!(
parsed.join_table_meta().get("Storage").unwrap()[1],
JoinTableMeta::new("storage", "id", "user", "id", Some(4))
);
assert!(parsed.internal_types.contains("AccountConnection"));
assert!(parsed.internal_types.contains("AccountEdge"));
assert!(parsed.internal_types.contains("UserConnection"));
assert!(parsed.internal_types.contains("UserEdge"));
}
#[test]
fn test_internal_type_defs_in_object_field_mapping() {
let schema = r#"
type Foo @entity {
id: ID!
name: String!
}
type Bar @entity {
id: ID!
foo: [Foo!]!
}
"#;
let parsed = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
);
assert!(parsed.is_ok());
let parsed = parsed.unwrap();
let bar_entity_fields = parsed.object_field_mappings.get("Bar").unwrap();
assert_eq!(
bar_entity_fields.get("fooConnection").unwrap(),
&"FooConnection"
);
}
#[test]
#[should_panic(expected = "TypeDefinition name 'TransactionData' is reserved.")]
fn test_schema_validator_check_disallowed_graphql_typedef_name() {
let schema = r#"
type Foo @entity {
id: ID!
}
type TransactionData @entity {
id: ID!
}
"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
#[test]
#[should_panic(
expected = "TypeDefinition(Union(Baz)) does not have consistent virtual/non-virtual members."
)]
fn test_schema_validator_check_derived_union_virtuality_is_well_formed() {
let schema = r#"
type Foo @entity {
id: ID!
name: String!
}
type Bar @entity {
id: ID!
age: U64!
}
type Zoo @entity(virtual: true) {
height: U64!
}
union Baz = Foo | Bar | Zoo
"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
#[test]
#[should_panic(
expected = "Derived type from Union(Baz) contains Field(name) which does not have a consistent type across all members."
)]
fn test_schema_validator_derived_field_type_is_consistent() {
let schema = r#"
type Foo @entity {
id: ID!
name: String!
}
type Bar @entity {
id: ID!
age: U64!
}
type Zoo @entity {
id: ID!
name: U64!
}
union Baz = Foo | Bar | Zoo
"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
#[test]
#[should_panic(
expected = "FieldDefinition(nested) is a nested list, which is not supported."
)]
fn test_schema_validator_ensure_fielddef_is_not_nested_list() {
let schema = r#"
type Foo @entity {
id: ID!
name: String!
}
type Zoo @entity {
id: ID!
nested: [[Foo!]!]!
}
"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
#[test]
#[should_panic(
expected = "FieldDefinition(id) on TypeDefinition(Foo) must be of type `ID!`. Found type `String!`."
)]
fn test_schema_validator_id_field_is_type_id() {
let schema = r#"
type Foo @entity {
id: String!
name: String!
}"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
#[test]
#[should_panic(
expected = "Virtual TypeDefinition(Foo) cannot contain an `id: ID!` FieldDefinition."
)]
fn test_schema_validator_virtual_type_has_no_id_field() {
let schema = r#"
type Foo @entity(virtual: true) {
id: ID!
name: String!
}"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
#[test]
#[should_panic(
expected = "FieldDefinition(id) on TypeDefinition(Bar) must be of type `ID!`. Found type `ID`."
)]
fn test_schema_validator_foreign_key_field_contains_no_unique_directive() {
let schema = r#"
type Foo @entity {
id: ID!
name: String!
}
type Bar @entity {
id: ID
foo: Foo! @unique
}
"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
#[test]
#[should_panic(
expected = "TypeDefinition(Bar) exceeds the allowed number of many-to-many` relationships. The maximum allowed is 10."
)]
fn test_schema_validator_verify_m2m_relationship_count() {
let schema = r#"
type Type1 @entity {
id: ID!
name: String!
}
type Type2 @entity {
id: ID!
name: String!
}
type Type3 @entity {
id: ID!
name: String!
}
type Type4 @entity {
id: ID!
name: String!
}
type Type5 @entity {
id: ID!
name: String!
}
type Type6 @entity {
id: ID!
name: String!
}
type Type7 @entity {
id: ID!
name: String!
}
type Type8 @entity {
id: ID!
name: String!
}
type Type9 @entity {
id: ID!
name: String!
}
type Type10 @entity {
id: ID!
name: String!
}
type Type11 @entity {
id: ID!
name: String!
}
type Bar @entity {
id: ID!
type1: [Type1!]!
type2: [Type2!]!
type3: [Type3!]!
type4: [Type4!]!
type5: [Type5!]!
type6: [Type6!]!
type7: [Type7!]!
type8: [Type8!]!
type9: [Type9!]!
type10: [Type10!]!
type11: [Type11!]!
}
"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
#[test]
#[should_panic(
expected = "FieldDefinition(foo) on TypeDefinition(Bar) is a many-to-many relationship where the inner scalar is of type `name: String!`. However, only inner scalars of type `id: ID!` are allowed."
)]
fn test_schema_validator_m2m_fk_field_ref_col_is_id() {
let schema = r#"
type Foo @entity {
id: ID!
name: String!
}
type Bar @entity {
id: ID!
foo: [Foo!]! @join(on:name)
}
"#;
let _ = ParsedGraphQLSchema::new(
"test",
"test",
Some(&GraphQLSchema::new(schema.to_string())),
)
.unwrap();
}
}