use std::borrow::Cow;
use crate::ast;
use crate::compat::graphql_parser_v0_4::from_graphql_parser_schema_ast;
use crate::compat::graphql_parser_v0_4::from_graphql_parser_schema_ast_with_source;
use crate::SourceMap;
use graphql_parser::schema::Definition as GpDef;
use graphql_parser::schema::DirectiveDefinition
as GpDirectiveDef;
use graphql_parser::schema::DirectiveLocation
as GpDirLoc;
use graphql_parser::schema::EnumType as GpEnum;
use graphql_parser::schema::EnumValue as GpEnumValue;
use graphql_parser::schema::InputObjectType
as GpInputObject;
use graphql_parser::schema::InterfaceType
as GpInterface;
use graphql_parser::schema::ObjectType as GpObject;
use graphql_parser::schema::ScalarType as GpScalar;
use graphql_parser::schema::SchemaDefinition
as GpSchemaDef;
use graphql_parser::schema::TypeDefinition as GpTd;
use graphql_parser::schema::UnionType as GpUnion;
fn pos(
line: usize,
column: usize,
) -> graphql_parser::Pos {
graphql_parser::Pos { line, column }
}
fn resolve(
source: &str,
byte_offset: u32,
) -> (usize, usize) {
let sm = SourceMap::new_with_source(source, None);
let pos = sm.resolve_offset(byte_offset)
.expect(
"byte offset should be resolvable",
);
(pos.line(), pos.col_utf8())
}
#[test]
fn test_object_type() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeDefinition(
GpTd::Object(GpObject {
position: pos(2, 1),
description: Some(
"A user".to_string(),
),
name: "User".to_string(),
implements_interfaces: vec![
"Node".to_string(),
],
directives: vec![],
fields: vec![
graphql_parser::schema::Field {
position: pos(3, 3),
description: None,
name: "name".to_string(),
arguments: vec![],
field_type:
graphql_parser::schema::Type
::NonNullType(
Box::new(
graphql_parser::schema::Type
::NamedType(
"String"
.to_string(),
),
),
),
directives: vec![],
},
],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
assert_eq!(doc.definitions.len(), 1);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Object(obj),
) => {
assert_eq!(obj.name.value, "User");
assert_eq!(
obj.span.start, 0,
"Without source, byte offset is 0",
);
assert_eq!(
obj.description.as_ref().map(
|d| d.value.as_ref()
),
Some("A user"),
);
assert_eq!(obj.implements.len(), 1);
assert_eq!(
obj.implements[0].value,
"Node",
);
assert_eq!(obj.fields.len(), 1);
assert_eq!(
obj.fields[0].name.value, "name",
);
assert_eq!(
obj.fields[0].span.start, 0,
"Without source, byte offset is 0",
);
match &obj.fields[0].field_type {
ast::TypeAnnotation::Named(n) => {
assert_eq!(
n.name.value, "String",
);
assert!(matches!(
n.nullability,
ast::Nullability::NonNull {
..
},
));
},
other => panic!(
"Expected Named, got {other:?}",
),
}
},
other => panic!(
"Expected Object, got {other:?}",
),
}
}
#[test]
fn test_schema_definition() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![
GpDef::SchemaDefinition(GpSchemaDef {
position: pos(1, 1),
directives: vec![],
query: Some("Query".to_string()),
mutation: Some(
"Mutation".to_string(),
),
subscription: None,
}),
],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::SchemaDefinition(sd) => {
assert_eq!(sd.root_operations.len(), 2);
assert_eq!(
sd.root_operations[0]
.named_type
.value,
"Query",
);
assert_eq!(
sd.root_operations[0].operation_kind,
ast::OperationKind::Query,
);
assert_eq!(
sd.root_operations[1]
.named_type
.value,
"Mutation",
);
assert_eq!(
sd.root_operations[1].operation_kind,
ast::OperationKind::Mutation,
);
},
other => panic!(
"Expected SchemaDefinition, got {other:?}",
),
}
}
#[test]
fn test_scalar_type() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeDefinition(
GpTd::Scalar(GpScalar {
position: pos(1, 1),
description: None,
name: "DateTime".to_string(),
directives: vec![],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => {
assert_eq!(s.name.value, "DateTime");
assert!(s.description.is_none());
},
other => panic!(
"Expected Scalar, got {other:?}",
),
}
}
#[test]
fn test_enum_type() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeDefinition(
GpTd::Enum(GpEnum {
position: pos(1, 1),
description: None,
name: "Status".to_string(),
directives: vec![],
values: vec![
GpEnumValue {
position: pos(2, 3),
description: None,
name: "ACTIVE".to_string(),
directives: vec![],
},
GpEnumValue {
position: pos(3, 3),
description: Some(
"Deactivated"
.to_string(),
),
name: "INACTIVE"
.to_string(),
directives: vec![],
},
],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Enum(e),
) => {
assert_eq!(e.name.value, "Status");
assert_eq!(e.values.len(), 2);
assert_eq!(
e.values[0].name.value, "ACTIVE",
);
assert_eq!(
e.values[0].span.start, 0,
"Without source, byte offset is 0",
);
assert_eq!(
e.values[1].name.value, "INACTIVE",
);
assert_eq!(
e.values[1]
.description
.as_ref()
.map(|d| d.value.as_ref()),
Some("Deactivated"),
);
},
other => panic!(
"Expected Enum, got {other:?}",
),
}
}
#[test]
fn test_interface_type() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeDefinition(
GpTd::Interface(GpInterface {
position: pos(4, 1),
description: Some(
"A node".to_string(),
),
name: "Node".to_string(),
implements_interfaces: vec![],
directives: vec![],
fields: vec![
graphql_parser::schema::Field {
position: pos(5, 3),
description: None,
name: "id".to_string(),
arguments: vec![],
field_type:
graphql_parser::schema::Type
::NamedType(
"ID".to_string(),
),
directives: vec![],
},
],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Interface(iface),
) => {
assert_eq!(iface.name.value, "Node");
assert_eq!(
iface.span.start, 0,
"Without source, byte offset is 0",
);
assert_eq!(iface.fields.len(), 1);
assert_eq!(
iface.fields[0].name.value, "id",
);
},
other => panic!(
"Expected Interface, got {other:?}",
),
}
}
#[test]
fn test_union_type() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeDefinition(
GpTd::Union(GpUnion {
position: pos(6, 1),
description: None,
name: "SearchResult".to_string(),
directives: vec![],
types: vec![
"User".to_string(),
"Post".to_string(),
],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Union(u),
) => {
assert_eq!(
u.name.value, "SearchResult",
);
assert_eq!(u.members.len(), 2);
assert_eq!(
u.members[0].value, "User",
);
assert_eq!(
u.members[1].value, "Post",
);
},
other => panic!(
"Expected Union, got {other:?}",
),
}
}
#[test]
fn test_input_object_type() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeDefinition(
GpTd::InputObject(GpInputObject {
position: pos(7, 1),
description: None,
name: "CreateUserInput"
.to_string(),
directives: vec![],
fields: vec![
graphql_parser::schema::InputValue {
position: pos(8, 3),
description: None,
name: "name".to_string(),
value_type:
graphql_parser::schema::Type
::NamedType(
"String".to_string(),
),
default_value: None,
directives: vec![],
},
],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::InputObject(io),
) => {
assert_eq!(
io.name.value, "CreateUserInput",
);
assert_eq!(io.fields.len(), 1);
assert_eq!(
io.fields[0].name.value, "name",
);
},
other => panic!(
"Expected InputObject, got {other:?}",
),
}
}
#[test]
fn test_directive_definition() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![
GpDef::DirectiveDefinition(
GpDirectiveDef {
position: pos(9, 1),
description: Some(
"Cache hint".to_string(),
),
name: "cached".to_string(),
arguments: vec![],
repeatable: true,
locations: vec![
GpDirLoc::FieldDefinition,
GpDirLoc::Object,
],
},
),
],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::DirectiveDefinition(dd) => {
assert_eq!(dd.name.value, "cached");
assert!(dd.repeatable);
assert_eq!(
dd.description
.as_ref()
.map(|d| d.value.as_ref()),
Some("Cache hint"),
);
assert_eq!(dd.locations.len(), 2);
assert_eq!(
dd.locations[0].kind,
ast::DirectiveLocationKind
::FieldDefinition,
);
assert_eq!(
dd.locations[1].kind,
ast::DirectiveLocationKind::Object,
);
},
other => panic!(
"Expected DirectiveDefinition, got {other:?}",
),
}
}
#[test]
fn test_type_extension() {
use graphql_parser::schema::ObjectTypeExtension
as GpObjExt;
use graphql_parser::schema::TypeExtension
as GpTe;
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeExtension(
GpTe::Object(GpObjExt {
position: pos(10, 1),
name: "User".to_string(),
implements_interfaces: vec![],
directives: vec![],
fields: vec![
graphql_parser::schema::Field {
position: pos(11, 3),
description: None,
name: "age".to_string(),
arguments: vec![],
field_type:
graphql_parser::schema::Type
::NamedType(
"Int".to_string(),
),
directives: vec![],
},
],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeExtension(
ast::TypeExtension::Object(ext),
) => {
assert_eq!(ext.name.value, "User");
assert_eq!(
ext.span.start, 0,
"Without source, byte offset is 0",
);
assert_eq!(ext.fields.len(), 1);
assert_eq!(
ext.fields[0].name.value, "age",
);
},
other => panic!(
"Expected Object extension, got {other:?}",
),
}
}
#[test]
fn test_syntax_fields_are_none() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeDefinition(
GpTd::Scalar(GpScalar {
position: pos(1, 1),
description: None,
name: "JSON".to_string(),
directives: vec![],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => {
assert!(s.syntax.is_none());
},
_ => panic!("Expected Scalar"),
}
assert!(doc.syntax.is_none());
}
#[test]
fn test_strings_are_cow_owned() {
let gp_doc = graphql_parser::schema::Document {
definitions: vec![GpDef::TypeDefinition(
GpTd::Scalar(GpScalar {
position: pos(1, 1),
description: Some(
"JSON data".to_string(),
),
name: "JSON".to_string(),
directives: vec![],
}),
)],
};
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => {
assert!(matches!(
&s.name.value,
Cow::Owned(_),
));
assert!(matches!(
&s.description.as_ref().unwrap().value,
Cow::Owned(_),
));
},
_ => panic!("Expected Scalar"),
}
}
#[test]
fn test_without_source_byte_offsets_are_zero() {
let source = "type User {\n name: String\n}\n";
let gp_doc: graphql_parser::schema::Document<
'static,
String,
> = graphql_parser::parse_schema::<String>(source)
.expect("valid schema");
let doc = from_graphql_parser_schema_ast(&gp_doc);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Object(obj),
) => {
assert_eq!(
obj.span.start, 0,
"Without source, byte_offset should \
be 0",
);
assert_eq!(
obj.fields[0].span.start, 0,
"Without source, field byte_offset \
should be 0",
);
},
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_with_source_computes_byte_offsets() {
let source =
"type User {\n name: String\n}\n";
let gp_doc: graphql_parser::schema::Document<
'static,
String,
> = graphql_parser::parse_schema::<String>(source)
.expect("valid schema");
let doc =
from_graphql_parser_schema_ast_with_source(
&gp_doc, source,
);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Object(obj),
) => {
assert_eq!(
obj.span.start as usize, 0,
);
assert_eq!(
resolve(source, obj.span.start),
(0, 0),
);
assert_eq!(
obj.fields[0].span.start as usize,
14,
);
assert_eq!(
resolve(
source,
obj.fields[0].span.start,
),
(1, 2),
);
},
_ => panic!("Expected Object type"),
}
}
#[test]
fn test_with_source_multi_type_byte_offsets() {
let source = "\
scalar Date
\"A user\"
type User {
id: ID!
name: String
}
enum Role {
ADMIN
USER
}
";
let gp_doc: graphql_parser::schema::Document<
'static,
String,
> = graphql_parser::parse_schema::<String>(source)
.expect("valid schema");
let doc =
from_graphql_parser_schema_ast_with_source(
&gp_doc, source,
);
assert_eq!(doc.definitions.len(), 3);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => {
assert_eq!(
s.span.start as usize, 0,
);
},
_ => panic!("Expected Scalar"),
}
match &doc.definitions[1] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Object(obj),
) => {
assert_eq!(
obj.span.start as usize, 22,
);
assert_eq!(
resolve(source, obj.span.start).0,
3,
);
assert_eq!(
obj.fields[0].span.start as usize,
36,
);
},
_ => panic!("Expected Object"),
}
match &doc.definitions[2] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Enum(e),
) => {
assert_eq!(
e.span.start as usize, 62,
);
assert_eq!(
resolve(source, e.span.start).0,
8,
);
},
_ => panic!("Expected Enum"),
}
}
#[test]
fn test_with_source_bare_cr_line_endings() {
let source = "scalar A\rscalar B\r";
let gp_doc = graphql_parser::schema::Document {
definitions: vec![
GpDef::TypeDefinition(GpTd::Scalar(GpScalar {
position: pos(1, 1),
description: None,
name: "A".to_string(),
directives: vec![],
})),
GpDef::TypeDefinition(GpTd::Scalar(GpScalar {
position: pos(2, 1),
description: None,
name: "B".to_string(),
directives: vec![],
})),
],
};
let doc = from_graphql_parser_schema_ast_with_source(
&gp_doc, source,
);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => {
assert_eq!(
s.span.start as usize, 0,
"A should be at byte 0",
);
},
_ => panic!("Expected Scalar A"),
}
match &doc.definitions[1] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => {
assert_eq!(
s.span.start as usize, 9,
"B should be at byte 9 (after 'scalar A\\r')",
);
},
_ => panic!("Expected Scalar B"),
}
}
#[test]
fn test_with_source_crlf_line_endings() {
let source = "scalar A\r\nscalar B\r\n";
let gp_doc = graphql_parser::schema::Document {
definitions: vec![
GpDef::TypeDefinition(GpTd::Scalar(GpScalar {
position: pos(1, 1),
description: None,
name: "A".to_string(),
directives: vec![],
})),
GpDef::TypeDefinition(GpTd::Scalar(GpScalar {
position: pos(2, 1),
description: None,
name: "B".to_string(),
directives: vec![],
})),
],
};
let doc = from_graphql_parser_schema_ast_with_source(
&gp_doc, source,
);
match &doc.definitions[0] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => {
assert_eq!(
s.span.start as usize, 0,
"A should be at byte 0",
);
},
_ => panic!("Expected Scalar A"),
}
match &doc.definitions[1] {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => {
assert_eq!(
s.span.start as usize, 10,
"B should be at byte 10 (after 'scalar A\\r\\n')",
);
},
_ => panic!("Expected Scalar B"),
}
}
#[test]
fn test_with_source_mixed_line_endings() {
let source =
"scalar A\nscalar B\r\nscalar C\rscalar D";
let gp_doc = graphql_parser::schema::Document {
definitions: vec![
GpDef::TypeDefinition(GpTd::Scalar(GpScalar {
position: pos(1, 1),
description: None,
name: "A".to_string(),
directives: vec![],
})),
GpDef::TypeDefinition(GpTd::Scalar(GpScalar {
position: pos(2, 1),
description: None,
name: "B".to_string(),
directives: vec![],
})),
GpDef::TypeDefinition(GpTd::Scalar(GpScalar {
position: pos(3, 1),
description: None,
name: "C".to_string(),
directives: vec![],
})),
GpDef::TypeDefinition(GpTd::Scalar(GpScalar {
position: pos(4, 1),
description: None,
name: "D".to_string(),
directives: vec![],
})),
],
};
let doc = from_graphql_parser_schema_ast_with_source(
&gp_doc, source,
);
let byte_offsets: Vec<usize> = doc.definitions
.iter()
.map(|d| match d {
ast::Definition::TypeDefinition(
ast::TypeDefinition::Scalar(s),
) => s.span.start as usize,
_ => panic!("Expected Scalar"),
})
.collect();
assert_eq!(byte_offsets[0], 0, "A at byte 0");
assert_eq!(
byte_offsets[1], 9,
"B at byte 9 (after 'scalar A\\n')",
);
assert_eq!(
byte_offsets[2], 19,
"C at byte 19 (after 'scalar B\\r\\n')",
);
assert_eq!(
byte_offsets[3], 28,
"D at byte 28 (after 'scalar C\\r')",
);
}