Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.

Commit e9b62ee

Browse files
Merge pull request #104 from 1Password/blitz/push-muqkuztzrytk
Add semantic nullability capability to async-graphql
2 parents b90ba46 + 8095145 commit e9b62ee

31 files changed

+421
-70
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ graphiql = ["dep:askama"]
3838
raw_value = ["async-graphql-value/raw_value"]
3939
boxed-trait = ["async-graphql-derive/boxed-trait"]
4040
custom-error-conversion = []
41+
nullable-result = []
4142

4243
[[bench]]
4344
harness = false

derive/src/args.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ pub struct SimpleObjectField {
237237
pub complexity: Option<Expr>,
238238
#[darling(default, multiple)]
239239
pub requires_scopes: Vec<String>,
240+
#[darling(default)]
241+
pub semantic_non_null: Option<bool>,
240242
}
241243

242244
#[derive(FromDeriveInput)]
@@ -292,6 +294,8 @@ pub struct SimpleObject {
292294
pub requires_scopes: Vec<String>,
293295
#[darling(rename = "crate")]
294296
pub crate_path: Option<Path>,
297+
#[darling(default)]
298+
pub semantic_non_null: bool,
295299
}
296300

297301
#[derive(FromMeta, Default)]
@@ -345,6 +349,8 @@ pub struct Object {
345349
pub requires_scopes: Vec<String>,
346350
#[darling(rename = "crate")]
347351
pub crate_path: Option<Path>,
352+
#[darling(default)]
353+
pub semantic_non_null: bool,
348354
}
349355

350356
#[derive(FromMeta, Default)]
@@ -373,6 +379,7 @@ pub struct ObjectField {
373379
pub directives: Vec<Expr>,
374380
#[darling(default, multiple)]
375381
pub requires_scopes: Vec<String>,
382+
pub semantic_non_null: Option<bool>,
376383
}
377384

378385
#[derive(FromMeta, Default, Clone)]
@@ -681,6 +688,8 @@ pub struct InterfaceField {
681688
pub directives: Vec<Expr>,
682689
#[darling(default, multiple)]
683690
pub requires_scopes: Vec<String>,
691+
#[darling(default)]
692+
pub semantic_non_null: Option<bool>,
684693
}
685694

686695
#[derive(FromVariant)]
@@ -719,6 +728,8 @@ pub struct Interface {
719728
pub tags: Vec<String>,
720729
#[darling(default, multiple, rename = "directive")]
721730
pub directives: Vec<Expr>,
731+
#[darling(default)]
732+
pub semantic_non_null: bool,
722733
// for OneofObject
723734
#[darling(default)]
724735
pub input_name: Option<String>,
@@ -765,6 +776,7 @@ pub struct Subscription {
765776
pub directives: Vec<Expr>,
766777
#[darling(rename = "crate")]
767778
pub crate_path: Option<Path>,
779+
pub semantic_non_null: bool,
768780
}
769781

770782
#[derive(FromMeta, Default)]
@@ -793,6 +805,7 @@ pub struct SubscriptionField {
793805
pub complexity: Option<Expr>,
794806
#[darling(default, multiple, rename = "directive")]
795807
pub directives: Vec<Expr>,
808+
pub semantic_non_null: Option<bool>,
796809
}
797810

798811
#[derive(FromField)]
@@ -997,6 +1010,7 @@ pub struct ComplexObject {
9971010
pub guard: Option<Expr>,
9981011
#[darling(rename = "crate")]
9991012
pub crate_path: Option<Path>,
1013+
pub semantic_non_null: bool,
10001014
}
10011015

10021016
#[derive(FromMeta, Default)]
@@ -1024,6 +1038,7 @@ pub struct ComplexObjectField {
10241038
pub directives: Vec<Expr>,
10251039
#[darling(default, multiple)]
10261040
pub requires_scopes: Vec<String>,
1041+
pub semantic_non_null: Option<bool>,
10271042
}
10281043

10291044
#[derive(FromMeta, Default)]

derive/src/complex_object.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub fn generate(
130130
.into());
131131
}
132132
};
133-
let ty = ty.value_type();
133+
let ty = ty.value_type(object_args.internal);
134134
let ident = &method.sig.ident;
135135

