Skip to content

Commit 5e020ae

Browse files
karatakistusharmathmeskill
authored
fix(jit): input arguments required error (#2756)
Co-authored-by: Tushar Mathur <tusharmath@gmail.com> Co-authored-by: Kiryl Mialeshka <8974488+meskill@users.noreply.github.com>
1 parent a6310d2 commit 5e020ae

30 files changed

+422
-95
lines changed

src/core/blueprint/index.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use indexmap::IndexMap;
22

3+
use super::InputObjectTypeDefinition;
34
use crate::core::blueprint::{
45
Blueprint, Definition, FieldDefinition, InputFieldDefinition, SchemaDefinition,
56
};
@@ -78,6 +79,13 @@ impl Index {
7879
false
7980
}
8081
}
82+
83+
pub fn get_input_type_definition(&self, type_name: &str) -> Option<&InputObjectTypeDefinition> {
84+
match self.map.get(type_name) {
85+
Some((Definition::InputObject(input), _)) => Some(input),
86+
_ => None,
87+
}
88+
}
8189
}
8290

8391
impl From<&Blueprint> for Index {

src/core/jit/context.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,9 @@ impl<'a, Input: Clone, Output> Context<'a, Input, Output> {
8080

8181
for arg in field.args.iter() {
8282
let name = arg.name.as_str();
83-
let value = arg
84-
.value
85-
.clone()
86-
// TODO: default value resolution should happen in the InputResolver
87-
.or_else(|| arg.default_value.clone());
83+
let value = arg.value.clone();
8884
if let Some(value) = value {
8985
arg_map.insert(Name::new(name), value);
90-
} else if !arg.type_of.is_nullable() {
91-
// TODO: throw error here
92-
todo!()
9386
}
9487
}
9588
Some(arg_map)

src/core/jit/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ pub enum BuildError {
2020
pub enum ResolveInputError {
2121
#[error("Variable `{0}` is not defined")]
2222
VariableIsNotFound(String),
23+
#[error("Argument `{arg_name}` for field `{field_name}` is required")]
24+
ArgumentIsRequired {
25+
arg_name: String,
26+
field_name: String,
27+
},
2328
}
2429

2530
#[derive(Error, Debug, Clone)]

src/core/jit/input_resolver.rs

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use async_graphql_value::{ConstValue, Value};
22

3-
use super::{OperationPlan, ResolveInputError, Variables};
3+
use super::{Arg, Field, OperationPlan, ResolveInputError, Variables};
4+
use crate::core::json::{JsonLikeOwned, JsonObjectLike};
5+
use crate::core::Type;
46

57
/// Trait to represent conversion from some dynamic type (with variables)
68
/// to the resolved variant based on the additional provided info.
@@ -18,8 +20,6 @@ pub trait InputResolvable {
1820
impl InputResolvable for Value {
1921
type Output = ConstValue;
2022

21-
// TODO:
22-
// - provide default values
2323
fn resolve(self, variables: &Variables<ConstValue>) -> Result<Self::Output, ResolveInputError> {
2424
self.into_const_with(|name| {
2525
variables
@@ -45,8 +45,9 @@ impl<Input> InputResolver<Input> {
4545
impl<Input, Output> InputResolver<Input>
4646
where
4747
Input: Clone,
48-
Output: Clone,
48+
Output: Clone + JsonLikeOwned + TryFrom<serde_json::Value>,
4949
Input: InputResolvable<Output = Output>,
50+
<Output as TryFrom<serde_json::Value>>::Error: std::fmt::Debug,
5051
{
5152
pub fn resolve_input(
5253
&self,
@@ -57,7 +58,28 @@ where
5758
.as_parent()
5859
.iter()
5960
.map(|field| field.clone().try_map(|value| value.resolve(variables)))
60-
.collect::<Result<_, _>>()?;
61+
.map(|field| match field {
62+
Ok(field) => {
63+
let args = field
64+
.args
65+
.into_iter()
66+
.map(|arg| {
67+
let value = self.recursive_parse_arg(
68+
&field.name,
69+
&arg.name,
70+
&arg.type_of,
71+
&arg.default_value,
72+
arg.value,
73+
)?;
74+
Ok(Arg { value, ..arg })
75+
})
76+
.collect::<Result<_, _>>()?;
77+
78+
Ok(Field { args, ..field })
79+
}
80+
Err(err) => Err(err),
81+
})
82+
.collect::<Result<Vec<_>, _>>()?;
6183

6284
Ok(OperationPlan::new(
6385
new_fields,
@@ -66,4 +88,78 @@ where
6688
self.plan.is_introspection_query,
6789
))
6890
}
91+
92+
#[allow(clippy::too_many_arguments)]
93+
fn recursive_parse_arg(
94+
&self,
95+
parent_name: &str,
96+
arg_name: &str,
97+
type_of: &Type,
98+
default_value: &Option<Output>,
99+
value: Option<Output>,
100+
) -> Result<Option<Output>, ResolveInputError> {
101+
let is_value_null = value.as_ref().map(|val| val.is_null()).unwrap_or(true);
102+
let value = if !type_of.is_nullable() && value.is_none() {
103+
let default_value = default_value.clone();
104+
105+
Some(default_value.ok_or(ResolveInputError::ArgumentIsRequired {
106+
arg_name: arg_name.to_string(),
107+
field_name: parent_name.to_string(),
108+
})?)
109+
} else if !type_of.is_nullable() && is_value_null {
110+
return Err(ResolveInputError::ArgumentIsRequired {
111+
arg_name: arg_name.to_string(),
112+
field_name: parent_name.to_string(),
113+
});
114+
} else if value.is_none() {
115+
default_value.clone()
116+
} else {
117+
value
118+
};
119+
120+
let Some(mut value) = value else {
121+
return Ok(None);
122+
};
123+
124+
let Some(def) = self.plan.index.get_input_type_definition(type_of.name()) else {
125+
return Ok(Some(value));
126+
};
127+
128+
if let Some(obj) = value.as_object_mut() {
129+
for arg_field in &def.fields {
130+
let parent_name = format!("{}.{}", parent_name, arg_name);
131+
let field_value = obj.get_key(&arg_field.name).cloned();
132+
let field_default = arg_field
133+
.default_value
134+
.clone()
135+
.map(|value| Output::try_from(value).expect("The conversion cannot fail"));
136+
let value = self.recursive_parse_arg(
137+
&parent_name,
138+
&arg_field.name,
139+
&arg_field.of_type,
140+
&field_default,
141+
field_value,
142+
)?;
143+
if let Some(value) = value {
144+
obj.insert_key(&arg_field.name, value);
145+
}
146+
}
147+
} else if let Some(arr) = value.as_array_mut() {
148+
for (index, item) in arr.iter_mut().enumerate() {
149+
let parent_name = format!("{}.{}.{}", parent_name, arg_name, index);
150+
151+
*item = self
152+
.recursive_parse_arg(
153+
&parent_name,
154+
&index.to_string(),
155+
type_of,
156+
&None,
157+
Some(item.clone()),
158+
)?
159+
.expect("Because we start with `Some`, we will end with `Some`");
160+
}
161+
}
162+
163+
Ok(Some(value))
164+
}
69165
}

src/core/jit/snapshots/tailcall__core__jit__builder__tests__default_value.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ expression: plan.into_nested()
3535
): String(
3636
"tailcall test",
3737
),
38+
Name(
39+
"id",
40+
): Number(
41+
Number(101),
42+
),
3843
},
3944
),
4045
),

src/core/jit/snapshots/tailcall__core__jit__builder__tests__resolving_operation-2.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ expression: plan.into_nested()
3535
): String(
3636
"test-12",
3737
),
38+
Name(
39+
"id",
40+
): Number(
41+
Number(101),
42+
),
3843
},
3944
),
4045
),

src/core/json/borrow.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ impl<'ctx> JsonLike<'ctx> for Value<'ctx> {
4747
}
4848
}
4949

