8000 Add new feature: `nullable-result` · async-graphql/async-graphql@3373f1f · GitHub
[go: up one dir, main page]

Skip to content

Commit 3373f1f

Browse files
committed
Add new feature: nullable-result
1 parent 626d5c4 commit 3373f1f

12 files changed

+144
-47
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ altair = ["handlebars", "schemars"]
3333
playground = []
3434
raw_value = ["async-graphql-value/raw_value"]
3535
uuid-validator = ["uuid"]
36+
nullable-result = []
3637

3738
[[bench]]
3839
harness = false

derive/src/complex_object.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ pub fn generate(
140140
.into())
141141
}
142142
};
143-
let ty = ty.value_type();
143+
let ty = ty.value_type(object_args.internal);
144144
let ident = &method.sig.ident;
145145

146146
schema_fields.push(quote! {
@@ -323,7 +323,7 @@ pub fn generate(
323323
.into())
324324
}
325325
};
326-
let schema_ty = ty.value_type();
326+
let schema_ty = ty.value_type(object_args.internal);
327327
let visible = visible_fn(&method_args.visible);
328328

329329
let complexity = if let Some(complexity) = &method_args.complexity {

derive/src/interface.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
287287
OutputType::Value(ty) => ty,
288288
OutputType::Result(ty) => ty,
289289
};
290-
let schema_ty = oty.value_type();
290+
let schema_ty = oty.value_type(interface_args.internal);
291291

292292
methods.push(quote! {
293293
#[inline]

derive/src/object.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub fn generate(
180180
}
181181
};
182182

183-
let entity_type = ty.value_type();
183+
let entity_type = ty.value_type(object_args.internal);
184184
let mut key_pat = Vec::new();
185185
let mut key_getter = Vec::new();
186186
let mut use_keys = Vec::new();
@@ -300,7 +300,7 @@ pub fn generate(
300300
.into())
301301
}
302302
};
303-
let ty = ty.value_type();
303+
let ty = ty.value_type(object_args.internal);
304304
let ident = &method.sig.ident;
305305

306306
schema_fields.push(quote! {
@@ -467,7 +467,7 @@ pub fn generate(
467467
.into())
468468
}
469469
};
470-
let schema_ty = ty.value_type();
470+
let schema_ty = ty.value_type(object_args.internal);
471471
let visible = visible_fn(&method_args.visible);
472472