136136
schema_fields.push(quote! {
@@ -330,7 +330,7 @@ pub fn generate(
330330
.into());
331331
}
332332
};
333-
let schema_ty = ty.value_type();
333+
let schema_ty = ty.value_type(object_args.internal);
334334
let visible = visible_fn(&method_args.visible);
335335

336336
let complexity = if let Some(complexity) = &method_args.complexity {
@@ -375,6 +375,14 @@ pub fn generate(
375375
} else {
376376
quote! { ::std::option::Option::None }
377377
};
378+
let semantic_nullability = if method_args
379+
.semantic_non_null
380+
.unwrap_or(object_args.semantic_non_null)
381+
{
382+
quote! { <#schema_ty as #crate_name::OutputType>::semantic_nullability() }
383+
} else {
384+
quote! { #crate_name::registry::SemanticNullability::None }
385+
};
378386

379387
schema_fields.push(quote! {
380388
#(#cfg_attrs)*
@@ -400,6 +408,7 @@ pub fn generate(
400408
compute_complexity: #complexity,
401409
directive_invocations: ::std::vec![ #(#directives),* ],
402410
requires_scopes: ::std::vec![ #(#requires_scopes),* ],
411+
semantic_nullability: #semantic_nullability,
403412
}));
404413
});
405414

derive/src/interface.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
170170
override_from,
171171
directives,
172172
requires_scopes,
173+
semantic_non_null,
173174
} in &interface_args.fields
174175
{
175176
let (name, method_name) = if let Some(method) = method {
@@ -304,7 +305,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
304305
OutputType::Value(ty) => ty,
305306
OutputType::Result(ty) => ty,
306307
};
307-
let schema_ty = oty.value_type();
308+
let schema_ty = oty.value_type(interface_args.internal);
308309

309310
methods.push(quote! {
310311
#[allow(missing_docs)]
@@ -331,6 +332,12 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
331332
directives,
332333
TypeDirectiveLocation::FieldDefinition,
333334
);
335+
let semantic_nullability = if semantic_non_null.unwrap_or(interface_args.semantic_non_null)
336+
{
337+
quote! { <#schema_ty as #crate_name::OutputType>::semantic_nullability() }
338+
} else {
339+
quote! { #crate_name::registry::SemanticNullability::None }
340+
};
334341

335342
schema_fields.push(quote! {
336343
fields.insert(::std::string::ToString::to_string(#name), #crate_name::registry::MetaField {
@@ -355,6 +362,7 @@ pub fn generate(interface_args: &args::Interface) -> GeneratorResult<TokenStream
355362
compute_complexity: ::std::option::Option::None,
356363
directive_invocations: ::std::vec![ #(#directives),* ],
357364
requires_scopes: ::std::vec![ #(#requires_scopes),* ],
365+
semantic_nullability: #semantic_nullability,
358366
});
359367
});
360368

derive/src/object.rs

Lines changed: 12 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! {
@@ -479,7 +479,7 @@ pub fn generate(
479479
.into());
480480
}
481481
};
482-
let schema_ty = ty.value_type();
482+
let schema_ty = ty.value_type(object_args.internal);
483483
let visible = visible_fn(&method_args.visible);
484484

485485
let complexity = if let Some(complexity) = &method_args.complexity {
@@ -524,6 +524,14 @@ pub fn generate(
524524
} else {
525525
quote! { ::std::option::Option::None }
526526
};
527+
let semantic_nullability = if method_args
528+
.semantic_non_null
529+
.unwrap_or(object_args.semantic_non_null)
530+
{
531+
quote! { <#schema_ty as #crate_name::OutputType>::semantic_nullability() }
532+
} else {
533+
quote! { #crate_name::registry::SemanticNullability::None }
534+
};
527535

528536
schema_fields.push(quote! {
529537
#(#cfg_attrs)*
@@ -549,6 +557,7 @@ pub fn generate(
549557
compute_complexity: #complexity,
550558
directive_invocations: ::std::vec![ #(#directives),* ],
551559
requires_scopes: ::std::vec![ #(#requires_scopes),* ],
560+
semantic_nullability: #semantic_nullability,
552561
});
553562
});
554563

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_path;
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_path(&None, 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/simple_object.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,14 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
227227
} else {
228228
quote! { ::std::option::Option::None }
229229
};
230+
let semantic_nullability = if field
231+
.semantic_non_null
232+
.unwrap_or(object_args.semantic_non_null)
233+
{
234+
quote! { <#ty as #crate_name::OutputType>::semantic_nullability() }
235+
} else {
236+
quote! { #crate_name::registry::SemanticNullability::None }
237+
};
230238

231239
if !field.flatten {
232240
schema_fields.push(quote! {
@@ -248,6 +256,7 @@ pub fn generate(object_args: &args::SimpleObject) -> GeneratorResult<TokenStream
248256
compute_complexity: #complexity,
249257
directive_invocations: ::std::vec![ #(#directives),* ],
250258
requires_scopes: ::std::vec![ #(#requires_scopes),* ],
259+
semantic_nullability: #semantic_nullability,
251260
});
252261
});
253262
} else {

derive/src/subscription.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ pub fn generate(
184184
.into());
185185
}
186186
};
187-
let res_ty = ty.value_type();
187+
let res_ty = match ty {
188+
OutputType::Value(ty) => ty,
189+
OutputType::Result(ty) => ty,
190+
};
188191
let stream_ty = if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = &res_ty {
189192
let mut r = None;
190193
for b in bounds {
@@ -196,6 +199,14 @@ pub fn generate(
196199
} else {
197200
quote! { #res_ty }
198201
};
202+
let output_ty = match ty {
203+
OutputType::Value(_) => {
204+
quote! { <#stream_ty as #crate_name::futures_util::stream::Stream>::Item }
205+
}
206+
OutputType::Result(_) => {
207+
quote! { #crate_name::Result<<#stream_ty as #crate_name::futures_util::stream::Stream>::Item> }
208+
}
209+
};
199210

200211
if let OutputType::Value(inner_ty) = &ty {
201212
let block = &method.block;
@@ -261,6 +272,15 @@ pub fn generate(
261272
TypeDirectiveLocation::FieldDefinition,
262273
);
263274

275+
let semantic_nullability = if field
276+
.semantic_non_null
277+
.unwrap_or(subscription_args.semantic_non_null)
278+
{
279+
quote! { <#output_ty as #crate_name::OutputType>::semantic_nullability() }
280+
} else {
281+
quote! { #crate_name::registry::SemanticNullability::None }
282+
};
283+
264284
schema_fields.push(quote! {
265285
#(#cfg_attrs)*
266286
fields.insert(::std::borrow::ToOwned::to_owned(#field_name), #crate_name::registry::MetaField {
@@ -271,7 +291,7 @@ pub fn generate(
271291
#(#schema_args)*
272292
args
273293
},
274-
ty: <<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::OutputType>::create_type_info(registry),
294+
ty: <#output_ty as #crate_name::OutputType>::create_type_info(registry),
275295
deprecation: #field_deprecation,
276296
cache_control: ::std::default::Default::default(),
277297
external: false,
@@ -285,6 +305,7 @@ pub fn generate(
285305
compute_complexity: #complexity,
286306
directive_invocations: ::std::vec![ #(#directives),* ],
287307
requires_scopes: ::std::vec![],
308+
semantic_nullability: #semantic_nullability,
288309
});
289310
});
290311

@@ -348,7 +369,7 @@ pub fn generate(
348369
let ri = #crate_name::extensions::ResolveInfo {
349370
path_node: ctx_selection_set.path_node.as_ref().unwrap(),
350371
parent_type: &parent_type,
351-
return_type: &<<#stream_ty as #crate_name::futures_util::stream::Stream>::Item as #crate_name::OutputType>::qualified_type_name(),
372+
return_type: &<#output_ty as #crate_name::OutputType>::qualified_type_name(),
352373
name: field.node.name.node.as_str(),
353374
alias: field.node.alias.as_ref().map(|alias| alias.node.as_str()),
354375
is_for_introspection: false,

0 commit comments

Comments
 (0)