Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions changelog/master.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# [master] yyyy-mm-dd

## Changes

* Changed serialization of `NaiveDate` when using the optional `chronos` support.

**Note:** while this is not a Rust breaking change, if you relied on the serialization format (perhaps by storing serialized data in a database or making asumptions in your client code written in another language) it could be a breaking change for your application.

[#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.
invalid [names](http://facebook.github.io/graphql/October2016/#Name) at compile time.

[#170](https://github.com/graphql-rust/juniper/pull/170)

Expand All @@ -19,4 +20,11 @@
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)

* The `GraphQLObject`, `GraphQLInputObject`, and `GraphQLEnum` custom derives
now parse doc strings and use them as descriptions. This behavior can be
overridden by using an explicit GraphQL `description` annotation such as
`#[graphql(description = "my description")]`.

[#194](https://github.com/graphql-rust/juniper/issues/194)
6 changes: 6 additions & 0 deletions juniper_codegen/src/derive_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ impl EnumAttrs {
internal: false,
};

// Check doc comments for description.
res.description = get_doc_comment(&input.attrs);

// Check attributes for name and description.
if let Some(items) = get_graphl_attr(&input.attrs) {
for item in items {
Expand Down Expand Up @@ -74,6 +77,9 @@ impl EnumVariantAttrs {
fn from_input(variant: &Variant) -> EnumVariantAttrs {
let mut res = EnumVariantAttrs::default();

// Check doc comments for description.
res.description = get_doc_comment(&variant.attrs);

// Check attributes for name and description.
if let Some(items) = get_graphl_attr(&variant.attrs) {
for item in items {
Expand Down
6 changes: 6 additions & 0 deletions juniper_codegen/src/derive_input_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ impl ObjAttrs {
fn from_input(input: &DeriveInput) -> ObjAttrs {
let mut res = ObjAttrs::default();

// Check doc comments for description.
res.description = get_doc_comment(&input.attrs);

// Check attributes for name and description.
if let Some(items) = get_graphl_attr(&input.attrs) {
for item in items {
Expand Down Expand Up @@ -72,6 +75,9 @@ impl ObjFieldAttrs {
fn from_input(variant: &Field) -> ObjFieldAttrs {
let mut res = ObjFieldAttrs::default();

// Check doc comments for description.
res.description = get_doc_comment(&variant.attrs);

// Check attributes for name and description.
if let Some(items) = get_graphl_attr(&variant.attrs) {
for item in items {
Expand Down
6 changes: 6 additions & 0 deletions juniper_codegen/src/derive_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ impl ObjAttrs {
fn from_input(input: &DeriveInput) -> ObjAttrs {
let mut res = ObjAttrs::default();

// Check doc comments for description.
res.description = get_doc_comment(&input.attrs);

// Check attributes for name and description.
if let Some(items) = get_graphl_attr(&input.attrs) {
for item in items {
Expand Down Expand Up @@ -56,6 +59,9 @@ impl ObjFieldAttrs {
fn from_input(variant: &Field) -> ObjFieldAttrs {
let mut res = ObjFieldAttrs::default();

// Check doc comments for description.
res.description = get_doc_comment(&variant.attrs);

// Check attributes for name and description.
if let Some(items) = get_graphl_attr(&variant.attrs) {
for item in items {
Expand Down
256 changes: 207 additions & 49 deletions juniper_codegen/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,70 @@
use syn::{
Attribute,
Meta,
NestedMeta,
Lit,
};
use regex::Regex;
use syn::{Attribute, Lit, Meta, MetaNameValue, NestedMeta};

// Gets doc comment.
pub fn get_doc_comment(attrs: &Vec<Attribute>) -> Option<String> {
if let Some(items) = get_doc_attr(attrs) {
if let Some(doc_strings) = get_doc_strings(&items) {
return Some(join_doc_strings(&doc_strings));
}
}
None
}

// Concatenates doc strings into one string.
fn join_doc_strings(docs: &Vec<String>) -> String {
let s: String = docs.iter()
// Trim any extra spaces.
.map(|x| x.trim().to_string())
// Convert empty comments to newlines.
.map(|x| if x == "" { "\n".to_string() } else { x.clone() })
.collect::<Vec<String>>()
.join(" ");
// Clean up spacing on empty lines.
s.replace(" \n ", "\n")
}

// Gets doc strings from doc comment attributes.
fn get_doc_strings(items: &Vec<MetaNameValue>) -> Option<Vec<String>> {
let mut docs = Vec::new();
for item in items {
if item.ident == "doc" {
match item.lit {
Lit::Str(ref strlit) => {
docs.push(strlit.value().to_string());
}
_ => panic!("doc attributes only have string literal"),
}
}
}
if !docs.is_empty() {
return Some(docs);
}
None
}

// Gets doc comment attributes.
fn get_doc_attr(attrs: &Vec<Attribute>) -> Option<Vec<MetaNameValue>> {
let mut docs = Vec::new();
for attr in attrs {
match attr.interpret_meta() {
Some(Meta::NameValue(ref nv)) if nv.ident == "doc" => docs.push(nv.clone()),
_ => {}
}
}
if !docs.is_empty() {
return Some(docs);
}
None
}

// Get the nested items of a a #[graphql(...)] attribute.
pub fn get_graphl_attr(attrs: &Vec<Attribute>) -> Option<Vec<NestedMeta>> {
for attr in attrs {
match attr.interpret_meta() {
Some(Meta::List(ref list)) if list.ident == "graphql" => {
return Some(list.nested.iter().map(|x| x.clone()).collect());
},
}
_ => {}
}
}
Expand All @@ -23,9 +75,7 @@ pub fn keyed_item_value(item: &NestedMeta, name: &str, must_be_string: bool) ->
match item {
&NestedMeta::Meta(Meta::NameValue(ref nameval)) if nameval.ident == name => {
match &nameval.lit {
&Lit::Str(ref strlit) => {
Some(strlit.value())
},
&Lit::Str(ref strlit) => Some(strlit.value()),
_ => if must_be_string {
panic!(format!(
"Invalid format for attribute \"{:?}\": expected a string",
Expand All @@ -35,7 +85,7 @@ pub fn keyed_item_value(item: &NestedMeta, name: &str, must_be_string: bool) ->
None
},
}
},
}
_ => None,
}
}
Expand All @@ -49,7 +99,8 @@ pub fn to_camel_case(s: &str) -> String {
if i > 0 && part.len() == 1 {
dest.push_str(&part.to_uppercase());
} else if i > 0 && part.len() > 1 {
let first = part.chars()
let first = part
.chars()
.next()
.unwrap()
.to_uppercase()
Expand All @@ -66,19 +117,6 @@ pub fn to_camel_case(s: &str) -> String {
dest
}

#[test]
fn test_to_camel_case() {
assert_eq!(&to_camel_case("test")[..], "test");
assert_eq!(&to_camel_case("_test")[..], "Test");
assert_eq!(&to_camel_case("first_second")[..], "firstSecond");
assert_eq!(&to_camel_case("first_")[..], "first");
assert_eq!(&to_camel_case("a_b_c")[..], "aBC");
assert_eq!(&to_camel_case("a_bc")[..], "aBc");
assert_eq!(&to_camel_case("a_b")[..], "aB");
assert_eq!(&to_camel_case("a")[..], "a");
assert_eq!(&to_camel_case("")[..], "");
}

pub(crate) fn to_upper_snake_case(s: &str) -> String {
let mut last_lower = false;
let mut upper = String::new();
Expand All @@ -101,35 +139,155 @@ pub(crate) fn to_upper_snake_case(s: &str) -> String {
upper
}

#[test]
fn test_to_upper_snake_case() {
assert_eq!(to_upper_snake_case("abc"), "ABC");
assert_eq!(to_upper_snake_case("a_bc"), "A_BC");
assert_eq!(to_upper_snake_case("ABC"), "ABC");
assert_eq!(to_upper_snake_case("A_BC"), "A_BC");
assert_eq!(to_upper_snake_case("SomeInput"), "SOME_INPUT");
assert_eq!(to_upper_snake_case("someInput"), "SOME_INPUT");
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!{
lazy_static! {
static ref GRAPHQL_NAME_SPEC: Regex = Regex::new("^[_A-Za-z][_0-9A-Za-z]*$").unwrap();
}
GRAPHQL_NAME_SPEC.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);
#[cfg(test)]
mod test {
use super::*;
use quote::__rt::*;
use syn::{Ident, LitStr};

fn strs_to_strings(source: Vec<&str>) -> Vec<String> {
source
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
}

fn litstr(s: &str) -> Lit {
Lit::Str(LitStr::new(s, Span::call_site()))
}

fn ident(s: &str) -> Ident {
Ident::new(s, Span::call_site())
}

mod test_get_doc_strings {
use super::*;

#[test]
fn test_single() {
let result = get_doc_strings(&vec![MetaNameValue {
ident: ident("doc").into(),
eq_token: Default::default(),
lit: litstr("foo"),
}]);
assert_eq!(
&result.unwrap(),
Some(&strs_to_strings(vec!["foo"])).unwrap()
);
}

#[test]
fn test_many() {
let result = get_doc_strings(&vec![
MetaNameValue {
ident: ident("doc").into(),
eq_token: Default::default(),
lit: litstr("foo"),
},
MetaNameValue {
ident: ident("doc").into(),
eq_token: Default::default(),
lit: litstr("\n"),
},
MetaNameValue {
ident: ident("doc").into(),
eq_token: Default::default(),
lit: litstr("bar"),
},
]);
assert_eq!(
&result.unwrap(),
Some(&strs_to_strings(vec!["foo", "\n", "bar"])).unwrap()
);
}

#[test]
fn test_not_doc() {
let result = get_doc_strings(&vec![MetaNameValue {
ident: ident("blah").into(),
eq_token: Default::default(),
lit: litstr("foo"),
}]);
assert_eq!(&result, &None);
}
}

mod test_join_doc_strings {
use super::*;

#[test]
fn test_single() {
let result = join_doc_strings(&strs_to_strings(vec!["foo"]));
assert_eq!(&result, "foo");
}
#[test]
fn test_multiple() {
let result = join_doc_strings(&strs_to_strings(vec!["foo", "bar"]));
assert_eq!(&result, "foo bar");
}

#[test]
fn test_trims_spaces() {
let result = join_doc_strings(&strs_to_strings(vec![" foo ", "bar ", " baz"]));
assert_eq!(&result, "foo bar baz");
}

#[test]
fn test_empty() {
let result = join_doc_strings(&strs_to_strings(vec!["foo", "", "bar"]));
assert_eq!(&result, "foo\nbar");
}

#[test]
fn test_newline_spaces() {
let result = join_doc_strings(&strs_to_strings(vec!["foo ", "", " bar"]));
assert_eq!(&result, "foo\nbar");
}
}

#[test]
fn test_to_camel_case() {
assert_eq!(&to_camel_case("test")[..], "test");
assert_eq!(&to_camel_case("_test")[..], "Test");
assert_eq!(&to_camel_case("first_second")[..], "firstSecond");
assert_eq!(&to_camel_case("first_")[..], "first");
assert_eq!(&to_camel_case("a_b_c")[..], "aBC");
assert_eq!(&to_camel_case("a_bc")[..], "aBc");
assert_eq!(&to_camel_case("a_b")[..], "aB");
assert_eq!(&to_camel_case("a")[..], "a");
assert_eq!(&to_camel_case("")[..], "");
}

#[test]
fn test_to_upper_snake_case() {
assert_eq!(to_upper_snake_case("abc"), "ABC");
assert_eq!(to_upper_snake_case("a_bc"), "A_BC");
assert_eq!(to_upper_snake_case("ABC"), "ABC");
assert_eq!(to_upper_snake_case("A_BC"), "A_BC");
assert_eq!(to_upper_snake_case("SomeInput"), "SOME_INPUT");
assert_eq!(to_upper_snake_case("someInput"), "SOME_INPUT");
assert_eq!(to_upper_snake_case("someINpuT"), "SOME_INPU_T");
assert_eq!(to_upper_snake_case("some_INpuT"), "SOME_INPU_T");
}

#[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);
}
}
Loading