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,
|
||||
}
|
||||
|
||||
#[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),
|
||||
|
|
|
@ -20,6 +20,12 @@ struct Input {
|
|||
other: Option<bool>,
|
||||
}
|
||||
|
||||
#[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]
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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<TokenStream> {
|
||||
|
@ -48,7 +48,12 @@ pub fn impl_enum(ast: syn::DeriveInput, error: GraphQLScope) -> syn::Result<Toke
|
|||
.name
|
||||
.clone()
|
||||
.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 );
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![allow(clippy::match_wild_err_arm)]
|
||||
use crate::{
|
||||
result::{GraphQLScope, UnsupportedAttribute},
|
||||
util::{self, span_container::SpanContainer},
|
||||
util::{self, span_container::SpanContainer, RenameRule},
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
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 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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<Self, Self::Err> {
|
||||
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<SpanContainer<String>>,
|
||||
|
@ -304,6 +339,7 @@ pub struct ObjectAttributes {
|
|||
pub interfaces: Vec<SpanContainer<syn::Type>>,
|
||||
pub no_async: Option<SpanContainer<()>>,
|
||||
pub is_internal: bool,
|
||||
pub rename: Option<RenameRule>,
|
||||
}
|
||||
|
||||
impl Parse for ObjectAttributes {
|
||||
|
@ -365,6 +401,15 @@ impl Parse for ObjectAttributes {
|
|||
"internal" => {
|
||||
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"));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue