Add Compile Time Check For "Invalid" Names ()

This commit is contained in:
piperRyan 2018-05-23 01:25:20 -06:00 committed by Christian Legnitto
parent 69db4c247b
commit 9080448da2
8 changed files with 78 additions and 18 deletions

View file

@ -7,6 +7,11 @@
[#151](https://github.com/graphql-rust/juniper/pull/151) [#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, * Large integers (> signed 32bit) are now deserialized as floats. Previously,
they produced the "integer out of range" error. For languages that do not they produced the "integer out of range" error. For languages that do not
have distinction between integer and floating point types (including have distinction between integer and floating point types (including
@ -14,5 +19,4 @@
fractional part could not be decoded (because they are represented without fractional part could not be decoded (because they are represented without
a decimal part `.0`). a decimal part `.0`).
[#179](https://github.com/graphql-rust/juniper/pull/179) [#179](https://github.com/graphql-rust/juniper/pull/179)

View file

@ -16,6 +16,8 @@ proc-macro = true
[dependencies] [dependencies]
syn = { version = "0.13.*", features = ["full", "extra-traits"] } syn = { version = "0.13.*", features = ["full", "extra-traits"] }
quote = "0.5.*" quote = "0.5.*"
regex = "0.2.10"
lazy_static = "1.0.0"
[badges] [badges]
travis-ci = { repository = "graphql-rust/juniper" } travis-ci = { repository = "graphql-rust/juniper" }

View file

@ -32,8 +32,13 @@ impl EnumAttrs {
if let Some(items) = get_graphl_attr(&input.attrs) { if let Some(items) = get_graphl_attr(&input.attrs) {
for item in items { for item in items {
if let Some(val) = keyed_item_value(&item, "name", true) { if let Some(val) = keyed_item_value(&item, "name", true) {
res.name = Some(val); if is_valid_name(&*val) {
continue; 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) { if let Some(val) = keyed_item_value(&item, "description", true) {
res.description = Some(val); res.description = Some(val);
@ -73,8 +78,13 @@ impl EnumVariantAttrs {
if let Some(items) = get_graphl_attr(&variant.attrs) { if let Some(items) = get_graphl_attr(&variant.attrs) {
for item in items { for item in items {
if let Some(val) = keyed_item_value(&item, "name", true) { if let Some(val) = keyed_item_value(&item, "name", true) {
res.name = Some(val); if is_valid_name(&*val) {
continue; 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) { if let Some(val) = keyed_item_value(&item, "description", true) {
res.description = Some(val); res.description = Some(val);

View file

@ -29,8 +29,13 @@ impl ObjAttrs {
if let Some(items) = get_graphl_attr(&input.attrs) { if let Some(items) = get_graphl_attr(&input.attrs) {
for item in items { for item in items {
if let Some(val) = keyed_item_value(&item, "name", true) { if let Some(val) = keyed_item_value(&item, "name", true) {
res.name = Some(val); if is_valid_name(&*val) {
continue; 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) { if let Some(val) = keyed_item_value(&item, "description", true) {
res.description = Some(val); res.description = Some(val);
@ -71,8 +76,13 @@ impl ObjFieldAttrs {
if let Some(items) = get_graphl_attr(&variant.attrs) { if let Some(items) = get_graphl_attr(&variant.attrs) {
for item in items { for item in items {
if let Some(val) = keyed_item_value(&item, "name", true) { if let Some(val) = keyed_item_value(&item, "name", true) {
res.name = Some(val); if is_valid_name(&*val) {
continue; 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) { if let Some(val) = keyed_item_value(&item, "description", true) {
res.description = Some(val); res.description = Some(val);

View file

@ -23,8 +23,13 @@ impl ObjAttrs {
if let Some(items) = get_graphl_attr(&input.attrs) { if let Some(items) = get_graphl_attr(&input.attrs) {
for item in items { for item in items {
if let Some(val) = keyed_item_value(&item, "name", true) { if let Some(val) = keyed_item_value(&item, "name", true) {
res.name = Some(val); if is_valid_name(&*val) {
continue; 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) { if let Some(val) = keyed_item_value(&item, "description", true) {
res.description = Some(val); res.description = Some(val);
@ -55,8 +60,13 @@ impl ObjFieldAttrs {
if let Some(items) = get_graphl_attr(&variant.attrs) { if let Some(items) = get_graphl_attr(&variant.attrs) {
for item in items { for item in items {
if let Some(val) = keyed_item_value(&item, "name", true) { if let Some(val) = keyed_item_value(&item, "name", true) {
res.name = Some(val); if is_valid_name(&*val) {
continue; 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) { if let Some(val) = keyed_item_value(&item, "description", true) {
res.description = Some(val); res.description = Some(val);

View file

@ -10,6 +10,9 @@ extern crate proc_macro;
#[macro_use] #[macro_use]
extern crate quote; extern crate quote;
extern crate syn; extern crate syn;
#[macro_use]
extern crate lazy_static;
extern crate regex;
mod util; mod util;
mod derive_enum; mod derive_enum;

View file

@ -4,6 +4,7 @@ use syn::{
NestedMeta, NestedMeta,
Lit, Lit,
}; };
use regex::Regex;
// Get the nested items of a a #[graphql(...)] attribute. // Get the nested items of a a #[graphql(...)] attribute.
pub fn get_graphl_attr(attrs: &Vec<Attribute>) -> Option<Vec<NestedMeta>> { 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("someINpuT"), "SOME_INPU_T");
assert_eq!(to_upper_snake_case("some_INpuT"), "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);
}

View file

@ -8,8 +8,7 @@ use juniper::{self, FromInputValue, GraphQLType, InputValue, ToInputValue};
#[graphql(name = "Some", description = "enum descr")] #[graphql(name = "Some", description = "enum descr")]
enum SomeEnum { enum SomeEnum {
Regular, Regular,
#[graphql(name = "FULL", description = "field descr", deprecated = "depr")] Full,
#[graphql(name = "full", description = "field descr", deprecated = "depr")] Full,
} }
#[test] #[test]
@ -37,10 +36,10 @@ fn test_derived_enum() {
// Test FULL variant. // Test FULL variant.
assert_eq!( assert_eq!(
SomeEnum::Full.to_input_value(), SomeEnum::Full.to_input_value(),
InputValue::String("full".into()) InputValue::String("FULL".into())
); );
assert_eq!( assert_eq!(
FromInputValue::from_input_value(&InputValue::String("full".into())), FromInputValue::from_input_value(&InputValue::String("FULL".into())),
Some(SomeEnum::Full) Some(SomeEnum::Full)
); );
} }