50+
fn as_array_mut(&mut self) -> Option<&mut Vec<Self>> {
51+
match self {
52+
Value::Array(arr) => Some(arr),
53+
_ => None,
54+
}
55+
}
56+
5057
fn into_array(self) -> Option<Vec<Self>> {
5158
match self {
5259
Value::Array(array) => Some(array),

src/core/json/graphql.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ impl<'json> JsonLike<'json> for ConstValue {
3333
}
3434
}
3535

36+
fn as_array_mut(&mut self) -> Option<&mut Vec<Self>> {
37+
match self {
38+
ConstValue::List(seq) => Some(seq),
39+
_ => None,
40+
}
41+
}
42+
3643
fn into_array(self) -> Option<Vec<Self>> {
3744
match self {
3845
ConstValue::List(seq) => Some(seq),

src/core/json/json_like.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub trait JsonLike<'json>: Sized {
1616

1717
// Operators
1818
fn as_array(&self) -> Option<&Vec<Self>>;
19+
fn as_array_mut(&mut self) -> Option<&mut Vec<Self>>;
1920
fn into_array(self) -> Option<Vec<Self>>;
2021
fn as_object(&self) -> Option<&Self::JsonObject>;
2122
fn as_object_mut(&mut self) -> Option<&mut Self::JsonObject>;

src/core/json/serde.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ impl<'json> JsonLike<'json> for serde_json::Value {
2626
self.as_array()
2727
}
2828

29+
fn as_array_mut(&mut self) -> Option<&mut Vec<Self>> {
30+
self.as_array_mut()
31+
}
32+
2933
fn into_array(self) -> Option<Vec<Self>> {
3034
if let Self::Array(vec) = self {
3135
Some(vec)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
source: tests/core/spec.rs
3+
expression: response
4+
---
5+
{
6+
"status": 200,
7+
"headers": {
8+
"content-type": "application/json"
9+
},
10+
"body": {
11+
"data": null,
12+
"errors": [
13+
{
14+
"message": "Build error: ResolveInputError: Argument `id` for field `user` is required"
15+
}
16+
]
17+
}
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
source: tests/core/spec.rs
3+
expression: response
4+
---
5+
{
6+
"status": 200,
7+
"headers": {
8+
"content-type": "application/json"
9+
},
10+
"body": {
11+
"data": null,
12+
"errors": [
13+
{
14+
"message": "Build error: ResolveInputError: Argument `size` for field `profilePic` is required"
15+
}
16+
]
17+
}
18+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
source: tests/core/spec.rs
3+
expression: response
4+
---
5+
{
6+
"status": 200,
7+
"headers": {
8+
"content-type": "application/json"
9+
},
10+
"body": {
11+
"data": {
12+
"user": {
13+
"id": 4,
14+
"name": "User 4",
15+
"spam": "FIZZ: [{\"bar\":\"BUZZ\"},{\"bar\":\"test\"}]"
16+
}
17+
}
18+
}
19+
}

tests/core/snapshots/graphql-conformance-015.md_5.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ expression: response
1212
"user": {
1313
"id": 4,
1414
"name": "User 4",
15-
"featuredVideo": "video_4_1600_900_"
15+
"featuredVideo": "video_4_1600_900_true"
1616
}
1717
}
1818
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
source: tests/core/spec.rs
3+
expression: response
4+
---
5+
{
6+
"status": 200,
7+
"headers": {
8+
"content-type": "application/json"
9+
},
10+
"body": {
11+
"data": null,
12+
"errors": [
13+
{
14+
"message": "Build error: ResolveInputError: Argument `height` for field `featuredVideoPreview.video` is required"
15+
}
16+
]
17+
}
18+
}

tests/core/snapshots/graphql-conformance-015.md_client.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ scalar Email
1212

1313
scalar Empty
1414

15+
input Foo {
16+
bar: String! = "BUZZ"
17+
}
18+
1519
scalar Int128
1620

1721
scalar Int16
@@ -49,6 +53,7 @@ type User {
4953
name: String!
5054
profilePic(size: Int! = 100, width: Int, height: Int = 100): String!
5155
searchComments(query: [[String!]!]! = [["today"]]): String!
56+
spam(foo: [Foo!]!): String!
5257
}
5358

5459
input VideoSize {

tests/core/snapshots/graphql-conformance-015.md_merged.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ schema
88
query: Query
99
}
1010

11+
input Foo {
12+
bar: String! = "BUZZ"
13+
}
14+
1115
input VideoSize {
1216
hdr: Boolean = true
1317
height: Int!
@@ -28,4 +32,5 @@ type User {
2832
profilePic(size: Int! = 100, width: Int, height: Int = 100): String!
2933
@expr(body: "{{.value.id}}_{{.args.size}}_{{.args.width}}_{{.args.height}}")
3034
searchComments(query: [[String!]!]! = [["today"]]): String! @expr(body: "video_{{.value.id}}_{{.args.query}}")
35+
spam(foo: [Foo!]!): String! @expr(body: "FIZZ: {{.args.foo}}")
3136
}

0 commit comments

Comments
 (0)