diff --git a/compiler/crates/relay-docblock/Cargo.toml b/compiler/crates/relay-docblock/Cargo.toml index 236475c1857c2..80236c178a40d 100644 --- a/compiler/crates/relay-docblock/Cargo.toml +++ b/compiler/crates/relay-docblock/Cargo.toml @@ -21,6 +21,7 @@ graphql-ir = { path = "../graphql-ir" } graphql-syntax = { path = "../graphql-syntax" } intern = { path = "../intern" } lazy_static = "1.4" +relay-schema = { path = "../relay-schema" } schema = { path = "../schema" } thiserror = "1.0.36" diff --git a/compiler/crates/relay-docblock/src/ir.rs b/compiler/crates/relay-docblock/src/ir.rs index 538a5efd2bcbc..519216bb1fbec 100644 --- a/compiler/crates/relay-docblock/src/ir.rs +++ b/compiler/crates/relay-docblock/src/ir.rs @@ -32,6 +32,7 @@ use graphql_syntax::NamedTypeAnnotation; use graphql_syntax::NonNullTypeAnnotation; use graphql_syntax::ObjectTypeDefinition; use graphql_syntax::ObjectTypeExtension; +use graphql_syntax::ScalarTypeDefinition; use graphql_syntax::SchemaDocument; use graphql_syntax::StringNode; use graphql_syntax::Token; @@ -41,6 +42,9 @@ use graphql_syntax::TypeSystemDefinition; use intern::string_key::Intern; use intern::string_key::StringKey; use lazy_static::lazy_static; +use relay_schema::CUSTOM_SCALAR_DIRECTIVE_NAME; +use relay_schema::EXPORT_NAME_CUSTOM_SCALAR_ARGUMENT_NAME; +use relay_schema::PATH_CUSTOM_SCALAR_ARGUMENT_NAME; use schema::suggestion_list::GraphQLSuggestions; use schema::InterfaceID; use schema::ObjectID; @@ -69,20 +73,25 @@ lazy_static! { ArgumentName("has_output_type".intern()); pub(crate) static ref ID_FIELD_NAME: StringKey = "id".intern(); static ref RESOLVER_MODEL_INSTANCE_FIELD_NAME: StringKey = "__relay_model_instance".intern(); + static ref MODEL_CUSTOM_SCALAR_TYPE_PREFIX: StringKey = "Model".intern(); } #[derive(Debug, PartialEq)] pub enum DocblockIr { RelayResolver(RelayResolverIr), StrongObjectResolver(StrongObjectIr), + WeakObjectType(WeakObjectIr), } impl DocblockIr { pub fn to_sdl_string(&self, schema: &SDLSchema) -> DiagnosticsResult { - match self { - DocblockIr::RelayResolver(relay_resolver) => relay_resolver.to_sdl_string(schema), - DocblockIr::StrongObjectResolver(strong_object) => strong_object.to_sdl_string(schema), - } + Ok(self + .to_graphql_schema_ast(schema)? + .definitions + .iter() + .map(|definition| format!("{}", definition)) + .collect::>() + .join("\n\n")) } pub fn to_graphql_schema_ast(&self, schema: &SDLSchema) -> DiagnosticsResult { match self { @@ -92,6 +101,7 @@ impl DocblockIr { DocblockIr::StrongObjectResolver(strong_object) => { strong_object.to_graphql_schema_ast(schema) } + DocblockIr::WeakObjectType(weak_object) => weak_object.to_graphql_schema_ast(schema), } } } @@ -151,16 +161,6 @@ trait ResolverIr { fn live(&self) -> Option; fn named_import(&self) -> Option; - fn to_sdl_string(&self, schema: &SDLSchema) -> DiagnosticsResult { - Ok(self - .to_graphql_schema_ast(schema)? - .definitions - .iter() - .map(|definition| format!("{}", definition)) - .collect::>() - .join("\n\n")) - } - fn to_graphql_schema_ast(&self, schema: &SDLSchema) -> DiagnosticsResult { Ok(SchemaDocument { location: self.location(), @@ -605,6 +605,141 @@ impl ResolverIr for StrongObjectIr { } } +/// Relay Resolver docblock representing a "model" type for a weak object +#[derive(Debug, PartialEq)] +pub struct WeakObjectIr { + pub type_name: PopulatedIrField, + pub description: Option>, + pub deprecated: Option, + pub location: Location, +} + +impl WeakObjectIr { + // Generate the named GraphQL type (with an __relay_model_instance field). + fn type_definition(&self) -> TypeSystemDefinition { + let span = self.type_name.value.location.span(); + + let mut directives = vec![ConstantDirective { + span: span.clone(), + at: dummy_token(&span), + name: string_key_as_identifier(RELAY_RESOLVER_MODEL_DIRECTIVE_NAME.0), + arguments: None, + }]; + if let Some(deprecated) = self.deprecated { + directives.push(ConstantDirective { + span: span.clone(), + at: dummy_token(span), + name: string_key_as_identifier(DEPRECATED_RESOLVER_DIRECTIVE_NAME.0), + arguments: deprecated.value.map(|value| { + List::generated(vec![string_argument( + DEPRECATED_REASON_ARGUMENT_NAME.0, + value, + )]) + }), + }) + } + TypeSystemDefinition::ObjectTypeDefinition(ObjectTypeDefinition { + name: as_identifier(self.type_name.value), + interfaces: vec![], + directives, + fields: Some(List::generated(vec![FieldDefinition { + name: string_key_as_identifier(*RESOLVER_MODEL_INSTANCE_FIELD_NAME), + type_: TypeAnnotation::Named(NamedTypeAnnotation { + name: string_key_as_identifier(self.model_type_name()), + }), + arguments: None, + directives: vec![], + description: self.description.map(as_string_node), + }])), + }) + } + + // Genete a custom sclar definition based on the exported type. + fn instance_scalar_type_definition(&self) -> TypeSystemDefinition { + let span = self.type_name.value.location.span(); + TypeSystemDefinition::ScalarTypeDefinition(ScalarTypeDefinition { + name: Identifier { + span: *span, + token: dummy_token(span), + value: self.model_type_name(), + }, + directives: vec![ConstantDirective { + span: *span, + at: dummy_token(span), + name: as_identifier(WithLocation::generated(*CUSTOM_SCALAR_DIRECTIVE_NAME)), + arguments: Some(List::generated(vec![ + ConstantArgument { + span: *span, + name: as_identifier(WithLocation::generated( + *PATH_CUSTOM_SCALAR_ARGUMENT_NAME, + )), + colon: dummy_token(span), + value: ConstantValue::String(StringNode { + token: dummy_token(span), + value: self.location.source_location().path().intern(), + }), + }, + ConstantArgument { + span: *span, + name: as_identifier(WithLocation::generated( + *EXPORT_NAME_CUSTOM_SCALAR_ARGUMENT_NAME, + )), + colon: dummy_token(span), + value: ConstantValue::String(StringNode { + token: dummy_token(span), + value: self.type_name.value.item, + }), + }, + ])), + }], + }) + } + + // Derive a typename for the custom scalar that will be used as this type's + // `__relay_model_instance` model. + fn model_type_name(&self) -> StringKey { + // TODO: Ensure this type does not already exist? + format!( + "{}{}", + self.type_name.value.item, *MODEL_CUSTOM_SCALAR_TYPE_PREFIX + ) + .intern() + } +} + +impl ResolverIr for WeakObjectIr { + fn definitions(&self, _schema: &SDLSchema) -> DiagnosticsResult> { + Ok(vec![ + self.instance_scalar_type_definition(), + self.type_definition(), + ]) + } + + fn location(&self) -> Location { + self.location + } + + fn root_fragment(&self) -> Option> { + None + } + + fn output_type(&self) -> Option<&OutputType> { + None + } + + fn deprecated(&self) -> Option { + self.deprecated + } + + fn live(&self) -> Option { + None + } + + fn named_import(&self) -> Option { + None + } +} + fn string_argument(name: StringKey, value: WithLocation) -> ConstantArgument { let span = value.location.span(); ConstantArgument { diff --git a/compiler/crates/relay-docblock/src/lib.rs b/compiler/crates/relay-docblock/src/lib.rs index bcc33fd8fa7c5..fdacdf69f6446 100644 --- a/compiler/crates/relay-docblock/src/lib.rs +++ b/compiler/crates/relay-docblock/src/lib.rs @@ -40,6 +40,7 @@ use ir::OutputType; use ir::PopulatedIrField; pub use ir::RelayResolverIr; use ir::StrongObjectIr; +use ir::WeakObjectIr; use lazy_static::lazy_static; use crate::errors::ErrorMessages; @@ -60,6 +61,7 @@ lazy_static! { static ref LIVE_FIELD: StringKey = "live".intern(); static ref ROOT_FRAGMENT_FIELD: StringKey = "rootFragment".intern(); static ref OUTPUT_TYPE_FIELD: StringKey = "outputType".intern(); + static ref WEAK_FIELD: StringKey = "weak".intern(); static ref EMPTY_STRING: StringKey = "".intern(); static ref ARGUMENT_DEFINITIONS: DirectiveName = DirectiveName("argumentDefinitions".intern()); static ref ARGUMENT_TYPE: StringKey = "type".intern(); @@ -107,6 +109,7 @@ impl RelayResolverParser { *DEPRECATED_FIELD, *LIVE_FIELD, *OUTPUT_TYPE_FIELD, + *WEAK_FIELD, ], options, } @@ -161,14 +164,18 @@ impl RelayResolverParser { return Err(()); } - self.parse_strong_object( - ast.location, - PopulatedIrField { - key_location: relay_resolver.key_location, - value: type_name, - }, - ) - .map(DocblockIr::StrongObjectResolver) + let type_ = PopulatedIrField { + key_location: relay_resolver.key_location, + value: type_name, + }; + + if self.fields.get(&WEAK_FIELD).is_some() { + self.parse_weak_type(ast.location, type_) + .map(DocblockIr::WeakObjectType) + } else { + self.parse_strong_object(ast.location, type_) + .map(DocblockIr::StrongObjectResolver) + } } else { self.parse_relay_resolver(ast.location, definitions_in_file) .map(DocblockIr::RelayResolver) @@ -606,4 +613,18 @@ impl RelayResolverParser { named_import: self.options.use_named_imports.then_some(type_.value.item), }) } + + fn parse_weak_type( + &self, + ast_location: Location, + type_: PopulatedIrField, + ) -> ParseResult { + // TODO: Validate that no incompatible docblock fields are used. + Ok(WeakObjectIr { + type_name: type_, + description: self.description, + deprecated: self.fields.get(&DEPRECATED_FIELD).copied(), + location: ast_location, + }) + } } diff --git a/compiler/crates/relay-docblock/tests/to_schema/fixtures/weak-type.expected b/compiler/crates/relay-docblock/tests/to_schema/fixtures/weak-type.expected new file mode 100644 index 0000000000000..31a33db069c70 --- /dev/null +++ b/compiler/crates/relay-docblock/tests/to_schema/fixtures/weak-type.expected @@ -0,0 +1,27 @@ +==================================== INPUT ==================================== +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @RelayResolver MyClientType + * + * @weak + * @deprecated Don't use this any more + * + * Check out this awesome client type! + */ + + export type MyClientType = { + name: string +} +==================================== OUTPUT =================================== +scalar MyClientTypeModel @__RelayCustomScalar(path: "/path/to/test/fixture/weak-type.js", export_name: "MyClientType") + + +type MyClientType @__RelayResolverModel @deprecated(reason: "Don't use this any more") { + __relay_model_instance: MyClientTypeModel +} diff --git a/compiler/crates/relay-docblock/tests/to_schema/fixtures/weak-type.js b/compiler/crates/relay-docblock/tests/to_schema/fixtures/weak-type.js new file mode 100644 index 0000000000000..858511efa0443 --- /dev/null +++ b/compiler/crates/relay-docblock/tests/to_schema/fixtures/weak-type.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @RelayResolver MyClientType + * + * @weak + * @deprecated Don't use this any more + * + * Check out this awesome client type! + */ + + export type MyClientType = { + name: string +} \ No newline at end of file diff --git a/compiler/crates/relay-docblock/tests/to_schema_test.rs b/compiler/crates/relay-docblock/tests/to_schema_test.rs index a4670730ebec0..d0be32cd6a820 100644 --- a/compiler/crates/relay-docblock/tests/to_schema_test.rs +++ b/compiler/crates/relay-docblock/tests/to_schema_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<<83c48009350539bc419375d2dd6ef433>> + * @generated SignedSource<> */ mod to_schema; @@ -151,3 +151,10 @@ fn relay_resolver_with_output_type() { let expected = include_str!("to_schema/fixtures/relay-resolver-with-output-type.expected"); test_fixture(transform_fixture, "relay-resolver-with-output-type.js", "to_schema/fixtures/relay-resolver-with-output-type.expected", input, expected); } + +#[test] +fn weak_type() { + let input = include_str!("to_schema/fixtures/weak-type.js"); + let expected = include_str!("to_schema/fixtures/weak-type.expected"); + test_fixture(transform_fixture, "weak-type.js", "to_schema/fixtures/weak-type.expected", input, expected); +} diff --git a/compiler/crates/relay-lsp/src/docblock_resolution_info.rs b/compiler/crates/relay-lsp/src/docblock_resolution_info.rs index a04af3256067d..202462aaa5612 100644 --- a/compiler/crates/relay-lsp/src/docblock_resolution_info.rs +++ b/compiler/crates/relay-lsp/src/docblock_resolution_info.rs @@ -69,8 +69,39 @@ pub fn create_docblock_resolution_info( Err(LSPRuntimeError::ExpectedError) } - DocblockIr::StrongObjectResolver(_) => Err(LSPRuntimeError::UnexpectedError( - "TODO: Implement support for strong object.".to_owned(), - )), + DocblockIr::StrongObjectResolver(strong_object) => { + if strong_object.type_.value.location.contains(position_span) { + return Ok(DocblockResolutionInfo::OnType( + strong_object.type_.value.item, + )); + } + + if let Some(deprecated) = strong_object.deprecated { + if deprecated.key_location.contains(position_span) { + return Ok(DocblockResolutionInfo::Deprecated); + } + } + Err(LSPRuntimeError::ExpectedError) + } + DocblockIr::WeakObjectType(weak_type_ir) => { + if weak_type_ir + .type_name + .value + .location + .contains(position_span) + { + return Ok(DocblockResolutionInfo::OnType( + weak_type_ir.type_name.value.item, + )); + } + + if let Some(deprecated) = weak_type_ir.deprecated { + if deprecated.key_location.contains(position_span) { + return Ok(DocblockResolutionInfo::Deprecated); + } + } + // TODO: We could provide location mapping for the @weak docblock attribute + Err(LSPRuntimeError::ExpectedError) + } } } diff --git a/compiler/crates/relay-lsp/src/references/mod.rs b/compiler/crates/relay-lsp/src/references/mod.rs index 0f6a3b257c590..b63bd8669db32 100644 --- a/compiler/crates/relay-lsp/src/references/mod.rs +++ b/compiler/crates/relay-lsp/src/references/mod.rs @@ -85,6 +85,11 @@ fn get_references_response( "TODO: Implement support for strong object.".to_owned(), )); } + DocblockIr::WeakObjectType(_) => { + return Err(LSPRuntimeError::UnexpectedError( + "TODO: Implement support for weak object.".to_owned(), + )); + } }; let references = find_field_locations(program, field_name, type_name) diff --git a/compiler/crates/relay-schema/Cargo.toml b/compiler/crates/relay-schema/Cargo.toml index 67ee44b2ccc6b..cb4ae57a109d1 100644 --- a/compiler/crates/relay-schema/Cargo.toml +++ b/compiler/crates/relay-schema/Cargo.toml @@ -10,3 +10,5 @@ license = "MIT" common = { path = "../common" } intern = { path = "../intern" } schema = { path = "../schema" } +lazy_static = "1.4" + diff --git a/compiler/crates/relay-schema/src/lib.rs b/compiler/crates/relay-schema/src/lib.rs index e72b6767127d7..2d6665632ac57 100644 --- a/compiler/crates/relay-schema/src/lib.rs +++ b/compiler/crates/relay-schema/src/lib.rs @@ -13,17 +13,28 @@ use std::iter::once; +use ::intern::string_key::StringKey; use common::ArgumentName; use common::DiagnosticsResult; use common::DirectiveName; use common::SourceLocationKey; use intern::intern; +use lazy_static::lazy_static; use schema::ArgumentDefinitions; use schema::SDLSchema; use schema::TypeReference; const RELAY_EXTENSIONS: &str = include_str!("./relay-extensions.graphql"); +lazy_static! { + static ref DEFER: DirectiveName = DirectiveName(intern!("defer")); + static ref STREAM: DirectiveName = DirectiveName(intern!("stream")); + static ref LABEL: ArgumentName = ArgumentName(intern!("label")); + pub static ref CUSTOM_SCALAR_DIRECTIVE_NAME: StringKey = intern!("__RelayCustomScalar"); + pub static ref PATH_CUSTOM_SCALAR_ARGUMENT_NAME: StringKey = intern!("path"); + pub static ref EXPORT_NAME_CUSTOM_SCALAR_ARGUMENT_NAME: StringKey = intern!("export_name"); +} + pub fn build_schema_with_extensions, U: AsRef>( server_sdls: &[(T, SourceLocationKey)], extension_sdls: &[(U, SourceLocationKey)], @@ -40,14 +51,11 @@ pub fn build_schema_with_extensions, U: AsRef>( // Remove label arg from @defer and @stream directives since the compiler // adds these arguments. - for directive_name in &[ - DirectiveName(intern!("defer")), - DirectiveName(intern!("stream")), - ] { + for directive_name in &[*DEFER, *STREAM] { if let Some(directive) = schema.get_directive_mut(*directive_name) { let mut next_args: Vec<_> = directive.arguments.iter().cloned().collect(); for arg in next_args.iter_mut() { - if arg.name == ArgumentName(intern!("label")) { + if arg.name == *LABEL { if let TypeReference::NonNull(of) = &arg.type_ { arg.type_ = *of.clone() }; diff --git a/compiler/crates/relay-typegen/Cargo.toml b/compiler/crates/relay-typegen/Cargo.toml index 1c4cadd2344d5..7848a4d2bbb21 100644 --- a/compiler/crates/relay-typegen/Cargo.toml +++ b/compiler/crates/relay-typegen/Cargo.toml @@ -19,6 +19,7 @@ intern = { path = "../intern" } itertools = "0.10.3" lazy_static = "1.4" relay-config = { path = "../relay-config" } +relay-schema = { path = "../relay-schema" } relay-transforms = { path = "../relay-transforms" } schema = { path = "../schema" } diff --git a/compiler/crates/relay-typegen/src/visit.rs b/compiler/crates/relay-typegen/src/visit.rs index 8f726e921c734..38fed7c5546d5 100644 --- a/compiler/crates/relay-typegen/src/visit.rs +++ b/compiler/crates/relay-typegen/src/visit.rs @@ -6,10 +6,14 @@ */ use std::hash::Hash; +use std::path::PathBuf; use ::intern::intern; use ::intern::string_key::Intern; use ::intern::string_key::StringKey; +use ::intern::Lookup; +use common::ArgumentName; +use common::DirectiveName; use common::NamedItem; use graphql_ir::Condition; use graphql_ir::Directive; @@ -26,6 +30,9 @@ use indexmap::IndexSet; use relay_config::CustomScalarType; use relay_config::CustomScalarTypeImport; use relay_config::TypegenLanguage; +use relay_schema::CUSTOM_SCALAR_DIRECTIVE_NAME; +use relay_schema::EXPORT_NAME_CUSTOM_SCALAR_ARGUMENT_NAME; +use relay_schema::PATH_CUSTOM_SCALAR_ARGUMENT_NAME; use relay_transforms::ClientEdgeMetadata; use relay_transforms::FragmentAliasMetadata; use relay_transforms::ModuleMetadata; @@ -1677,7 +1684,35 @@ fn transform_graphql_scalar_type( scalar: ScalarID, custom_scalars: &mut CustomScalarsImports, ) -> AST { - let scalar_name = typegen_context.schema.scalar(scalar).name; + let scalar_definition = typegen_context.schema.scalar(scalar); + let scalar_name = scalar_definition.name; + + if let Some(directive) = scalar_definition + .directives + .named(DirectiveName(*CUSTOM_SCALAR_DIRECTIVE_NAME)) + { + let path = directive + .arguments + .named(ArgumentName(*PATH_CUSTOM_SCALAR_ARGUMENT_NAME)) + .expect(&format!( + "Expected @{} directive to have a path argument", + *CUSTOM_SCALAR_DIRECTIVE_NAME + )) + .expect_string_literal(); + let export_name = directive + .arguments + .named(ArgumentName(*EXPORT_NAME_CUSTOM_SCALAR_ARGUMENT_NAME)) + .expect(&format!( + "Expected @{} directive to have an export_name argument", + *CUSTOM_SCALAR_DIRECTIVE_NAME + )) + .expect_string_literal(); + custom_scalars.insert((export_name, PathBuf::from(path.lookup()))); + return AST::RawType(scalar_name.item.0); + } + // TODO: We could implement custom variables that are provided via the + // config by inserting them into the schema with directives, thus avoiding + // having two different ways to express typed custom scalars internally. if let Some(custom_scalar) = typegen_context .project_config .typegen_config diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-weak-client-type.expected b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-weak-client-type.expected new file mode 100644 index 0000000000000..cd272fefb360d --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-weak-client-type.expected @@ -0,0 +1,36 @@ +==================================== INPUT ==================================== +query RelayClientIDFieldQuery($id: ID!) { + my_custom_type { + __instance + } +} + +# %extensions% + +scalar CustomClientTypeModel + @__RelayCustomScalar( + path: "./path/to/the/module.js" + export_name: "CustomClientTypeModel" + ) + +type CustomClientType { + __instance: CustomClientTypeModel +} + +extend type Query { + my_custom_type: CustomClientType +} +==================================== OUTPUT =================================== +import type { CustomClientTypeModel } from "./path/to/the/module.js"; +export type RelayClientIDFieldQuery$variables = {| + id: string, +|}; +export type RelayClientIDFieldQuery$data = {| + +my_custom_type: ?{| + +__instance: ?CustomClientTypeModel, + |}, +|}; +export type RelayClientIDFieldQuery = {| + response: RelayClientIDFieldQuery$data, + variables: RelayClientIDFieldQuery$variables, +|}; diff --git a/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-weak-client-type.graphql b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-weak-client-type.graphql new file mode 100644 index 0000000000000..0047656bb05f3 --- /dev/null +++ b/compiler/crates/relay-typegen/tests/generate_flow/fixtures/relay-weak-client-type.graphql @@ -0,0 +1,21 @@ +query RelayClientIDFieldQuery($id: ID!) { + my_custom_type { + __instance + } +} + +# %extensions% + +scalar CustomClientTypeModel + @__RelayCustomScalar( + path: "./path/to/the/module.js" + export_name: "CustomClientTypeModel" + ) + +type CustomClientType { + __instance: CustomClientTypeModel +} + +extend type Query { + my_custom_type: CustomClientType +} diff --git a/compiler/crates/relay-typegen/tests/generate_flow_test.rs b/compiler/crates/relay-typegen/tests/generate_flow_test.rs index 86973b2914c26..4e307822f2dca 100644 --- a/compiler/crates/relay-typegen/tests/generate_flow_test.rs +++ b/compiler/crates/relay-typegen/tests/generate_flow_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<<998676e02bd09c1762645af9a892181f>> + * @generated SignedSource<<40e28ca764c2c97dfc7df1f620282c5a>> */ mod generate_flow; @@ -390,6 +390,13 @@ fn relay_resolver_required() { test_fixture(transform_fixture, "relay-resolver-required.graphql", "generate_flow/fixtures/relay-resolver-required.expected", input, expected); } +#[test] +fn relay_weak_client_type() { + let input = include_str!("generate_flow/fixtures/relay-weak-client-type.graphql"); + let expected = include_str!("generate_flow/fixtures/relay-weak-client-type.expected"); + test_fixture(transform_fixture, "relay-weak-client-type.graphql", "generate_flow/fixtures/relay-weak-client-type.expected", input, expected); +} + #[test] fn required() { let input = include_str!("generate_flow/fixtures/required.graphql"); diff --git a/compiler/crates/schema/src/definitions.rs b/compiler/crates/schema/src/definitions.rs index 08731378a386c..4dfe2cdef46af 100644 --- a/compiler/crates/schema/src/definitions.rs +++ b/compiler/crates/schema/src/definitions.rs @@ -399,6 +399,24 @@ pub struct ArgumentValue { pub value: ConstantValue, } +impl ArgumentValue { + /// If the value is a constant string literal, return the value, otherwise None. + pub fn get_string_literal(&self) -> Option { + if let ConstantValue::String(string_node) = &self.value { + Some(string_node.value) + } else { + None + } + } + /// Return the constant string literal of this value. + /// Panics if the value is not a constant string literal. + pub fn expect_string_literal(&self) -> StringKey { + self.get_string_literal().unwrap_or_else(|| { + panic!("expected a string literal, got {:?}", self); + }) + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct DirectiveValue { pub name: DirectiveName,