use super::harness::{
assert_coerced, assert_forwarded, assert_rejected, assert_rejected_by_validation,
};
fn operation(variable_type: &str) -> String {
format!("query ($value: {variable_type}) {{ fieldWithNullableStringInput }}")
}
fn variables(value: &str) -> String {
format!(r#"{{"value":{value}}}"#)
}
#[track_caller]
fn accepts(variable_type: &str, value: &str) {
assert_coerced(
&operation(variable_type),
&variables(value),
&variables(value),
);
}
#[track_caller]
fn rejects(variable_type: &str, value: &str, path: &str, reason: &str) {
assert_rejected(
&operation(variable_type),
&variables(value),
&format!(r#"Variable "$value" has invalid value{path}: {reason}"#),
);
}
mod non_null {
use super::*;
#[test]
fn non_null_returns_no_error_for_non_null_value() {
accepts("Int!", "1");
}
#[test]
fn non_null_value_is_coerced_by_the_wrapped_type() {
rejects(
"Int!",
r#""1""#,
"",
r#"Int cannot represent non-integer value: "1""#,
);
}
#[test]
fn non_null_list_variable_missing() {
assert_rejected(
"query ($input: [String]!) { nnList(input: $input) }",
"{}",
r#"Variable "$input" has invalid value: Expected a value of non-null type "[String]!" to be provided."#,
);
}
}
mod spec_non_null_examples {
use super::*;
#[test]
fn spec_non_null_argument_cannot_be_omitted() {
assert_rejected_by_validation("{ withNonNullArg }", "ProvidedRequiredArguments");
}
#[test]
fn spec_null_literal_for_non_null_argument() {
assert_rejected_by_validation(
"{ withNonNullArg(cannotBeNull: null) }",
"ValuesOfCorrectType",
);
}
#[test]
fn spec_nullable_variable_for_non_null_argument() {
assert_rejected_by_validation(
"query withNullableVariable($var: String) { withNonNullArg(cannotBeNull: $var) }",
"VariablesInAllowedPosition",
);
}
}
mod nullability_matrix {
use super::*;
const LIST: &str = "query ($input: [String]) { list(input: $input) }";
const NN_LIST: &str = "query ($input: [String]!) { nnList(input: $input) }";
const LIST_NN: &str = "query ($input: [String!]) { listNN(input: $input) }";
const NN_LIST_NN: &str = "query ($input: [String!]!) { nnListNN(input: $input) }";
fn input(value: &str) -> String {
format!(r#"{{"input":{value}}}"#)
}
#[track_caller]
fn accepts(operation: &str, value: &str) {
assert_coerced(operation, &input(value), &input(value));
}
#[track_caller]
fn rejects(operation: &str, value: &str, path: &str, reason: &str) {
assert_rejected(
operation,
&input(value),
&format!(r#"Variable "$input" has invalid value{path}: {reason}"#),
);
}
#[test]
fn allows_lists_to_be_null() {
accepts(LIST, "null");
}
#[test]
fn allows_lists_to_contain_values() {
accepts(LIST, r#"["A"]"#);
}
#[test]
fn allows_lists_to_contain_null() {
accepts(LIST, r#"["A",null,"B"]"#);
}
#[test]
fn does_not_allow_non_null_lists_to_be_null() {
rejects(
NN_LIST,
"null",
"",
r#"Expected value of non-null type "[String]!" not to be null."#,
);
}
#[test]
fn allows_non_null_lists_to_contain_values() {
accepts(NN_LIST, r#"["A"]"#);
}
#[test]
fn allows_non_null_lists_to_contain_null() {
accepts(NN_LIST, r#"["A",null,"B"]"#);
}
#[test]
fn allows_lists_of_non_nulls_to_be_null() {
accepts(LIST_NN, "null");
}
#[test]
fn allows_lists_of_non_nulls_to_contain_values() {
accepts(LIST_NN, r#"["A"]"#);
}
#[test]
fn does_not_allow_lists_of_non_nulls_to_contain_null() {
rejects(
LIST_NN,
r#"["A",null,"B"]"#,
" at [1]",
r#"Expected value of non-null type "String!" not to be null."#,
);
}
#[test]
fn does_not_allow_non_null_lists_of_non_nulls_to_be_null() {
rejects(
NN_LIST_NN,
"null",
"",
r#"Expected value of non-null type "[String!]!" not to be null."#,
);
}
#[test]
fn allows_non_null_lists_of_non_nulls_to_contain_values() {
accepts(NN_LIST_NN, r#"["A"]"#);
}
#[test]
fn does_not_allow_non_null_lists_of_non_nulls_to_contain_null() {
rejects(
NN_LIST_NN,
r#"["A",null,"B"]"#,
" at [1]",
r#"Expected value of non-null type "String!" not to be null."#,
);
}
#[test]
fn list_of_non_null_rejects_a_null_first_item() {
super::rejects(
"[Int!]",
"[null]",
" at [0]",
r#"Expected value of non-null type "Int!" not to be null."#,
);
}
#[test]
fn non_null_list_accepts_an_empty_list() {
accepts(NN_LIST, "[]");
}
#[test]
fn non_null_list_of_non_null_accepts_an_empty_list() {
accepts(NN_LIST_NN, "[]");
}
#[test]
fn non_null_list_of_non_null_accepts_a_single_value() {
accepts(NN_LIST_NN, r#""A""#);
}
}
mod list_coercion {
use super::*;
#[test]
fn list_returns_no_error_for_a_valid_input() {
accepts("[Int]", "[1,2,3]");
}
#[test]
fn list_returns_an_error_for_an_invalid_input() {
rejects(
"[Int]",
r#"[1,"b",true,4]"#,
" at [1]",
r#"Int cannot represent non-integer value: "b""#,
);
}
#[test]
fn list_accepts_a_non_list_value() {
accepts("[Int]", "42");
}
#[test]
fn list_returns_an_error_for_a_non_list_invalid_value() {
rejects(
"[Int]",
r#""INVALID""#,
"",
r#"Int cannot represent non-integer value: "INVALID""#,
);
}
#[test]
fn list_returns_null_for_a_null_value() {
accepts("[Int]", "null");
}
#[test]
fn list_accepts_an_empty_list() {
accepts("[Int]", "[]");
}
#[test]
fn list_of_string_accepts_a_single_string() {
accepts("[String]", r#""AB""#);
}
#[test]
fn list_of_string_rejects_a_single_number() {
rejects(
"[String]",
"123",
"",
"String cannot represent a non string value: 123",
);
}
#[test]
fn list_rejects_an_object_as_a_single_item() {
rejects(
"[Int]",
r#"{"length":1}"#,
"",
"Int cannot represent non-integer value: { length: 1 }",
);
}
#[test]
fn list_rejects_a_list_as_an_item() {
rejects(
"[Int]",
"[[1]]",
" at [0]",
"Int cannot represent non-integer value: [1]",
);
}
#[test]
fn non_list_type_rejects_a_list() {
rejects(
"Int",
"[1]",
"",
"Int cannot represent non-integer value: [1]",
);
}
}
mod nested_lists {
use super::*;
#[test]
fn nested_list_returns_no_error_for_a_valid_input() {
accepts("[[Int]]", "[[1],[2,3]]");
}
#[test]
fn nested_list_accepts_a_non_list_value() {
accepts("[[Int]]", "42");
}
#[test]
fn nested_list_returns_null_for_a_null_value() {
accepts("[[Int]]", "null");
}
#[test]
fn nested_list_accepts_nested_non_list_values() {
accepts("[[Int]]", "[1,2,3]");
}
#[test]
fn nested_list_accepts_nested_null_values() {
accepts("[[Int]]", "[42,[null],null]");
}
#[test]
fn nested_list_keeps_a_null_item_null() {
accepts("[[Int]]", "[1,null,3]");
}
#[test]
fn nested_list_rejects_an_incorrect_item_value() {
rejects(
"[[Int]]",
r#"[[1],["b"]]"#,
" at [1][0]",
r#"Int cannot represent non-integer value: "b""#,
);
}
#[test]
fn nested_list_wrapped_item_error_has_no_inner_index() {
rejects(
"[[Int]]",
r#"[1,"b"]"#,
" at [1]",
r#"Int cannot represent non-integer value: "b""#,
);
}
#[test]
fn nested_list_rejects_a_non_list_invalid_value() {
rejects(
"[[Int]]",
r#""b""#,
"",
r#"Int cannot represent non-integer value: "b""#,
);
}
#[test]
fn nested_list_accepts_an_empty_inner_list() {
accepts("[[Int]]", "[[]]");
}
}
mod nested_lists_with_non_null {
use super::*;
#[test]
fn nested_list_of_non_null_rejects_a_null_item() {
rejects(
"[[Int!]]",
"[[1],[null]]",
" at [1][0]",
r#"Expected value of non-null type "Int!" not to be null."#,
);
}
#[test]
fn nested_list_of_non_null_allows_a_null_inner_list() {
accepts("[[Int!]]", "[[1],null]");
}
#[test]
fn nested_list_of_non_null_wraps_items_and_allows_null() {
accepts("[[Int!]]", "[1,null]");
}
#[test]
fn list_of_non_null_lists_rejects_a_null_inner_list() {
rejects(
"[[Int]!]",
"[[1],null]",
" at [1]",
r#"Expected value of non-null type "[Int]!" not to be null."#,
);
}
#[test]
fn list_of_non_null_lists_allows_null_items() {
accepts("[[Int]!]", "[[null]]");
}
#[test]
fn fully_non_null_nested_list_accepts_an_empty_inner_list() {
accepts("[[Int!]!]!", "[[]]");
}
#[test]
fn fully_non_null_nested_list_accepts_a_scalar() {
accepts("[[Int!]!]!", "1");
}
#[test]
fn fully_non_null_nested_list_rejects_a_null_inner_list() {
rejects(
"[[Int!]!]!",
"[1,[2],null]",
" at [2]",
r#"Expected value of non-null type "[Int!]!" not to be null."#,
);
}
#[test]
fn fully_non_null_nested_list_rejects_null() {
rejects(
"[[Int!]!]!",
"null",
"",
r#"Expected value of non-null type "[[Int!]!]!" not to be null."#,
);
}
}
mod defaults {
use super::*;
#[test]
fn list_default_single_value_is_inserted_as_is() {
assert_coerced(&operation("[Int] = 1"), "{}", r#"{"value":1}"#);
}
#[test]
fn non_null_list_default_with_null_item_is_rejected_by_validation() {
assert_rejected_by_validation(
"query ($value: [Int!]! = [1, null]) { fieldWithNullableStringInput }",
"ValuesOfCorrectType",
);
}
}
mod large_inputs {
use super::*;
fn ints(count: usize) -> Vec<String> {
(0..count).map(|n| n.to_string()).collect()
}
#[test]
fn large_list_of_valid_items_is_forwarded_unchanged() {
let value = format!("[{}]", ints(100_000).join(","));
assert_forwarded(&operation("[Int]"), &variables(&value), &variables(&value));
}
#[test]
fn large_list_of_nulls_yields_one_error() {
let value = format!("[{}]", vec!["null"; 100_000].join(","));
rejects(
"[Int!]",
&value,
" at [0]",
r#"Expected value of non-null type "Int!" not to be null."#,
);
}
#[test]
fn large_list_reports_the_last_index() {
let mut items = ints(99_999);
items.push(r#""x""#.to_string());
rejects(
"[Int]",
&format!("[{}]", items.join(",")),
" at [99999]",
r#"Int cannot represent non-integer value: "x""#,
);
}
#[test]
fn invalid_item_message_is_bounded() {
let inner: Vec<String> = (1..=100_000).map(|n| n.to_string()).collect();
rejects(
"[Int]",
&format!("[[{}]]", inner.join(",")),
" at [0]",
"Int cannot represent non-integer value: \
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ... 99990 more items]",
);
}
}