Allow disabling case conversion (#765)
This commit is contained in:
parent
68210f54ca
commit
c964fd805c
8 changed files with 236 additions and 9 deletions
|
@ -16,6 +16,13 @@ enum SomeEnum {
|
||||||
Full,
|
Full,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(juniper::GraphQLEnum, Debug, PartialEq)]
|
||||||
|
#[graphql(rename = "none")]
|
||||||
|
enum NoRenameEnum {
|
||||||
|
OneVariant,
|
||||||
|
AnotherVariant,
|
||||||
|
}
|
||||||
|
|
||||||
/// Enum doc.
|
/// Enum doc.
|
||||||
#[derive(juniper::GraphQLEnum)]
|
#[derive(juniper::GraphQLEnum)]
|
||||||
enum DocEnum {
|
enum DocEnum {
|
||||||
|
@ -64,6 +71,12 @@ fn test_derived_enum() {
|
||||||
assert_eq!(meta.name(), Some("Some"));
|
assert_eq!(meta.name(), Some("Some"));
|
||||||
assert_eq!(meta.description(), Some(&"enum descr".to_string()));
|
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.
|
// Test Regular variant.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<_ as ToInputValue>::to_input_value(&SomeEnum::Regular),
|
<_ as ToInputValue>::to_input_value(&SomeEnum::Regular),
|
||||||
|
|
|
@ -20,6 +20,12 @@ struct Input {
|
||||||
other: Option<bool>,
|
other: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
||||||
|
#[graphql(rename = "none")]
|
||||||
|
struct NoRenameInput {
|
||||||
|
regular_field: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Object comment.
|
/// Object comment.
|
||||||
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
#[derive(GraphQLInputObject, Debug, PartialEq)]
|
||||||
struct DocComment {
|
struct DocComment {
|
||||||
|
@ -147,6 +153,21 @@ fn test_derived_input_object() {
|
||||||
other: Some(true),
|
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]
|
#[test]
|
||||||
|
|
|
@ -32,6 +32,7 @@ struct Nested {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
struct NoRenameQuery;
|
||||||
|
|
||||||
/// Object comment.
|
/// Object comment.
|
||||||
#[derive(GraphQLObject, Debug, PartialEq)]
|
#[derive(GraphQLObject, Debug, PartialEq)]
|
||||||
|
@ -72,6 +73,12 @@ struct SkippedFieldObj {
|
||||||
skipped: i32,
|
skipped: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject, Debug, PartialEq)]
|
||||||
|
#[graphql(rename = "none")]
|
||||||
|
struct NoRenameObj {
|
||||||
|
one_field: bool,
|
||||||
|
another_field: i32,
|
||||||
|
}
|
||||||
struct Context;
|
struct Context;
|
||||||
impl juniper::Context for Context {}
|
impl juniper::Context for Context {}
|
||||||
|
|
||||||
|
@ -123,6 +130,30 @@ impl Query {
|
||||||
skipped: 42,
|
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]
|
#[tokio::test]
|
||||||
|
@ -173,6 +204,98 @@ async fn test_doc_comment_override() {
|
||||||
.await;
|
.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]
|
#[tokio::test]
|
||||||
async fn test_derived_object() {
|
async fn test_derived_object() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -4,7 +4,7 @@ use syn::{ext::IdentExt, spanned::Spanned, Data, Fields};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
result::{GraphQLScope, UnsupportedAttribute},
|
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<TokenStream> {
|
pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result<TokenStream> {
|
||||||
|
@ -48,7 +48,12 @@ pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result<Toke
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.map(SpanContainer::into_inner)
|
.map(SpanContainer::into_inner)
|
||||||
.unwrap_or_else(|| util::to_upper_snake_case(&field_name.unraw().to_string()));
|
.unwrap_or_else(|| {
|
||||||
|
attrs
|
||||||
|
.rename
|
||||||
|
.unwrap_or(RenameRule::ScreamingSnakeCase)
|
||||||
|
.apply(&field_name.unraw().to_string())
|
||||||
|
});
|
||||||
|
|
||||||
let resolver_code = quote!( #ident::#field_name );
|
let resolver_code = quote!( #ident::#field_name );
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#![allow(clippy::match_wild_err_arm)]
|
#![allow(clippy::match_wild_err_arm)]
|
||||||
use crate::{
|
use crate::{
|
||||||
result::{GraphQLScope, UnsupportedAttribute},
|
result::{GraphQLScope, UnsupportedAttribute},
|
||||||
util::{self, span_container::SpanContainer},
|
util::{self, span_container::SpanContainer, RenameRule},
|
||||||
};
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
|
@ -50,7 +50,10 @@ pub fn impl_input_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Res
|
||||||
let field_ident = field.ident.as_ref().unwrap();
|
let field_ident = field.ident.as_ref().unwrap();
|
||||||
let name = match field_attrs.name {
|
let name = match field_attrs.name {
|
||||||
Some(ref name) => name.to_string(),
|
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 {
|
if let Some(span) = field_attrs.skip {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
result::{GraphQLScope, UnsupportedAttribute},
|
result::{GraphQLScope, UnsupportedAttribute},
|
||||||
util::{self, span_container::SpanContainer},
|
util::{self, span_container::SpanContainer, RenameRule},
|
||||||
};
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
@ -50,7 +50,12 @@ pub fn build_derive_object(ast: syn::DeriveInput, error: GraphQLScope) -> syn::R
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.map(SpanContainer::into_inner)
|
.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("__") {
|
if name.starts_with("__") {
|
||||||
error.no_double_underscore(if let Some(name) = field_attrs.name {
|
error.no_double_underscore(if let Some(name) = field_attrs.name {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
result::{GraphQLScope, UnsupportedAttribute},
|
result::{GraphQLScope, UnsupportedAttribute},
|
||||||
util::{self, span_container::SpanContainer},
|
util::{self, span_container::SpanContainer, RenameRule},
|
||||||
};
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
@ -44,6 +44,8 @@ fn create(
|
||||||
.map(SpanContainer::into_inner)
|
.map(SpanContainer::into_inner)
|
||||||
.unwrap_or_else(|| _impl.type_ident.unraw().to_string());
|
.unwrap_or_else(|| _impl.type_ident.unraw().to_string());
|
||||||
|
|
||||||
|
let top_attrs = &_impl.attrs;
|
||||||
|
|
||||||
let fields = _impl
|
let fields = _impl
|
||||||
.methods
|
.methods
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -78,7 +80,12 @@ fn create(
|
||||||
let final_name = attrs
|
let final_name = attrs
|
||||||
.argument(&arg_name)
|
.argument(&arg_name)
|
||||||
.and_then(|attrs| attrs.rename.clone().map(|ident| ident.value()))
|
.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!(
|
let expect_text = format!(
|
||||||
"Internal error: missing argument {} - validation must have failed",
|
"Internal error: missing argument {} - validation must have failed",
|
||||||
|
@ -137,7 +144,12 @@ fn create(
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.map(SpanContainer::into_inner)
|
.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("__") {
|
if name.starts_with("__") {
|
||||||
error.no_double_underscore(if let Some(name) = attrs.name {
|
error.no_double_underscore(if let Some(name) = attrs.name {
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub mod parse_impl;
|
||||||
pub mod span_container;
|
pub mod span_container;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use proc_macro_error::abort;
|
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 == '_')
|
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<Self, Self::Err> {
|
||||||
|
match rule {
|
||||||
|
"none" => Ok(Self::None),
|
||||||
|
"camelCase" => Ok(Self::CamelCase),
|
||||||
|
"SCREAMING_SNAKE_CASE" => Ok(Self::ScreamingSnakeCase),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ObjectAttributes {
|
pub struct ObjectAttributes {
|
||||||
pub name: Option<SpanContainer<String>>,
|
pub name: Option<SpanContainer<String>>,
|
||||||
|
@ -304,6 +339,7 @@ pub struct ObjectAttributes {
|
||||||
pub interfaces: Vec<SpanContainer<syn::Type>>,
|
pub interfaces: Vec<SpanContainer<syn::Type>>,
|
||||||
pub no_async: Option<SpanContainer<()>>,
|
pub no_async: Option<SpanContainer<()>>,
|
||||||
pub is_internal: bool,
|
pub is_internal: bool,
|
||||||
|
pub rename: Option<RenameRule>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for ObjectAttributes {
|
impl Parse for ObjectAttributes {
|
||||||
|
@ -365,6 +401,15 @@ impl Parse for ObjectAttributes {
|
||||||
"internal" => {
|
"internal" => {
|
||||||
output.is_internal = true;
|
output.is_internal = true;
|
||||||
}
|
}
|
||||||
|
"rename" => {
|
||||||
|
input.parse::<syn::Token![=]>()?;
|
||||||
|
let val = input.parse::<syn::LitStr>()?;
|
||||||
|
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"));
|
return Err(syn::Error::new(ident.span(), "unknown attribute"));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue