From 9080448da23a991568062541cb3250ee7d261a42 Mon Sep 17 00:00:00 2001 From: piperRyan <35411044+piperRyan@users.noreply.github.com> Date: Wed, 23 May 2018 01:25:20 -0600 Subject: [PATCH] Add Compile Time Check For "Invalid" Names (#170) --- changelog/master.md | 8 ++++++-- juniper_codegen/Cargo.toml | 2 ++ juniper_codegen/src/derive_enum.rs | 18 ++++++++++++++---- juniper_codegen/src/derive_input_object.rs | 18 ++++++++++++++---- juniper_codegen/src/derive_object.rs | 18 ++++++++++++++---- juniper_codegen/src/lib.rs | 3 +++ juniper_codegen/src/util.rs | 22 ++++++++++++++++++++++ juniper_tests/src/codegen/derive_enum.rs | 7 +++---- 8 files changed, 78 insertions(+), 18 deletions(-) diff --git a/changelog/master.md b/changelog/master.md index 5aa87418..d64d5c74 100644 --- a/changelog/master.md +++ b/changelog/master.md @@ -7,6 +7,11 @@ [#151](https://github.com/graphql-rust/juniper/pull/151) +* The `GraphQLObject`, `GraphQLInputObject`, and `GraphQLEnum` custom derives will reject + invalid [names](http://facebook.github.io/graphql/October2016/#Name) at compile time. + + [#170](https://github.com/graphql-rust/juniper/pull/170) + * Large integers (> signed 32bit) are now deserialized as floats. Previously, they produced the "integer out of range" error. For languages that do not have distinction between integer and floating point types (including @@ -14,5 +19,4 @@ fractional part could not be decoded (because they are represented without a decimal part `.0`). - [#179](https://github.com/graphql-rust/juniper/pull/179) - + [#179](https://github.com/graphql-rust/juniper/pull/179) \ No newline at end of file diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 13dbd08d..0695bc8f 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -16,6 +16,8 @@ proc-macro = true [dependencies] syn = { version = "0.13.*", features = ["full", "extra-traits"] } quote = "0.5.*" +regex = "0.2.10" +lazy_static = "1.0.0" [badges] travis-ci = { repository = "graphql-rust/juniper" } diff --git a/juniper_codegen/src/derive_enum.rs b/juniper_codegen/src/derive_enum.rs index 83b9bf78..90ad866b 100644 --- a/juniper_codegen/src/derive_enum.rs +++ b/juniper_codegen/src/derive_enum.rs @@ -32,8 +32,13 @@ impl EnumAttrs { if let Some(items) = get_graphl_attr(&input.attrs) { for item in items { if let Some(val) = keyed_item_value(&item, "name", true) { - res.name = Some(val); - continue; + if is_valid_name(&*val) { + res.name = Some(val); + continue; + } else { + panic!("Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", + &*val); + } } if let Some(val) = keyed_item_value(&item, "description", true) { res.description = Some(val); @@ -73,8 +78,13 @@ impl EnumVariantAttrs { if let Some(items) = get_graphl_attr(&variant.attrs) { for item in items { if let Some(val) = keyed_item_value(&item, "name", true) { - res.name = Some(val); - continue; + if is_valid_name(&*val) { + res.name = Some(val); + continue; + } else { + panic!("Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", + &*val); + } } if let Some(val) = keyed_item_value(&item, "description", true) { res.description = Some(val); diff --git a/juniper_codegen/src/derive_input_object.rs b/juniper_codegen/src/derive_input_object.rs index f645a3ec..c0f30eed 100644 --- a/juniper_codegen/src/derive_input_object.rs +++ b/juniper_codegen/src/derive_input_object.rs @@ -29,8 +29,13 @@ impl ObjAttrs { if let Some(items) = get_graphl_attr(&input.attrs) { for item in items { if let Some(val) = keyed_item_value(&item, "name", true) { - res.name = Some(val); - continue; + if is_valid_name(&*val) { + res.name = Some(val); + continue; + } else { + panic!("Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", + &*val); + } } if let Some(val) = keyed_item_value(&item, "description", true) { res.description = Some(val); @@ -71,8 +76,13 @@ impl ObjFieldAttrs { if let Some(items) = get_graphl_attr(&variant.attrs) { for item in items { if let Some(val) = keyed_item_value(&item, "name", true) { - res.name = Some(val); - continue; + if is_valid_name(&*val) { + res.name = Some(val); + continue; + } else { + panic!("Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", + &*val); + } } if let Some(val) = keyed_item_value(&item, "description", true) { res.description = Some(val); diff --git a/juniper_codegen/src/derive_object.rs b/juniper_codegen/src/derive_object.rs index 67133201..78417b60 100644 --- a/juniper_codegen/src/derive_object.rs +++ b/juniper_codegen/src/derive_object.rs @@ -23,8 +23,13 @@ impl ObjAttrs { if let Some(items) = get_graphl_attr(&input.attrs) { for item in items { if let Some(val) = keyed_item_value(&item, "name", true) { - res.name = Some(val); - continue; + if is_valid_name(&*val) { + res.name = Some(val); + continue; + } else { + panic!("Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", + &*val); + } } if let Some(val) = keyed_item_value(&item, "description", true) { res.description = Some(val); @@ -55,8 +60,13 @@ impl ObjFieldAttrs { if let Some(items) = get_graphl_attr(&variant.attrs) { for item in items { if let Some(val) = keyed_item_value(&item, "name", true) { - res.name = Some(val); - continue; + if is_valid_name(&*val) { + res.name = Some(val); + continue; + } else { + panic!("Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but \"{}\" does not", + &*val); + } } if let Some(val) = keyed_item_value(&item, "description", true) { res.description = Some(val); diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 55d7045f..97b6ddfd 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -10,6 +10,9 @@ extern crate proc_macro; #[macro_use] extern crate quote; extern crate syn; +#[macro_use] +extern crate lazy_static; +extern crate regex; mod util; mod derive_enum; diff --git a/juniper_codegen/src/util.rs b/juniper_codegen/src/util.rs index ac1afefd..21f4fc66 100644 --- a/juniper_codegen/src/util.rs +++ b/juniper_codegen/src/util.rs @@ -4,6 +4,7 @@ use syn::{ NestedMeta, Lit, }; +use regex::Regex; // Get the nested items of a a #[graphql(...)] attribute. pub fn get_graphl_attr(attrs: &Vec<Attribute>) -> Option<Vec<NestedMeta>> { @@ -111,3 +112,24 @@ fn test_to_upper_snake_case() { assert_eq!(to_upper_snake_case("someINpuT"), "SOME_INPU_T"); assert_eq!(to_upper_snake_case("some_INpuT"), "SOME_INPU_T"); } + +#[doc(hidden)] +pub fn is_valid_name(field_name: &str) -> bool { + lazy_static!{ + static ref CAMELCASE: Regex = Regex::new("^[_A-Za-z][_0-9A-Za-z]*$").unwrap(); + } + CAMELCASE.is_match(field_name) +} + +#[test] +fn test_is_valid_name(){ + assert_eq!(is_valid_name("yesItIs"), true); + assert_eq!(is_valid_name("NoitIsnt"), true); + assert_eq!(is_valid_name("iso6301"), true); + assert_eq!(is_valid_name("thisIsATest"), true); + assert_eq!(is_valid_name("i6Op"), true); + assert_eq!(is_valid_name("i!"), false); + assert_eq!(is_valid_name(""), false); + assert_eq!(is_valid_name("aTest"), true); + assert_eq!(is_valid_name("__Atest90"), true); +} diff --git a/juniper_tests/src/codegen/derive_enum.rs b/juniper_tests/src/codegen/derive_enum.rs index 6277ce09..2e1ea626 100644 --- a/juniper_tests/src/codegen/derive_enum.rs +++ b/juniper_tests/src/codegen/derive_enum.rs @@ -8,8 +8,7 @@ use juniper::{self, FromInputValue, GraphQLType, InputValue, ToInputValue}; #[graphql(name = "Some", description = "enum descr")] enum SomeEnum { Regular, - - #[graphql(name = "full", description = "field descr", deprecated = "depr")] Full, + #[graphql(name = "FULL", description = "field descr", deprecated = "depr")] Full, } #[test] @@ -37,10 +36,10 @@ fn test_derived_enum() { // Test FULL variant. assert_eq!( SomeEnum::Full.to_input_value(), - InputValue::String("full".into()) + InputValue::String("FULL".into()) ); assert_eq!( - FromInputValue::from_input_value(&InputValue::String("full".into())), + FromInputValue::from_input_value(&InputValue::String("FULL".into())), Some(SomeEnum::Full) ); }