From 5394dcd102fb7ce6946f6597d7cab3223506e41b Mon Sep 17 00:00:00 2001 From: James Lamb Date: Thu, 28 Nov 2024 19:09:47 +1100 Subject: [PATCH 1/4] extend introspection schema to include oneOf and specifiedByURL --- graphql_client_cli/src/graphql/introspection_query.graphql | 1 + graphql_client_cli/src/graphql/introspection_schema.graphql | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/graphql_client_cli/src/graphql/introspection_query.graphql b/graphql_client_cli/src/graphql/introspection_query.graphql index d17da75f3..253027a43 100644 --- a/graphql_client_cli/src/graphql/introspection_query.graphql +++ b/graphql_client_cli/src/graphql/introspection_query.graphql @@ -27,6 +27,7 @@ fragment FullType on __Type { kind name description + isOneOf fields(includeDeprecated: true) { name description diff --git a/graphql_client_cli/src/graphql/introspection_schema.graphql b/graphql_client_cli/src/graphql/introspection_schema.graphql index 29f5587bf..e83443a19 100644 --- a/graphql_client_cli/src/graphql/introspection_schema.graphql +++ b/graphql_client_cli/src/graphql/introspection_schema.graphql @@ -36,6 +36,12 @@ type __Type { # NON_NULL and LIST only ofType: __Type + + # may be non-null for custom SCALAR, otherwise null. + specifiedByURL: String + + # should be non-null for INPUT_OBJECT only + isOneOf: Boolean } type __Field { From 1cc394daa7397c472bfa7d1274da16dd2394b0c9 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Fri, 29 Nov 2024 17:11:39 +1100 Subject: [PATCH 2/4] draft @include for IsOneOf for introspection with cli option --- .../src/graphql/introspection_query.graphql | 4 +- .../src/graphql/introspection_schema.graphql | 2 +- graphql_client_cli/src/introspect_schema.rs | 50 ++++++++++++++++--- graphql_client_cli/src/main.rs | 7 +++ 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/graphql_client_cli/src/graphql/introspection_query.graphql b/graphql_client_cli/src/graphql/introspection_query.graphql index 253027a43..dfbea7019 100644 --- a/graphql_client_cli/src/graphql/introspection_query.graphql +++ b/graphql_client_cli/src/graphql/introspection_query.graphql @@ -1,4 +1,4 @@ -query IntrospectionQuery { +query IntrospectionQuery($isOneOf: Boolean!) { __schema { queryType { name @@ -27,7 +27,7 @@ fragment FullType on __Type { kind name description - isOneOf + isOneOf @include(if: $isOneOf) fields(includeDeprecated: true) { name description diff --git a/graphql_client_cli/src/graphql/introspection_schema.graphql b/graphql_client_cli/src/graphql/introspection_schema.graphql index e83443a19..f177de1a7 100644 --- a/graphql_client_cli/src/graphql/introspection_schema.graphql +++ b/graphql_client_cli/src/graphql/introspection_schema.graphql @@ -104,4 +104,4 @@ enum __DirectiveLocation { ENUM_VALUE INPUT_OBJECT INPUT_FIELD_DEFINITION -} +} \ No newline at end of file diff --git a/graphql_client_cli/src/introspect_schema.rs b/graphql_client_cli/src/introspect_schema.rs index 62e62fbb5..d260bfd4c 100644 --- a/graphql_client_cli/src/introspect_schema.rs +++ b/graphql_client_cli/src/introspect_schema.rs @@ -1,10 +1,15 @@ use crate::error::Error; use crate::CliResult; -use graphql_client::GraphQLQuery; +use graphql_client::{GraphQLQuery, QueryBody}; use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE}; +use serde_json::json; +use std::fmt; use std::path::PathBuf; use std::str::FromStr; +use clap::ValueEnum; +use introspection_query::Variables; + #[derive(GraphQLQuery)] #[graphql( schema_path = "src/graphql/introspection_schema.graphql", @@ -21,6 +26,7 @@ pub fn introspect_schema( authorization: Option, headers: Vec
, no_ssl: bool, + options: Option>, ) -> CliResult<()> { use std::io::Write; @@ -29,11 +35,8 @@ pub fn introspect_schema( None => Box::new(std::io::stdout()), }; - let request_body: graphql_client::QueryBody<()> = graphql_client::QueryBody { - variables: (), - query: introspection_query::QUERY, - operation_name: introspection_query::OPERATION_NAME, - }; + let request_body: QueryBody = + IntrospectionQuery::build_query(construct_options(options)); let client = reqwest::blocking::Client::builder() .danger_accept_invalid_certs(no_ssl) @@ -126,6 +129,41 @@ impl FromStr for Header { } } +#[derive(ValueEnum, Clone, Debug, PartialEq)] +pub enum IntrospectionOptions { + /// Enable the @oneOf directive in the introspection query. + IsOneOf, + /// SpecifiedBy is is used within the type system definition language to + /// provide a scalar specification URL for specifying the behavior of custom scalar types. + SpecifiedBy, +} + +fn construct_options(options: Option>) -> introspection_query::Variables { + match options { + Some(opts) => introspection_query::Variables { + is_one_of: opts.contains(&IntrospectionOptions::IsOneOf), + }, + None => introspection_query::Variables { is_one_of: false }, + } +} + +impl FromStr for IntrospectionOptions { + type Err = String; + + fn from_str(input: &str) -> Result { + match input.to_lowercase().as_str() { + "isoneof" => Ok(IntrospectionOptions::IsOneOf), + _ => Err(format!("unknown option {:?}", input)), + } + } +} + +impl fmt::Debug for Variables { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", json!(self)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/graphql_client_cli/src/main.rs b/graphql_client_cli/src/main.rs index 5fd1bcb1e..cb902ba82 100644 --- a/graphql_client_cli/src/main.rs +++ b/graphql_client_cli/src/main.rs @@ -5,6 +5,7 @@ mod introspect_schema; use clap::Parser; use env_logger::fmt::{Color, Style, StyledValue}; use error::Error; +use introspect_schema::IntrospectionOptions; use log::Level; use std::path::PathBuf; use Cli::Generate; @@ -34,6 +35,10 @@ enum Cli { /// Default value is false. #[clap(long = "no-ssl")] no_ssl: bool, + /// Introspection Options + /// is-one-of will include IsOneOf in the introspection query. + #[clap(long = "options", action(clap::ArgAction::Append))] + options: Option>, }, #[clap(name = "generate")] Generate { @@ -93,12 +98,14 @@ fn main() -> CliResult<()> { authorization, headers, no_ssl, + options, } => introspect_schema::introspect_schema( &schema_location, output, authorization, headers, no_ssl, + options, ), Generate { variables_derives, From 215824f84fc4c342d741d165aeb4a4ef1ccb7da2 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Tue, 10 Dec 2024 11:34:44 +1100 Subject: [PATCH 3/4] tidy up code, remove include logic, separate the schemas into individual files for better readability --- .../src/graphql/introspection_query.graphql | 3 +- ..._query_with_isOneOf_specifiedByUrl.graphql | 101 ++++++++++++++++++ ...introspection_query_with_is_one_of.graphql | 100 +++++++++++++++++ ...rospection_query_with_specified_by.graphql | 100 +++++++++++++++++ .../src/graphql/introspection_schema.graphql | 2 + .../src/introspection_queries.rs | 41 +++++++ ...pect_schema.rs => introspection_schema.rs} | 88 ++++++--------- graphql_client_cli/src/main.rs | 28 +++-- 8 files changed, 398 insertions(+), 65 deletions(-) create mode 100644 graphql_client_cli/src/graphql/introspection_query_with_isOneOf_specifiedByUrl.graphql create mode 100644 graphql_client_cli/src/graphql/introspection_query_with_is_one_of.graphql create mode 100644 graphql_client_cli/src/graphql/introspection_query_with_specified_by.graphql create mode 100644 graphql_client_cli/src/introspection_queries.rs rename graphql_client_cli/src/{introspect_schema.rs => introspection_schema.rs} (75%) diff --git a/graphql_client_cli/src/graphql/introspection_query.graphql b/graphql_client_cli/src/graphql/introspection_query.graphql index dfbea7019..d17da75f3 100644 --- a/graphql_client_cli/src/graphql/introspection_query.graphql +++ b/graphql_client_cli/src/graphql/introspection_query.graphql @@ -1,4 +1,4 @@ -query IntrospectionQuery($isOneOf: Boolean!) { +query IntrospectionQuery { __schema { queryType { name @@ -27,7 +27,6 @@ fragment FullType on __Type { kind name description - isOneOf @include(if: $isOneOf) fields(includeDeprecated: true) { name description diff --git a/graphql_client_cli/src/graphql/introspection_query_with_isOneOf_specifiedByUrl.graphql b/graphql_client_cli/src/graphql/introspection_query_with_isOneOf_specifiedByUrl.graphql new file mode 100644 index 000000000..e78d209f9 --- /dev/null +++ b/graphql_client_cli/src/graphql/introspection_query_with_isOneOf_specifiedByUrl.graphql @@ -0,0 +1,101 @@ +query IntrospectionQueryWithIsOneOfSpecifiedByURL { + __schema { + queryType { + name + } + mutationType { + name + } + subscriptionType { + name + } + types { + ...FullTypeWithisOneOfSpecifiedByURL + } + directives { + name + description + locations + args { + ...InputValue + } + } + } +} + +fragment FullTypeWithisOneOfSpecifiedByURL on __Type { + kind + name + description + isOneOf + specifiedByURL + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ...TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} diff --git a/graphql_client_cli/src/graphql/introspection_query_with_is_one_of.graphql b/graphql_client_cli/src/graphql/introspection_query_with_is_one_of.graphql new file mode 100644 index 000000000..20faed646 --- /dev/null +++ b/graphql_client_cli/src/graphql/introspection_query_with_is_one_of.graphql @@ -0,0 +1,100 @@ +query IntrospectionQueryWithIsOneOf { + __schema { + queryType { + name + } + mutationType { + name + } + subscriptionType { + name + } + types { + ...FullTypeWithisOneOf + } + directives { + name + description + locations + args { + ...InputValue + } + } + } +} + +fragment FullTypeWithisOneOf on __Type { + kind + name + description + isOneOf + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ...TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} diff --git a/graphql_client_cli/src/graphql/introspection_query_with_specified_by.graphql b/graphql_client_cli/src/graphql/introspection_query_with_specified_by.graphql new file mode 100644 index 000000000..7c2db3b93 --- /dev/null +++ b/graphql_client_cli/src/graphql/introspection_query_with_specified_by.graphql @@ -0,0 +1,100 @@ +query IntrospectionQueryWithSpecifiedBy { + __schema { + queryType { + name + } + mutationType { + name + } + subscriptionType { + name + } + types { + ...FullTypeWithSpecifiedBy + } + directives { + name + description + locations + args { + ...InputValue + } + } + } +} + +fragment FullTypeWithSpecifiedBy on __Type { + kind + name + description + specifiedByURL + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ...TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} diff --git a/graphql_client_cli/src/graphql/introspection_schema.graphql b/graphql_client_cli/src/graphql/introspection_schema.graphql index f177de1a7..38582b0f8 100644 --- a/graphql_client_cli/src/graphql/introspection_schema.graphql +++ b/graphql_client_cli/src/graphql/introspection_schema.graphql @@ -38,7 +38,9 @@ type __Type { ofType: __Type # may be non-null for custom SCALAR, otherwise null. + # https://spec.graphql.org/draft/#sec-Scalars.Custom-Scalars specifiedByURL: String + specifiedBy: String # should be non-null for INPUT_OBJECT only isOneOf: Boolean diff --git a/graphql_client_cli/src/introspection_queries.rs b/graphql_client_cli/src/introspection_queries.rs new file mode 100644 index 000000000..6b4b94dcf --- /dev/null +++ b/graphql_client_cli/src/introspection_queries.rs @@ -0,0 +1,41 @@ +use graphql_client::GraphQLQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/introspection_schema.graphql", + query_path = "src/graphql/introspection_query.graphql", + response_derives = "Serialize", + variable_derives = "Deserialize" +)] +#[allow(dead_code)] +pub struct IntrospectionQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/introspection_schema.graphql", + query_path = "src/graphql/introspection_query_with_is_one_of.graphql", + response_derives = "Serialize", + variable_derives = "Deserialize" +)] +#[allow(dead_code)] +pub struct IntrospectionQueryWithIsOneOf; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/introspection_schema.graphql", + query_path = "src/graphql/introspection_query_with_specified_by.graphql", + response_derives = "Serialize", + variable_derives = "Deserialize" +)] +#[allow(dead_code)] +pub struct IntrospectionQueryWithSpecifiedBy; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/introspection_schema.graphql", + query_path = "src/graphql/introspection_query_with_isOneOf_specifiedByUrl.graphql", + response_derives = "Serialize", + variable_derives = "Deserialize" +)] +#[allow(dead_code)] +pub struct IntrospectionQueryWithIsOneOfSpecifiedByURL; diff --git a/graphql_client_cli/src/introspect_schema.rs b/graphql_client_cli/src/introspection_schema.rs similarity index 75% rename from graphql_client_cli/src/introspect_schema.rs rename to graphql_client_cli/src/introspection_schema.rs index d260bfd4c..137750505 100644 --- a/graphql_client_cli/src/introspect_schema.rs +++ b/graphql_client_cli/src/introspection_schema.rs @@ -1,24 +1,13 @@ use crate::error::Error; use crate::CliResult; -use graphql_client::{GraphQLQuery, QueryBody}; use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE}; -use serde_json::json; -use std::fmt; use std::path::PathBuf; use std::str::FromStr; -use clap::ValueEnum; -use introspection_query::Variables; - -#[derive(GraphQLQuery)] -#[graphql( - schema_path = "src/graphql/introspection_schema.graphql", - query_path = "src/graphql/introspection_query.graphql", - response_derives = "Serialize", - variable_derives = "Deserialize" -)] -#[allow(dead_code)] -struct IntrospectionQuery; +use crate::introspection_queries::{ + introspection_query, introspection_query_with_is_one_of, + introspection_query_with_is_one_of_specified_by_url, introspection_query_with_specified_by, +}; pub fn introspect_schema( location: &str, @@ -26,7 +15,8 @@ pub fn introspect_schema( authorization: Option, headers: Vec
, no_ssl: bool, - options: Option>, + is_one_of: bool, + specify_by_url: bool, ) -> CliResult<()> { use std::io::Write; @@ -35,8 +25,35 @@ pub fn introspect_schema( None => Box::new(std::io::stdout()), }; - let request_body: QueryBody = - IntrospectionQuery::build_query(construct_options(options)); + let mut request_body: graphql_client::QueryBody<()> = graphql_client::QueryBody { + variables: (), + query: introspection_query::QUERY, + operation_name: introspection_query::OPERATION_NAME, + }; + + if is_one_of { + request_body = graphql_client::QueryBody { + variables: (), + query: introspection_query_with_is_one_of::QUERY, + operation_name: introspection_query_with_is_one_of::OPERATION_NAME, + } + } + + if specify_by_url { + request_body = graphql_client::QueryBody { + variables: (), + query: introspection_query_with_specified_by::QUERY, + operation_name: introspection_query_with_specified_by::OPERATION_NAME, + } + } + + if is_one_of && specify_by_url { + request_body = graphql_client::QueryBody { + variables: (), + query: introspection_query_with_is_one_of_specified_by_url::QUERY, + operation_name: introspection_query_with_is_one_of_specified_by_url::OPERATION_NAME, + } + } let client = reqwest::blocking::Client::builder() .danger_accept_invalid_certs(no_ssl) @@ -129,41 +146,6 @@ impl FromStr for Header { } } -#[derive(ValueEnum, Clone, Debug, PartialEq)] -pub enum IntrospectionOptions { - /// Enable the @oneOf directive in the introspection query. - IsOneOf, - /// SpecifiedBy is is used within the type system definition language to - /// provide a scalar specification URL for specifying the behavior of custom scalar types. - SpecifiedBy, -} - -fn construct_options(options: Option>) -> introspection_query::Variables { - match options { - Some(opts) => introspection_query::Variables { - is_one_of: opts.contains(&IntrospectionOptions::IsOneOf), - }, - None => introspection_query::Variables { is_one_of: false }, - } -} - -impl FromStr for IntrospectionOptions { - type Err = String; - - fn from_str(input: &str) -> Result { - match input.to_lowercase().as_str() { - "isoneof" => Ok(IntrospectionOptions::IsOneOf), - _ => Err(format!("unknown option {:?}", input)), - } - } -} - -impl fmt::Debug for Variables { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", json!(self)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/graphql_client_cli/src/main.rs b/graphql_client_cli/src/main.rs index cb902ba82..793671202 100644 --- a/graphql_client_cli/src/main.rs +++ b/graphql_client_cli/src/main.rs @@ -1,11 +1,11 @@ mod error; mod generate; -mod introspect_schema; +mod introspection_queries; +mod introspection_schema; use clap::Parser; use env_logger::fmt::{Color, Style, StyledValue}; use error::Error; -use introspect_schema::IntrospectionOptions; use log::Level; use std::path::PathBuf; use Cli::Generate; @@ -30,15 +30,21 @@ enum Cli { /// Specify custom headers. /// --header 'X-Name: Value' #[clap(long = "header")] - headers: Vec, + headers: Vec, /// Disable ssl verification. /// Default value is false. #[clap(long = "no-ssl")] no_ssl: bool, - /// Introspection Options - /// is-one-of will include IsOneOf in the introspection query. - #[clap(long = "options", action(clap::ArgAction::Append))] - options: Option>, + /// Introspection Option: is-one-of will enable the @oneOf directive in the introspection query. + /// This is an proposed feature and is not compatible with many GraphQL servers. + /// Default value is false. + #[clap(long = "is-one-of")] + is_one_of: bool, + /// Introspection Option: specify-by-url will enable the @specifiedByURL directive in the introspection query. + /// This is an proposed feature and is not compatible with many GraphQL servers. + /// Default value is false. + #[clap(long = "specify-by-url")] + specify_by_url: bool, }, #[clap(name = "generate")] Generate { @@ -98,14 +104,16 @@ fn main() -> CliResult<()> { authorization, headers, no_ssl, - options, - } => introspect_schema::introspect_schema( + is_one_of, + specify_by_url, + } => introspection_schema::introspect_schema( &schema_location, output, authorization, headers, no_ssl, - options, + is_one_of, + specify_by_url, ), Generate { variables_derives, From de796b3e3b570c648b052d31924ac546412a0539 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 18 Dec 2024 08:53:12 +1100 Subject: [PATCH 4/4] fix prettier formatting, removes excess newline at end of introspection_schema.graphql, remove three clippy errors due to needless_lifetimes --- graphql_client_cli/src/graphql/introspection_schema.graphql | 2 +- graphql_client_codegen/src/codegen/selection.rs | 4 ++-- graphql_client_codegen/src/generated_module.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/graphql_client_cli/src/graphql/introspection_schema.graphql b/graphql_client_cli/src/graphql/introspection_schema.graphql index 38582b0f8..c82bded4d 100644 --- a/graphql_client_cli/src/graphql/introspection_schema.graphql +++ b/graphql_client_cli/src/graphql/introspection_schema.graphql @@ -106,4 +106,4 @@ enum __DirectiveLocation { ENUM_VALUE INPUT_OBJECT INPUT_FIELD_DEFINITION -} \ No newline at end of file +} diff --git a/graphql_client_codegen/src/codegen/selection.rs b/graphql_client_codegen/src/codegen/selection.rs index 5dd72e287..2ad054d95 100644 --- a/graphql_client_codegen/src/codegen/selection.rs +++ b/graphql_client_codegen/src/codegen/selection.rs @@ -391,7 +391,7 @@ struct ExpandedField<'a> { boxed: bool, } -impl<'a> ExpandedField<'a> { +impl ExpandedField<'_> { fn render(&self, options: &GraphQLClientCodegenOptions) -> Option { let ident = Ident::new(&self.rust_name, Span::call_site()); let qualified_type = decorate_type( @@ -457,7 +457,7 @@ struct ExpandedVariant<'a> { is_default_variant: bool, } -impl<'a> ExpandedVariant<'a> { +impl ExpandedVariant<'_> { fn render(&self) -> TokenStream { let name_ident = Ident::new(&self.name, Span::call_site()); let optional_type_ident = self.variant_type.as_ref().map(|variant_type| { diff --git a/graphql_client_codegen/src/generated_module.rs b/graphql_client_codegen/src/generated_module.rs index 4148d7e50..b225d001a 100644 --- a/graphql_client_codegen/src/generated_module.rs +++ b/graphql_client_codegen/src/generated_module.rs @@ -32,7 +32,7 @@ pub(crate) struct GeneratedModule<'a> { pub options: &'a crate::GraphQLClientCodegenOptions, } -impl<'a> GeneratedModule<'a> { +impl GeneratedModule<'_> { /// Generate the items for the variables and the response that will go inside the module. fn build_impls(&self) -> Result { Ok(crate::codegen::response_for_query(