473473
let complexity = if let Some(complexity) = &method_args.complexity {

derive/src/output_type.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use proc_macro2::{Ident, Span};
22
use quote::quote;
33
use syn::{Error, GenericArgument, PathArguments, Result, Type};
44

5+
use crate::utils::get_crate_name;
6+
57
pub enum OutputType<'a> {
68
Value(&'a Type),
79
Result(&'a Type),
@@ -42,10 +44,11 @@ impl<'a> OutputType<'a> {
4244
Ok(ty)
4345
}
4446

45-
pub fn value_type(&self) -> Type {
47+
pub fn value_type(&self, internal: bool) -> Type {
48+
let crate_name = get_crate_name(internal);
4649
let tokens = match self {
4750
OutputType::Value(ty) => quote! {#ty},
48-
OutputType::Result(ty) => quote! {#ty},
51+
OutputType::Result(ty) => quote! {#crate_name::Result<#ty>},
4952
};
5053
let mut ty = syn::parse2::<syn::Type>(tokens).unwrap();
5154
Self::remove_lifecycle(&mut ty);

derive/src/subscription.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,10 @@ pub fn generate(
177177
.into())
178178
}
179179
};
180-
let res_ty = ty.value_type();
180+
let res_ty = match ty {
181+
OutputType::Value(ty) => ty,
182+
OutputType::Result(ty) => ty,
183+
};
181184
let stream_ty = if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = &res_ty {
182185
let mut r = None;
183186
for b in bounds {
@@ -189,6 +192,14 @@ pub fn generate(
189192
} else {
190193
quote! { #res_ty }
191194
};
195+
let output_ty = match ty {
196+
OutputType::Value(_) => {
197+
quote! { <#stream_ty as #crate_name::futures_util::stream::Stream>::Item }
198+
}
199+
OutputType::Result(_) => {
200+
quote! { #crate_name::Result<<#stream_ty as #crate_name::futures_util::stream::Stream>::Item> }
201+
}
202+
};
192203

193204
if let OutputType::Value(inner_ty) = &ty {
194205
let block = &method.block;
@@ -261,7 +272,7 @@ pub fn generate(
261272
#(#schema_args)*
262273
args
263274
},
264-
ty: <<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::OutputType>::create_type_info(registry),
275+
ty: <#output_ty as #crate_name::OutputType>::create_type_info(registry),
265276
deprecation: #field_deprecation,
266277
cache_control: ::std::default::Default::default(),
267278
external: false,
@@ -337,7 +348,7 @@ pub fn generate(
337348
let ri = #crate_name::extensions::ResolveInfo {
338349
path_node: ctx_selection_set.path_node.as_ref().unwrap(),
339350
parent_type: &parent_type,
340-
return_type: &<<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::OutputType>::qualified_type_name(),
351+
return_type: &<#output_ty as #crate_name::OutputType>::qualified_type_name(),
341352
name: field.node.name.node.as_str(),
342353
alias: field.node.alias.as_ref().map(|alias| alias.node.as_str()),
343354
is_for_introspection: false,

src/base.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,18 @@ impl<T: OutputType + Sync, E: Into<Error> + Send + Sync + Clone> OutputType for
116116
T::type_name()
117117
}
118118

119+
#[cfg(feature = "nullable-result")]
120+
fn qualified_type_name() -> String {
121+
T::type_name().to_string()
122+
}
123+
119124
fn create_type_info(registry: &mut Registry) -> String {
120-
T::create_type_info(registry)
125+
let ty = T::create_type_info(registry);
126+
1241 if cfg!(feature = "nullable-result") {
127+
T::type_name().to_string()
128+
} else {
129+
ty
130+
}
121131
}
122132

123133
async fn resolve(
@@ -127,14 +137,24 @@ impl<T: OutputType + Sync, E: Into<Error> + Send + Sync + Clone> OutputType for
127137
) -> ServerResult<Value> {
128138
match self {
129139
Ok(value) => value.resolve(ctx, field).await,
130-
Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(field.pos))),
140+
Err(err) => {
141+
let err = ctx.set_error_path(err.clone().into().into_server_error(field.pos));
142+
if cfg!(feature = "nullable-result") {
143+
ctx.add_error(err);
144+
Ok(Value::Null)
145+
} else {
146+
Err(err)
147+
}
148+
}
131149
}
132150
}
133151
}
134152

135153
/// A GraphQL object.
136154
pub trait ObjectType: ContainerType {}
137155

156+
impl<T: ObjectType> ObjectType for Result<T> {}
157+
138158
impl<T: ObjectType + ?Sized> ObjectType for &T {}
139159

140160
impl<T: ObjectType + ?Sized> ObjectType for Box<T> {}

src/resolver_utils/container.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,17 @@ impl<'a> Fields<'a> {
211211
if extensions.is_empty() && field.node.directives.is_empty() {
212212
Ok((
213213
field_name,
214-
root.resolve_field(&ctx_field).await?.unwrap_or_default(),
214+
match root.resolve_field(&ctx_field).await {
215+
Ok(value) => value.unwrap_or_default(),
216+
Err(err) => {
217+
if cfg!(feature = "nullable-result") {
218+
ctx_field.add_error(err);
219+
Value::Null
220+
} else {
221+
return Err(err);
222+
}
223+
}
224+
},
215225
))
216226
} else {
217227
let type_name = T::type_name();
@@ -255,10 +265,20 @@ impl<'a> Fields<'a> {
255265
futures_util::pin_mut!(resolve_fut);
256266
Ok((
257267
field_name,
258-
extensions
268+
match extensions
259269
.resolve(resolve_info, &mut resolve_fut)
260-
.await?
261-
.unwrap_or_default(),
270+
.await
271+
{
272+
Ok(value) => value.unwrap_or_default(),
273+
Err(err) => {
274+
if cfg!(feature = "nullable-result") {
275+
ctx_field.add_error(err);
276+
Value::Null
277+
} else {
278+
return Err(err);
279+
}
280+
}
281+
},
262282
))
263283
} else {
264284
let mut resolve_fut = resolve_fut.boxed();
@@ -292,10 +312,20 @@ impl<'a> Fields<'a> {
292312

293313
Ok((
294314
field_name,
295-
extensions
315+
match extensions
296316
.resolve(resolve_info, &mut resolve_fut)
297-
.await?
298-
.unwrap_or_default(),
317+
.await
318+
{
319+
Ok(value) => value.unwrap_or_default(),
320+
Err(err) => {
321+
if cfg!(feature = "nullable-result") {
322+
ctx_field.add_error(err);
323+
Value::Null
324+
} else {
325+
return Err(err);
326+
}
327+
}
328+
},
299329
))
300330
}
301331
}

tests/error_ext.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ pub async fn test_error_extensions() {
3838
assert_eq!(
3939
serde_json::to_value(&schema.execute("{ extendErr }").await).unwrap(),
4040
serde_json::json!({
41-
"data": null,
41+
"data": (if cfg!(feature = "nullable-result"){
42+
serde_json::json!({ "extendErr": null })
43+
} else {
44+
serde_json::Value::Null
45+
}),
4246
"errors": [{
4347
"message": "my error",
4448
"locations": [{
@@ -58,7 +62,11 @@ pub async fn test_error_extensions() {
5862
assert_eq!(
5963
serde_json::to_value(&schema.execute("{ extendResult }").await).unwrap(),
6064
serde_json::json!({
61-
"data": null,
65+
"data": (if cfg!(feature = "nullable-result"){
66+
serde_json::json!({ "extendResult": null })
67+
} else {
68+
serde_json::Value::Null
69+
}),
6270
"errors": [{
6371
"message": "my error",
6472
"locations": [{

tests/result.rs

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,10 @@ pub async fn test_fieldresult() {
5353
]
5454
);
5555

56+
let resp = schema.execute("{ optError }").await;
57+
assert_eq!(resp.data, value!({ "optError": null }));
5658
assert_eq!(
57-
schema
58-
.execute("{ optError }")
59-
.await
60-
.into_result()
61-
.unwrap_err(),
59+
resp.errors,
6260
vec![ServerError {
6361
message: "TestError".to_string(),
6462
source: None,
@@ -68,12 +66,17 @@ pub async fn test_fieldresult() {
6866
}]
6967
);
7068

69+
let resp = schema.execute("{ vecError }").await;
70+
assert_eq!(
71+
resp.data,
72+
if cfg!(feature = "nullable-result") {
73+
value!({ "vecError": [1, null] })
74+
} else {
75+
Value::Null
76+
}
77+
);
7178
assert_eq!(
72-
schema
73-
.execute("{ vecError }")
74-
.await
75-
.into_result()
76-
.unwrap_err(),
79+
resp.errors,
7780
vec![ServerError {
7881
message: "TestError".to_string(),
7982
source: None,
@@ -184,6 +187,14 @@ pub async fn test_error_propagation() {
184187
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
185188
let resp = schema.execute("{ parent { child { name } } }").await;
186189

190+
assert_eq!(
191+
resp.data,
192+
if cfg!(feature = "nullable-result") {
193+
value!({ "parent": { "child": { "name": null } } })
194+
} else {
195+
Value::Null
196+
}
197+
);
187198
assert_eq!(
188199
resp.errors,
189200
vec![ServerError {
@@ -205,11 +216,11 @@ pub async fn test_error_propagation() {
205216
let resp = schema.execute("{ parent { childOpt { name } } }").await;
206217
assert_eq!(
207218
resp.data,
208-
value!({
209-
"parent": {
210-
"childOpt": null,
211-
}
212-
})
219+
if cfg!(feature = "nullable-result") {
220+
value!({ "parent": { "childOpt": { "name": null } } })
221+
} else {
222+
value!({ "parent": { "childOpt": null } })
223+
}
213224
);
214225
assert_eq!(
215226
resp.errors,
@@ -230,7 +241,14 @@ pub async fn test_error_propagation() {
230241
);
231242

232243
let resp = schema.execute("{ parentOpt { child { name } } }").await;
233-
assert_eq!(resp.data, value!({ "parentOpt": null }));
244+
assert_eq!(
245+
resp.data,
246+
if cfg!(feature = "nullable-result") {
247+
value!({ "parentOpt": { "child": { "name": null } } })
248+
} else {
249+
value!({ "parentOpt": null })
250+
}
251+
);
234252
assert_eq!(
235253
resp.errors,
236254
vec![ServerError {
@@ -252,13 +270,7 @@ pub async fn test_error_propagation() {
252270
let resp = schema.execute("{ parentOpt { child { nameOpt } } }").await;
253271
assert_eq!(
254272
resp.data,
255-
value!({
256-
"parentOpt": {
257-
"child": {
258-
"nameOpt": null,
259-
}
260-
},
261-
})
273+
value!({ "parentOpt": { "child": { "nameOpt": null } } })
262274
);
263275
assert_eq!(
264276
resp.errors,

0 commit comments

Comments
 (0)
0