From edcda0acdcbb331c157e22628e7b9b44f9f4b18d Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 12 Sep 2024 12:15:41 -0700 Subject: [PATCH] Take advantage of @semanticNonNull within @catch --- compiler/crates/relay-typegen/src/visit.rs | 98 +++++++++++-------- ...nked-semantic-non-null-with-catch.expected | 25 +++++ ...inked-semantic-non-null-with-catch.graphql | 12 +++ ...alar-semantic-non-null-with-catch.expected | 21 ++++ ...calar-semantic-non-null-with-catch.graphql | 10 ++ ...n-null-deeply-nested-within-catch.expected | 33 +++++++ ...on-null-deeply-nested-within-catch.graphql | 18 ++++ .../semantic-non-null-within-catch.expected | 29 ++++++ .../semantic-non-null-within-catch.graphql | 16 +++ .../tests/generate_typescript_test.rs | 30 +++++- 10 files changed, 248 insertions(+), 44 deletions(-) create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-semantic-non-null-with-catch.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-semantic-non-null-with-catch.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-semantic-non-null-with-catch.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-semantic-non-null-with-catch.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.graphql create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-within-catch.expected create mode 100644 compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-within-catch.graphql diff --git a/compiler/crates/relay-typegen/src/visit.rs b/compiler/crates/relay-typegen/src/visit.rs index c438f3d2393ed..6abe2a6eec081 100644 --- a/compiler/crates/relay-typegen/src/visit.rs +++ b/compiler/crates/relay-typegen/src/visit.rs @@ -57,6 +57,7 @@ use relay_transforms::RequiredMetadataDirective; use relay_transforms::ResolverOutputTypeInfo; use relay_transforms::TypeConditionInfo; use relay_transforms::ASSIGNABLE_DIRECTIVE_FOR_TYPEGEN; +use relay_transforms::CATCH_DIRECTIVE_NAME; use relay_transforms::CHILDREN_CAN_BUBBLE_METADATA_KEY; use relay_transforms::CLIENT_EXTENSION_DIRECTIVE_NAME; use relay_transforms::RELAY_ACTOR_CHANGE_DIRECTIVE_FOR_CODEGEN; @@ -159,7 +160,7 @@ pub(crate) fn visit_selections( runtime_imports: &mut RuntimeImports, custom_error_import: &mut Option, enclosing_linked_field_concrete_type: Option, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) -> Vec { let mut type_selections = Vec::new(); for selection in selections { @@ -175,7 +176,7 @@ pub(crate) fn visit_selections( encountered_fragments, imported_resolvers, runtime_imports, - is_throw_on_field_error, + emit_semantic_types, ), Selection::InlineFragment(inline_fragment) => visit_inline_fragment( typegen_context, @@ -191,7 +192,7 @@ pub(crate) fn visit_selections( runtime_imports, custom_error_import, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ), Selection::LinkedField(linked_field) => { let linked_field_type = typegen_context @@ -205,6 +206,11 @@ pub(crate) fn visit_selections( } else { Some(linked_field_type) }; + let field_emit_semantic_types = emit_semantic_types + || linked_field + .directives + .named(*CATCH_DIRECTIVE_NAME) + .is_some(); gen_visit_linked_field( typegen_context, &mut type_selections, @@ -223,10 +229,10 @@ pub(crate) fn visit_selections( runtime_imports, custom_error_import, nested_enclosing_linked_field_concrete_type, - is_throw_on_field_error, + field_emit_semantic_types, ) }, - is_throw_on_field_error, + emit_semantic_types, ) } Selection::ScalarField(scalar_field) => { @@ -246,7 +252,7 @@ pub(crate) fn visit_selections( resolver_metadata, RequiredMetadataDirective::find(&scalar_field.directives).is_some(), imported_resolvers, - is_throw_on_field_error, + emit_semantic_types, ); } else { visit_scalar_field( @@ -256,7 +262,7 @@ pub(crate) fn visit_selections( encountered_enums, custom_scalars, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ) } } @@ -274,7 +280,7 @@ pub(crate) fn visit_selections( runtime_imports, custom_error_import, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ), } } @@ -293,7 +299,7 @@ fn visit_fragment_spread( encountered_fragments: &mut EncounteredFragments, imported_resolvers: &mut ImportedResolvers, runtime_imports: &mut RuntimeImports, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { if let Some(resolver_metadata) = RelayResolverMetadata::find(&fragment_spread.directives) { visit_relay_resolver( @@ -309,7 +315,7 @@ fn visit_fragment_spread( resolver_metadata, RequiredMetadataDirective::find(&fragment_spread.directives).is_some(), imported_resolvers, - is_throw_on_field_error, + emit_semantic_types, ); } else { let name = fragment_spread.fragment.item; @@ -685,7 +691,7 @@ fn relay_resolver_field_type( local_resolver_name: StringKey, required: bool, live: bool, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) -> AST { let maybe_scalar_field = if let ResolverOutputTypeInfo::ScalarField = resolver_metadata.output_type_info { @@ -703,7 +709,7 @@ fn relay_resolver_field_type( }; if let Some(field) = maybe_scalar_field { - let type_ = match is_throw_on_field_error { + let type_ = match emit_semantic_types { true => field.semantic_type(), false => field.type_.clone(), }; @@ -721,7 +727,7 @@ fn relay_resolver_field_type( } } else { let field = resolver_metadata.field(typegen_context.schema); - let field_type = match is_throw_on_field_error { + let field_type = match emit_semantic_types { true => field.semantic_type(), false => field.type_.clone(), }; @@ -755,7 +761,7 @@ fn visit_relay_resolver( resolver_metadata: &RelayResolverMetadata, required: bool, imported_resolvers: &mut ImportedResolvers, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { import_relay_resolver_function_type( typegen_context, @@ -785,7 +791,7 @@ fn visit_relay_resolver( local_resolver_name, required, live, - is_throw_on_field_error, + emit_semantic_types, ); type_selections.push(TypeSelection::ScalarField(TypeSelectionScalarField { @@ -813,7 +819,7 @@ fn visit_client_edge( runtime_imports: &mut RuntimeImports, custom_error_import: &mut Option, enclosing_linked_field_concrete_type: Option, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { let (resolver_metadata, fragment_name) = match &client_edge_metadata.backing_field { Selection::FragmentSpread(fragment_spread) => ( @@ -858,7 +864,7 @@ fn visit_client_edge( runtime_imports, custom_error_import, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ); type_selections.append(&mut client_edge_selections); } @@ -878,7 +884,7 @@ fn visit_inline_fragment( runtime_imports: &mut RuntimeImports, custom_error_import: &mut Option, enclosing_linked_field_concrete_type: Option, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { if let Some(module_metadata) = ModuleMetadata::find(&inline_fragment.directives) { let name = module_metadata.fragment_name; @@ -925,7 +931,7 @@ fn visit_inline_fragment( runtime_imports, custom_error_import, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ); } else if let Some(client_edge_metadata) = ClientEdgeMetadata::find(inline_fragment) { visit_client_edge( @@ -942,7 +948,7 @@ fn visit_inline_fragment( runtime_imports, custom_error_import, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ); } else { let mut inline_selections = visit_selections( @@ -1028,7 +1034,7 @@ fn visit_actor_change( runtime_imports: &mut RuntimeImports, custom_error_import: &mut Option, enclosing_linked_field_concrete_type: Option, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { let linked_field = match &inline_fragment.selections[0] { Selection::LinkedField(linked_field) => linked_field, @@ -1059,7 +1065,7 @@ fn visit_actor_change( runtime_imports, custom_error_import, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ); type_selections.push(TypeSelection::ScalarField(TypeSelectionScalarField { field_name_or_alias: key, @@ -1098,7 +1104,7 @@ fn raw_response_visit_inline_fragment( runtime_imports: &mut RuntimeImports, custom_scalars: &mut CustomScalarsImports, enclosing_linked_field_concrete_type: Option, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { let mut selections = raw_response_visit_selections( typegen_context, @@ -1110,7 +1116,7 @@ fn raw_response_visit_inline_fragment( runtime_imports, custom_scalars, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ); if inline_fragment .directives @@ -1161,7 +1167,7 @@ fn gen_visit_linked_field( type_selections: &mut Vec, linked_field: &LinkedField, mut visit_selections_fn: impl FnMut(&[Selection]) -> Vec, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { let field = typegen_context.schema.field(linked_field.definition.item); let schema_name = field.name.item; @@ -1174,13 +1180,16 @@ fn gen_visit_linked_field( let coerce_to_nullable = has_explicit_catch_to_null(&linked_field.directives); - let node_type = match is_throw_on_field_error { - true => apply_directive_nullability(field, &linked_field.directives, coerce_to_nullable), - false => apply_required_directive_nullability( + let is_result_type = is_result_type_directive(&linked_field.directives); + + let node_type = if emit_semantic_types || is_result_type { + apply_directive_nullability(field, &linked_field.directives, coerce_to_nullable) + } else { + apply_required_directive_nullability( &field.type_, &linked_field.directives, coerce_to_nullable, - ), + ) }; type_selections.push(TypeSelection::LinkedField(TypeSelectionLinkedField { @@ -1189,7 +1198,7 @@ fn gen_visit_linked_field( node_selections: selections_to_map(selections.into_iter(), true), conditional: false, concrete_type: None, - is_result_type: is_result_type_directive(&linked_field.directives), + is_result_type, })); } @@ -1200,7 +1209,7 @@ fn visit_scalar_field( encountered_enums: &mut EncounteredEnums, custom_scalars: &mut CustomScalarsImports, enclosing_linked_field_concrete_type: Option, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { let field = typegen_context.schema.field(scalar_field.definition.item); let schema_name = field.name.item; @@ -1212,13 +1221,16 @@ fn visit_scalar_field( let coerce_to_nullable = has_explicit_catch_to_null(&scalar_field.directives); - let field_type = match is_throw_on_field_error { - true => apply_directive_nullability(field, &scalar_field.directives, coerce_to_nullable), - false => apply_required_directive_nullability( + let is_result_type = is_result_type_directive(&scalar_field.directives); + + let field_type = if emit_semantic_types || is_result_type { + apply_directive_nullability(field, &scalar_field.directives, coerce_to_nullable) + } else { + apply_required_directive_nullability( &field.type_, &scalar_field.directives, coerce_to_nullable, - ), + ) }; let special_field = ScalarFieldSpecialSchemaField::from_schema_name( @@ -1262,7 +1274,7 @@ fn visit_scalar_field( value: ast, conditional: false, concrete_type: None, - is_result_type: is_result_type_directive(&scalar_field.directives), + is_result_type, })); } @@ -1281,7 +1293,7 @@ fn visit_condition( runtime_imports: &mut RuntimeImports, custom_error_import: &mut Option, enclosing_linked_field_concrete_type: Option, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) { let mut selections = visit_selections( typegen_context, @@ -1296,7 +1308,7 @@ fn visit_condition( runtime_imports, custom_error_import, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ); for selection in selections.iter_mut() { selection.set_conditional(true); @@ -2218,7 +2230,7 @@ pub(crate) fn raw_response_visit_selections( runtime_imports: &mut RuntimeImports, custom_scalars: &mut CustomScalarsImports, enclosing_linked_field_concrete_type: Option, - is_throw_on_field_error: bool, + emit_semantic_types: bool, ) -> Vec { let mut type_selections = Vec::new(); for selection in selections { @@ -2294,10 +2306,10 @@ pub(crate) fn raw_response_visit_selections( runtime_imports, custom_scalars, nested_enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ) }, - is_throw_on_field_error, + emit_semantic_types, ) } Selection::ScalarField(scalar_field) => visit_scalar_field( @@ -2307,7 +2319,7 @@ pub(crate) fn raw_response_visit_selections( encountered_enums, custom_scalars, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, ), Selection::Condition(condition) => { type_selections.extend(raw_response_visit_selections( @@ -2320,7 +2332,7 @@ pub(crate) fn raw_response_visit_selections( runtime_imports, custom_scalars, enclosing_linked_field_concrete_type, - is_throw_on_field_error, + emit_semantic_types, )); } } diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-semantic-non-null-with-catch.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-semantic-non-null-with-catch.expected new file mode 100644 index 0000000000000..4048140448ad8 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-semantic-non-null-with-catch.expected @@ -0,0 +1,25 @@ +==================================== INPUT ==================================== +fragment MyFragment on Query { + # Should be a Result of a non-nullable since it's semanticNonNull + my_user @catch { + name + } +} + +%extensions% + +extend type Query { + my_user: User @semanticNonNull +} +==================================== OUTPUT =================================== +import { FragmentRefs, Result } from "relay-runtime"; +export type MyFragment$data = { + readonly my_user: Result<{ + readonly name: string | null | undefined; + }, ReadonlyArray>; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-semantic-non-null-with-catch.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-semantic-non-null-with-catch.graphql new file mode 100644 index 0000000000000..23f3491713b48 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/linked-semantic-non-null-with-catch.graphql @@ -0,0 +1,12 @@ +fragment MyFragment on Query { + # Should be a Result of a non-nullable since it's semanticNonNull + my_user @catch { + name + } +} + +%extensions% + +extend type Query { + my_user: User @semanticNonNull +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-semantic-non-null-with-catch.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-semantic-non-null-with-catch.expected new file mode 100644 index 0000000000000..7dde67a13207c --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-semantic-non-null-with-catch.expected @@ -0,0 +1,21 @@ +==================================== INPUT ==================================== +fragment MyFragment on Query { + # Should be a Result of a non-nullable since it's semanticNonNull + my_string @catch +} + +%extensions% + +extend type Query { + my_string: String @semanticNonNull +} +==================================== OUTPUT =================================== +import { FragmentRefs, Result } from "relay-runtime"; +export type MyFragment$data = { + readonly my_string: Result>; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-semantic-non-null-with-catch.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-semantic-non-null-with-catch.graphql new file mode 100644 index 0000000000000..8c4a95ea5aea0 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/scalar-semantic-non-null-with-catch.graphql @@ -0,0 +1,10 @@ +fragment MyFragment on Query { + # Should be a Result of a non-nullable since it's semanticNonNull + my_string @catch +} + +%extensions% + +extend type Query { + my_string: String @semanticNonNull +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.expected new file mode 100644 index 0000000000000..23dd1502eac48 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.expected @@ -0,0 +1,33 @@ +==================================== INPUT ==================================== +fragment MyFragment on Query { + me @catch(to: NULL) { + clientUser { + # Should be non-nullable since it's semanticNonNull within a catch + name + } + } +} + +%extensions% + +extend type User { + clientUser: ClientUser +} + +type ClientUser { + name: String @semanticNonNull +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly me: { + readonly clientUser: { + readonly name: string; + } | null | undefined; + } | null | undefined; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.graphql new file mode 100644 index 0000000000000..085397260d206 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.graphql @@ -0,0 +1,18 @@ +fragment MyFragment on Query { + me @catch(to: NULL) { + clientUser { + # Should be non-nullable since it's semanticNonNull within a catch + name + } + } +} + +%extensions% + +extend type User { + clientUser: ClientUser +} + +type ClientUser { + name: String @semanticNonNull +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-within-catch.expected b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-within-catch.expected new file mode 100644 index 0000000000000..ac0e38463ed33 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-within-catch.expected @@ -0,0 +1,29 @@ +==================================== INPUT ==================================== +fragment MyFragment on Query { + clientUser @catch(to: NULL) { + # Should be non-nullable since it's semanticNonNull within a catch + name + } +} + +%extensions% + +extend type Query { + clientUser: ClientUser +} + +type ClientUser { + name: String @semanticNonNull +} +==================================== OUTPUT =================================== +import { FragmentRefs } from "relay-runtime"; +export type MyFragment$data = { + readonly clientUser: { + readonly name: string; + } | null | undefined; + readonly " $fragmentType": "MyFragment"; +}; +export type MyFragment$key = { + readonly " $data"?: MyFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"MyFragment">; +}; diff --git a/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-within-catch.graphql b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-within-catch.graphql new file mode 100644 index 0000000000000..fa802b793cef4 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_typescript/fixtures/semantic-non-null-within-catch.graphql @@ -0,0 +1,16 @@ +fragment MyFragment on Query { + clientUser @catch(to: NULL) { + # Should be non-nullable since it's semanticNonNull within a catch + name + } +} + +%extensions% + +extend type Query { + clientUser: ClientUser +} + +type ClientUser { + name: String @semanticNonNull +} diff --git a/compiler/crates/relay-typegen/tests/generate_typescript_test.rs b/compiler/crates/relay-typegen/tests/generate_typescript_test.rs index 194d61b6e9cab..1b35fae61ac24 100644 --- a/compiler/crates/relay-typegen/tests/generate_typescript_test.rs +++ b/compiler/crates/relay-typegen/tests/generate_typescript_test.rs @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<40ee37b571f2844f07f55ccb1407a04a>> + * @generated SignedSource<> */ mod generate_typescript; @@ -173,6 +173,13 @@ async fn linked_field() { test_fixture(transform_fixture, file!(), "linked-field.graphql", "generate_typescript/fixtures/linked-field.expected", input, expected).await; } +#[tokio::test] +async fn linked_semantic_non_null_with_catch() { + let input = include_str!("generate_typescript/fixtures/linked-semantic-non-null-with-catch.graphql"); + let expected = include_str!("generate_typescript/fixtures/linked-semantic-non-null-with-catch.expected"); + test_fixture(transform_fixture, file!(), "linked-semantic-non-null-with-catch.graphql", "generate_typescript/fixtures/linked-semantic-non-null-with-catch.expected", input, expected).await; +} + #[tokio::test] async fn match_field() { let input = include_str!("generate_typescript/fixtures/match-field.graphql"); @@ -467,6 +474,20 @@ async fn scalar_field() { test_fixture(transform_fixture, file!(), "scalar-field.graphql", "generate_typescript/fixtures/scalar-field.expected", input, expected).await; } +#[tokio::test] +async fn scalar_semantic_non_null_with_catch() { + let input = include_str!("generate_typescript/fixtures/scalar-semantic-non-null-with-catch.graphql"); + let expected = include_str!("generate_typescript/fixtures/scalar-semantic-non-null-with-catch.expected"); + test_fixture(transform_fixture, file!(), "scalar-semantic-non-null-with-catch.graphql", "generate_typescript/fixtures/scalar-semantic-non-null-with-catch.expected", input, expected).await; +} + +#[tokio::test] +async fn semantic_non_null_deeply_nested_within_catch() { + let input = include_str!("generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.expected"); + test_fixture(transform_fixture, file!(), "semantic-non-null-deeply-nested-within-catch.graphql", "generate_typescript/fixtures/semantic-non-null-deeply-nested-within-catch.expected", input, expected).await; +} + #[tokio::test] async fn semantic_non_null_in_raw_response() { let input = include_str!("generate_typescript/fixtures/semantic_non_null_in_raw_response.graphql"); @@ -544,6 +565,13 @@ async fn semantic_non_null_scalar_with_catch() { test_fixture(transform_fixture, file!(), "semantic_non_null_scalar_with_catch.graphql", "generate_typescript/fixtures/semantic_non_null_scalar_with_catch.expected", input, expected).await; } +#[tokio::test] +async fn semantic_non_null_within_catch() { + let input = include_str!("generate_typescript/fixtures/semantic-non-null-within-catch.graphql"); + let expected = include_str!("generate_typescript/fixtures/semantic-non-null-within-catch.expected"); + test_fixture(transform_fixture, file!(), "semantic-non-null-within-catch.graphql", "generate_typescript/fixtures/semantic-non-null-within-catch.expected", input, expected).await; +} + #[tokio::test] async fn simple() { let input = include_str!("generate_typescript/fixtures/simple.graphql");