From c964fd805c8c9909d662a722532558cf7fad69a3 Mon Sep 17 00:00:00 2001 From: Igor Pashev Date: Tue, 13 Oct 2020 18:34:36 +0200 Subject: [PATCH] Allow disabling case conversion (#765) --- .../juniper_tests/src/codegen/derive_enum.rs | 13 ++ .../src/codegen/derive_input_object.rs | 21 +++ .../src/codegen/derive_object.rs | 123 ++++++++++++++++++ juniper_codegen/src/derive_enum.rs | 9 +- juniper_codegen/src/derive_input_object.rs | 7 +- juniper_codegen/src/derive_object.rs | 9 +- juniper_codegen/src/impl_object.rs | 18 ++- juniper_codegen/src/util/mod.rs | 45 +++++++ 8 files changed, 236 insertions(+), 9 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/derive_enum.rs b/integration_tests/juniper_tests/src/codegen/derive_enum.rs index 88a54a08..f4adbc69 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_enum.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_enum.rs @@ -16,6 +16,13 @@ enum SomeEnum { Full, } +#[derive(juniper::GraphQLEnum, Debug, PartialEq)] +#[graphql(rename = "none")] +enum NoRenameEnum { + OneVariant, + AnotherVariant, +} + /// Enum doc. #[derive(juniper::GraphQLEnum)] enum DocEnum { @@ -64,6 +71,12 @@ fn test_derived_enum() { assert_eq!(meta.name(), Some("Some")); assert_eq!(meta.description(), Some(&"enum descr".to_string())); + // Test no rename variant. + assert_eq!( + <_ as ToInputValue>::to_input_value(&NoRenameEnum::AnotherVariant), + InputValue::scalar("AnotherVariant") + ); + // Test Regular variant. assert_eq!( <_ as ToInputValue>::to_input_value(&SomeEnum::Regular), diff --git a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs index d018ad8b..d6fa4ca5 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_input_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_input_object.rs @@ -20,6 +20,12 @@ struct Input { other: Option, } +#[derive(GraphQLInputObject, Debug, PartialEq)] +#[graphql(rename = "none")] +struct NoRenameInput { + regular_field: String, +} + /// Object comment. #[derive(GraphQLInputObject, Debug, PartialEq)] struct DocComment { @@ -147,6 +153,21 @@ fn test_derived_input_object() { other: Some(true), } ); + + // Test disable renaming + + let input: InputValue = ::serde_json::from_value(serde_json::json!({ + "regular_field": "hello", + })) + .unwrap(); + + let output: NoRenameInput = FromInputValue::from_input_value(&input).unwrap(); + assert_eq!( + output, + NoRenameInput { + regular_field: "hello".into(), + } + ); } #[test] diff --git a/integration_tests/juniper_tests/src/codegen/derive_object.rs b/integration_tests/juniper_tests/src/codegen/derive_object.rs index b7dd51ca..8a387b80 100644 --- a/integration_tests/juniper_tests/src/codegen/derive_object.rs +++ b/integration_tests/juniper_tests/src/codegen/derive_object.rs @@ -32,6 +32,7 @@ struct Nested { } struct Query; +struct NoRenameQuery; /// Object comment. #[derive(GraphQLObject, Debug, PartialEq)] @@ -72,6 +73,12 @@ struct SkippedFieldObj { skipped: i32, } +#[derive(GraphQLObject, Debug, PartialEq)] +#[graphql(rename = "none")] +struct NoRenameObj { + one_field: bool, + another_field: i32, +} struct Context; impl juniper::Context for Context {} @@ -123,6 +130,30 @@ impl Query { skipped: 42, } } + + fn no_rename_obj() -> NoRenameObj { + NoRenameObj { + one_field: true, + another_field: 146, + } + } +} + +#[juniper::graphql_object(rename = "none")] +impl NoRenameQuery { + fn obj() -> Obj { + Obj { + regular_field: false, + c: 22, + } + } + + fn no_rename_obj() -> NoRenameObj { + NoRenameObj { + one_field: true, + another_field: 146, + } + } } #[tokio::test] @@ -173,6 +204,98 @@ async fn test_doc_comment_override() { .await; } +#[tokio::test] +async fn test_no_rename_root() { + let doc = r#" + { + no_rename_obj { + one_field + another_field + } + + obj { + regularField + } + }"#; + + let schema = RootNode::new( + NoRenameQuery, + EmptyMutation::<()>::new(), + EmptySubscription::<()>::new(), + ); + + assert_eq!( + execute(doc, None, &schema, &Variables::new(), &()).await, + Ok(( + Value::object( + vec![ + ( + "no_rename_obj", + Value::object( + vec![ + ("one_field", Value::scalar(true)), + ("another_field", Value::scalar(146)), + ] + .into_iter() + .collect(), + ), + ), + ( + "obj", + Value::object( + vec![("regularField", Value::scalar(false)),] + .into_iter() + .collect(), + ), + ) + ] + .into_iter() + .collect() + ), + vec![] + )) + ); +} + +#[tokio::test] +async fn test_no_rename_obj() { + let doc = r#" + { + noRenameObj { + one_field + another_field + } + }"#; + + let schema = RootNode::new( + Query, + EmptyMutation::<()>::new(), + EmptySubscription::<()>::new(), + ); + + assert_eq!( + execute(doc, None, &schema, &Variables::new(), &()).await, + Ok(( + Value::object( + vec![( + "noRenameObj", + Value::object( + vec![ + ("one_field", Value::scalar(true)), + ("another_field", Value::scalar(146)), + ] + .into_iter() + .collect(), + ), + )] + .into_iter() + .collect() + ), + vec![] + )) + ); +} + #[tokio::test] async fn test_derived_object() { assert_eq!( diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index bf6b3e7a..458bc43e 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -4,7 +4,7 @@ use syn::{ext::IdentExt, spanned::Spanned, Data, Fields}; use crate::{ result::{GraphQLScope, UnsupportedAttribute}, - util::{self, span_container::SpanContainer}, + util::{self, span_container::SpanContainer, RenameRule}, }; pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result { @@ -48,7 +48,12 @@ pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result syn::Res let field_ident = field.ident.as_ref().unwrap(); let name = match field_attrs.name { Some(ref name) => name.to_string(), - None => crate::util::to_camel_case(&field_ident.unraw().to_string()), + None => attrs + .rename + .unwrap_or(RenameRule::CamelCase) + .apply(&field_ident.unraw().to_string()), }; if let Some(span) = field_attrs.skip { diff --git a/juniper_codegen/src/derive_object.rs b/juniper_codegen/src/derive_object.rs index de2ac2dc..6d0af8fa 100644 --- a/juniper_codegen/src/derive_object.rs +++ b/juniper_codegen/src/derive_object.rs @@ -1,6 +1,6 @@ use crate::{ result::{GraphQLScope, UnsupportedAttribute}, - util::{self, span_container::SpanContainer}, + util::{self, span_container::SpanContainer, RenameRule}, }; use proc_macro2::TokenStream; use quote::quote; @@ -50,7 +50,12 @@ pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::R .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| util::to_camel_case(&field_name.unraw().to_string())); + .unwrap_or_else(|| { + attrs + .rename + .unwrap_or(RenameRule::CamelCase) + .apply(&field_name.unraw().to_string()) + }); if name.starts_with("__") { error.no_double_underscore(if let Some(name) = field_attrs.name { diff --git a/juniper_codegen/src/impl_object.rs b/juniper_codegen/src/impl_object.rs index 66a5879e..b594df5e 100644 --- a/juniper_codegen/src/impl_object.rs +++ b/juniper_codegen/src/impl_object.rs @@ -2,7 +2,7 @@ use crate::{ result::{GraphQLScope, UnsupportedAttribute}, - util::{self, span_container::SpanContainer}, + util::{self, span_container::SpanContainer, RenameRule}, }; use proc_macro2::TokenStream; use quote::quote; @@ -44,6 +44,8 @@ fn create( .map(SpanContainer::into_inner) .unwrap_or_else(|| _impl.type_ident.unraw().to_string()); + let top_attrs = &_impl.attrs; + let fields = _impl .methods .iter() @@ -78,7 +80,12 @@ fn create( let final_name = attrs .argument(&arg_name) .and_then(|attrs| attrs.rename.clone().map(|ident| ident.value())) - .unwrap_or_else(|| util::to_camel_case(&arg_name)); + .unwrap_or_else(|| { + top_attrs + .rename + .unwrap_or(RenameRule::CamelCase) + .apply(&arg_name) + }); let expect_text = format!( "Internal error: missing argument {} - validation must have failed", @@ -137,7 +144,12 @@ fn create( .name .clone() .map(SpanContainer::into_inner) - .unwrap_or_else(|| util::to_camel_case(&ident.unraw().to_string())); + .unwrap_or_else(|| { + top_attrs + .rename + .unwrap_or(RenameRule::CamelCase) + .apply(&ident.unraw().to_string()) + }); if name.starts_with("__") { error.no_double_underscore(if let Some(name) = attrs.name { diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index e597eb26..854e067f 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -5,6 +5,7 @@ pub mod parse_impl; pub mod span_container; use std::collections::HashMap; +use std::str::FromStr; use proc_macro2::{Span, TokenStream}; use proc_macro_error::abort; @@ -295,6 +296,40 @@ pub fn is_valid_name(field_name: &str) -> bool { chars.all(|c| c.is_ascii_alphanumeric() || c == '_') } +/// The different possible ways to change case of fields in a struct, or variants in an enum. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum RenameRule { + /// Don't apply a default rename rule. + None, + /// Rename to "camelCase" style. + CamelCase, + /// Rename to "SCREAMING_SNAKE_CASE" style + ScreamingSnakeCase, +} + +impl RenameRule { + pub fn apply(&self, field: &str) -> String { + match self { + Self::None => field.to_owned(), + Self::CamelCase => to_camel_case(field), + Self::ScreamingSnakeCase => to_upper_snake_case(field), + } + } +} + +impl FromStr for RenameRule { + type Err = (); + + fn from_str(rule: &str) -> Result { + match rule { + "none" => Ok(Self::None), + "camelCase" => Ok(Self::CamelCase), + "SCREAMING_SNAKE_CASE" => Ok(Self::ScreamingSnakeCase), + _ => Err(()), + } + } +} + #[derive(Default, Debug)] pub struct ObjectAttributes { pub name: Option>, @@ -304,6 +339,7 @@ pub struct ObjectAttributes { pub interfaces: Vec>, pub no_async: Option>, pub is_internal: bool, + pub rename: Option, } impl Parse for ObjectAttributes { @@ -365,6 +401,15 @@ impl Parse for ObjectAttributes { "internal" => { output.is_internal = true; } + "rename" => { + input.parse::()?; + let val = input.parse::()?; + if let Ok(rename) = RenameRule::from_str(&val.value()) { + output.rename = Some(rename); + } else { + return Err(syn::Error::new(val.span(), "unknown rename rule")); + } + } _ => { return Err(syn::Error::new(ident.span(), "unknown attribute")); }