Finish refactoring juniper_codegen
crate
This commit is contained in:
parent
0c8bcf582f
commit
576d6fb6dd
28 changed files with 769 additions and 940 deletions
114
juniper_codegen/src/common/deprecation.rs
Normal file
114
juniper_codegen/src/common/deprecation.rs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
//! Common functions, definitions and extensions for parsing and code generation
|
||||||
|
//! of [GraphQL deprecation directive][0].
|
||||||
|
//!
|
||||||
|
//! [0]: https://spec.graphql.org/October2021#sec--deprecated
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
spanned::Spanned as _,
|
||||||
|
token,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::{parse::ParseBufferExt as _, SpanContainer};
|
||||||
|
|
||||||
|
/// [GraphQL deprecation directive][0] defined on a [GraphQL field][1] or a
|
||||||
|
/// [GraphQL enum value][2] via `#[graphql(deprecated = ...)]` (or
|
||||||
|
/// `#[deprecated(note = ...)]`) attribute.
|
||||||
|
///
|
||||||
|
/// [0]: https://spec.graphql.org/October2021#sec--deprecated
|
||||||
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
||||||
|
/// [2]: https://spec.graphql.org/October2021#sec-Enum-Value
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct Directive {
|
||||||
|
/// Optional [reason][1] attached to this [deprecation][0].
|
||||||
|
///
|
||||||
|
/// [0]: https://spec.graphql.org/October2021#sec--deprecated
|
||||||
|
/// [1]: https://spec.graphql.org/October2021#sel-GAHnBZDACEDDGAA_6L
|
||||||
|
pub(crate) reason: Option<syn::LitStr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Directive {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
reason: input
|
||||||
|
.try_parse::<token::Eq>()?
|
||||||
|
.map(|_| input.parse::<syn::LitStr>())
|
||||||
|
.transpose()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Directive {
|
||||||
|
/// Tries to parse a [`Directive`] from a `#[deprecated(note = ...)]`
|
||||||
|
/// attribute, by looking up for it in the provided [`syn::Attribute`]s.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If failed to parse a [`Directive`] from a found
|
||||||
|
/// `#[deprecated(note = ...)]` attribute.
|
||||||
|
pub(crate) fn parse_from_deprecated_attr(
|
||||||
|
attrs: &[syn::Attribute],
|
||||||
|
) -> syn::Result<Option<SpanContainer<Self>>> {
|
||||||
|
for attr in attrs {
|
||||||
|
return Ok(match attr.parse_meta() {
|
||||||
|
Ok(syn::Meta::List(ref list)) if list.path.is_ident("deprecated") => {
|
||||||
|
let directive = Self::parse_from_deprecated_meta_list(list)?;
|
||||||
|
Some(SpanContainer::new(
|
||||||
|
list.path.span(),
|
||||||
|
directive.reason.as_ref().map(|r| r.span()),
|
||||||
|
directive,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Ok(syn::Meta::Path(ref path)) if path.is_ident("deprecated") => {
|
||||||
|
Some(SpanContainer::new(path.span(), None, Self::default()))
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to parse a [`Directive`] from the [`syn::MetaList`] of a single
|
||||||
|
/// `#[deprecated(note = ...)]` attribute.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If the `#[deprecated(note = ...)]` attribute has incorrect format.
|
||||||
|
fn parse_from_deprecated_meta_list(list: &syn::MetaList) -> syn::Result<Self> {
|
||||||
|
for meta in &list.nested {
|
||||||
|
if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = meta {
|
||||||
|
return if !nv.path.is_ident("note") {
|
||||||
|
Err(syn::Error::new(
|
||||||
|
nv.path.span(),
|
||||||
|
"unrecognized setting on #[deprecated(..)] attribute",
|
||||||
|
))
|
||||||
|
} else if let syn::Lit::Str(strlit) = &nv.lit {
|
||||||
|
Ok(Self {
|
||||||
|
reason: Some(strlit.clone()),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(syn::Error::new(
|
||||||
|
nv.lit.span(),
|
||||||
|
"only strings are allowed for deprecation",
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Directive {
|
||||||
|
fn to_tokens(&self, into: &mut TokenStream) {
|
||||||
|
let reason = self
|
||||||
|
.reason
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(|| quote! { None }, |text| quote! { Some(#text) });
|
||||||
|
quote! {
|
||||||
|
.deprecated(::std::option::Option::#reason)
|
||||||
|
}
|
||||||
|
.to_tokens(into);
|
||||||
|
}
|
||||||
|
}
|
200
juniper_codegen/src/common/description.rs
Normal file
200
juniper_codegen/src/common/description.rs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
//! Common functions, definitions and extensions for parsing and code generation
|
||||||
|
//! of [GraphQL description][0].
|
||||||
|
//!
|
||||||
|
//! [0]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
|
||||||
|
use crate::common::SpanContainer;
|
||||||
|
|
||||||
|
/// [GraphQL description][0] defined on a GraphQL definition via
|
||||||
|
/// `#[graphql(description = ...)]` (or `#[doc = ...]`) attribute.
|
||||||
|
///
|
||||||
|
/// [0]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) struct Description(syn::LitStr);
|
||||||
|
|
||||||
|
impl Parse for Description {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
input.parse::<syn::LitStr>().map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Description {
|
||||||
|
/// Tries to parse a [`Description`] from a `#[doc = ...]` attribute (or
|
||||||
|
/// Rust doc comment), by looking up for it in the provided
|
||||||
|
/// [`syn::Attribute`]s.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If failed to parse a [`Description`] from a found `#[doc = ...]`
|
||||||
|
/// attribute.
|
||||||
|
pub(crate) fn parse_from_doc_attrs(
|
||||||
|
attrs: &[syn::Attribute],
|
||||||
|
) -> syn::Result<Option<SpanContainer<Self>>> {
|
||||||
|
let (mut first_span, mut descriptions) = (None, Vec::new());
|
||||||
|
for attr in attrs {
|
||||||
|
match attr.parse_meta() {
|
||||||
|
Ok(syn::Meta::NameValue(ref nv)) if nv.path.is_ident("doc") => {
|
||||||
|
if let syn::Lit::Str(strlit) = &nv.lit {
|
||||||
|
if first_span.is_none() {
|
||||||
|
first_span = Some(strlit.span());
|
||||||
|
}
|
||||||
|
descriptions.push(strlit.value());
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
nv.lit.span(),
|
||||||
|
"#[doc] attributes may only have a string literal",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(first_span.map(|span| {
|
||||||
|
SpanContainer::new(
|
||||||
|
span,
|
||||||
|
None,
|
||||||
|
Self(syn::LitStr::new(&Self::concatenate(&descriptions), span)),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concatenates [`Description`] strings into a single one.
|
||||||
|
fn concatenate(descriptions: &[String]) -> String {
|
||||||
|
let last_index = descriptions.len() - 1;
|
||||||
|
descriptions
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.as_str().trim_end())
|
||||||
|
.map(|s| {
|
||||||
|
// Trim leading space.
|
||||||
|
s.strip_prefix(' ').unwrap_or(s)
|
||||||
|
})
|
||||||
|
.enumerate()
|
||||||
|
.fold(String::new(), |mut buffer, (index, s)| {
|
||||||
|
// Add newline, except when string ends in a continuation
|
||||||
|
// backslash or is the last line.
|
||||||
|
if index == last_index {
|
||||||
|
buffer.push_str(s);
|
||||||
|
} else if s.ends_with('\\') {
|
||||||
|
buffer.push_str(s.trim_end_matches('\\'));
|
||||||
|
buffer.push(' ');
|
||||||
|
} else {
|
||||||
|
buffer.push_str(s);
|
||||||
|
buffer.push('\n');
|
||||||
|
}
|
||||||
|
buffer
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Description {
|
||||||
|
fn to_tokens(&self, into: &mut TokenStream) {
|
||||||
|
let desc = &self.0;
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
.description(#desc)
|
||||||
|
}
|
||||||
|
.to_tokens(into);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod parse_from_doc_attrs_test {
|
||||||
|
use quote::quote;
|
||||||
|
use syn::parse_quote;
|
||||||
|
|
||||||
|
use super::Description;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single() {
|
||||||
|
let desc = Description::parse_from_doc_attrs(&[parse_quote! { #[doc = "foo"] }])
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.into_inner();
|
||||||
|
assert_eq!(
|
||||||
|
quote! { #desc }.to_string(),
|
||||||
|
quote! { .description("foo") }.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn many() {
|
||||||
|
let desc = Description::parse_from_doc_attrs(&[
|
||||||
|
parse_quote! { #[doc = "foo"] },
|
||||||
|
parse_quote! { #[doc = "\n"] },
|
||||||
|
parse_quote! { #[doc = "bar"] },
|
||||||
|
])
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.into_inner();
|
||||||
|
assert_eq!(
|
||||||
|
quote! { #desc }.to_string(),
|
||||||
|
quote! { .description("foo\n\nbar") }.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn not_doc() {
|
||||||
|
let desc = Description::parse_from_doc_attrs(&[parse_quote! { #[blah = "foo"] }]).unwrap();
|
||||||
|
assert_eq!(desc, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod concatenate_test {
|
||||||
|
use super::Description;
|
||||||
|
|
||||||
|
/// Forms a [`Vec`] of [`String`]s out of the provided [`str`]s
|
||||||
|
/// [`Iterator`].
|
||||||
|
fn to_strings<'i>(source: impl IntoIterator<Item = &'i str>) -> Vec<String> {
|
||||||
|
source.into_iter().map(ToOwned::to_owned).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single() {
|
||||||
|
assert_eq!(Description::concatenate(&to_strings(["foo"])), "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple() {
|
||||||
|
assert_eq!(
|
||||||
|
Description::concatenate(&to_strings(["foo", "bar"])),
|
||||||
|
"foo\nbar",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trims_spaces() {
|
||||||
|
assert_eq!(
|
||||||
|
Description::concatenate(&to_strings([" foo ", "bar ", " baz"])),
|
||||||
|
"foo\nbar\nbaz",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
assert_eq!(
|
||||||
|
Description::concatenate(&to_strings(["foo", "", "bar"])),
|
||||||
|
"foo\n\nbar",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn newline_spaces() {
|
||||||
|
assert_eq!(
|
||||||
|
Description::concatenate(&to_strings(["foo ", "", " bar"])),
|
||||||
|
"foo\n\nbar",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn continuation_backslash() {
|
||||||
|
assert_eq!(
|
||||||
|
Description::concatenate(&to_strings(["foo\\", "x\\", "y", "bar"])),
|
||||||
|
"foo x y\nbar",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
//!
|
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use proc_macro_error::{Diagnostic, Level};
|
use proc_macro_error::{Diagnostic, Level};
|
||||||
|
|
||||||
/// URL of the GraphQL specification (October 2021 Edition).
|
/// URL of the GraphQL specification (October 2021 Edition).
|
||||||
pub const SPEC_URL: &str = "https://spec.graphql.org/October2021";
|
pub(crate) const SPEC_URL: &str = "https://spec.graphql.org/October2021";
|
||||||
|
|
||||||
pub enum GraphQLScope {
|
pub(crate) enum Scope {
|
||||||
EnumDerive,
|
EnumDerive,
|
||||||
InputObjectDerive,
|
InputObjectDerive,
|
||||||
InterfaceAttr,
|
InterfaceAttr,
|
||||||
|
@ -22,8 +20,8 @@ pub enum GraphQLScope {
|
||||||
UnionDerive,
|
UnionDerive,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphQLScope {
|
impl Scope {
|
||||||
pub fn spec_section(&self) -> &str {
|
pub(crate) fn spec_section(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::EnumDerive => "#sec-Enums",
|
Self::EnumDerive => "#sec-Enums",
|
||||||
Self::InputObjectDerive => "#sec-Input-Objects",
|
Self::InputObjectDerive => "#sec-Input-Objects",
|
||||||
|
@ -36,7 +34,7 @@ impl GraphQLScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for GraphQLScope {
|
impl fmt::Display for Scope {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let name = match self {
|
let name = match self {
|
||||||
Self::EnumDerive => "enum",
|
Self::EnumDerive => "enum",
|
||||||
|
@ -51,30 +49,30 @@ impl fmt::Display for GraphQLScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphQLScope {
|
impl Scope {
|
||||||
fn spec_link(&self) -> String {
|
fn spec_link(&self) -> String {
|
||||||
format!("{}{}", SPEC_URL, self.spec_section())
|
format!("{}{}", SPEC_URL, self.spec_section())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn custom<S: AsRef<str>>(&self, span: Span, msg: S) -> Diagnostic {
|
pub(crate) fn custom<S: AsRef<str>>(&self, span: Span, msg: S) -> Diagnostic {
|
||||||
Diagnostic::spanned(span, Level::Error, format!("{} {}", self, msg.as_ref()))
|
Diagnostic::spanned(span, Level::Error, format!("{} {}", self, msg.as_ref()))
|
||||||
.note(self.spec_link())
|
.note(self.spec_link())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn error(&self, err: syn::Error) -> Diagnostic {
|
pub(crate) fn error(&self, err: syn::Error) -> Diagnostic {
|
||||||
Diagnostic::spanned(err.span(), Level::Error, format!("{} {}", self, err))
|
Diagnostic::spanned(err.span(), Level::Error, format!("{} {}", self, err))
|
||||||
.note(self.spec_link())
|
.note(self.spec_link())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit_custom<S: AsRef<str>>(&self, span: Span, msg: S) {
|
pub(crate) fn emit_custom<S: AsRef<str>>(&self, span: Span, msg: S) {
|
||||||
self.custom(span, msg).emit()
|
self.custom(span, msg).emit()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn custom_error<S: AsRef<str>>(&self, span: Span, msg: S) -> syn::Error {
|
pub(crate) fn custom_error<S: AsRef<str>>(&self, span: Span, msg: S) -> syn::Error {
|
||||||
syn::Error::new(span, format!("{} {}", self, msg.as_ref()))
|
syn::Error::new(span, format!("{} {}", self, msg.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_double_underscore(&self, field: Span) {
|
pub(crate) fn no_double_underscore(&self, field: Span) {
|
||||||
Diagnostic::spanned(
|
Diagnostic::spanned(
|
||||||
field,
|
field,
|
||||||
Level::Error,
|
Level::Error,
|
|
@ -14,16 +14,13 @@ use syn::{
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
default, diagnostic, filter_attrs,
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
ParseBufferExt as _, TypeExt as _,
|
ParseBufferExt as _, TypeExt as _,
|
||||||
},
|
},
|
||||||
scalar,
|
path_eq_single, rename, scalar, Description, SpanContainer,
|
||||||
},
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::{filter_attrs, path_eq_single, span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Available metadata (arguments) behind `#[graphql]` attribute placed on a
|
/// Available metadata (arguments) behind `#[graphql]` attribute placed on a
|
||||||
|
@ -44,19 +41,16 @@ pub(crate) struct Attr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
pub(crate) description: Option<SpanContainer<syn::LitStr>>,
|
pub(crate) description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified [default value][2] of this [GraphQL argument][1].
|
/// Explicitly specified [default value][2] of this [GraphQL argument][1].
|
||||||
///
|
///
|
||||||
/// If the exact default expression is not specified, then the [`Default`]
|
|
||||||
/// value is used.
|
|
||||||
///
|
|
||||||
/// If [`None`], then this [GraphQL argument][1] is considered as
|
/// If [`None`], then this [GraphQL argument][1] is considered as
|
||||||
/// [required][2].
|
/// [required][2].
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments
|
/// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments
|
||||||
pub(crate) default: Option<SpanContainer<Option<syn::Expr>>>,
|
pub(crate) default: Option<SpanContainer<default::Value>>,
|
||||||
|
|
||||||
/// Explicitly specified marker indicating that this method argument doesn't
|
/// Explicitly specified marker indicating that this method argument doesn't
|
||||||
/// represent a [GraphQL argument][1], but is a [`Context`] being injected
|
/// represent a [GraphQL argument][1], but is a [`Context`] being injected
|
||||||
|
@ -98,27 +92,15 @@ impl Parse for Attr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"default" => {
|
"default" => {
|
||||||
let mut expr = None;
|
let val = input.parse::<default::Value>()?;
|
||||||
if input.is_next::<token::Eq>() {
|
|
||||||
input.parse::<token::Eq>()?;
|
|
||||||
expr = Some(input.parse::<syn::Expr>()?);
|
|
||||||
} else if input.is_next::<token::Paren>() {
|
|
||||||
let inner;
|
|
||||||
let _ = syn::parenthesized!(inner in input);
|
|
||||||
expr = Some(inner.parse::<syn::Expr>()?);
|
|
||||||
}
|
|
||||||
out.default
|
out.default
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(val.span()), val))
|
||||||
ident.span(),
|
|
||||||
expr.as_ref().map(|e| e.span()),
|
|
||||||
expr,
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"ctx" | "context" | "Context" => {
|
"ctx" | "context" | "Context" => {
|
||||||
|
@ -241,18 +223,15 @@ pub(crate) struct OnField {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
pub(crate) description: Option<String>,
|
pub(crate) description: Option<Description>,
|
||||||
|
|
||||||
/// Default value of this [GraphQL field argument][1] in GraphQL schema.
|
/// Default value of this [GraphQL field argument][1] in GraphQL schema.
|
||||||
///
|
///
|
||||||
/// If outer [`Option`] is [`None`], then this [argument][1] is a
|
/// If [`None`], then this [argument][1] is a [required][2] one.
|
||||||
/// [required][2] one.
|
|
||||||
///
|
|
||||||
/// If inner [`Option`] is [`None`], then the [`Default`] value is used.
|
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments
|
/// [2]: https://spec.graphql.org/October2021#sec-Required-Arguments
|
||||||
pub(crate) default: Option<Option<syn::Expr>>,
|
pub(crate) default: Option<default::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible kinds of Rust method arguments for code generation.
|
/// Possible kinds of Rust method arguments for code generation.
|
||||||
|
@ -323,16 +302,9 @@ impl OnMethod {
|
||||||
|
|
||||||
let (name, ty) = (&arg.name, &arg.ty);
|
let (name, ty) = (&arg.name, &arg.ty);
|
||||||
|
|
||||||
let description = arg
|
let description = &arg.description;
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|desc| quote! { .description(#desc) });
|
|
||||||
|
|
||||||
let method = if let Some(val) = &arg.default {
|
let method = if let Some(val) = &arg.default {
|
||||||
let val = val
|
|
||||||
.as_ref()
|
|
||||||
.map(|v| quote! { (#v).into() })
|
|
||||||
.unwrap_or_else(|| quote! { <#ty as Default>::default() });
|
|
||||||
quote_spanned! { val.span() =>
|
quote_spanned! { val.span() =>
|
||||||
.arg_with_default::<#ty>(#name, &#val, info)
|
.arg_with_default::<#ty>(#name, &#val, info)
|
||||||
}
|
}
|
||||||
|
@ -395,8 +367,8 @@ impl OnMethod {
|
||||||
/// given `scope`.
|
/// given `scope`.
|
||||||
pub(crate) fn parse(
|
pub(crate) fn parse(
|
||||||
argument: &mut syn::PatType,
|
argument: &mut syn::PatType,
|
||||||
renaming: &RenameRule,
|
renaming: &rename::Policy,
|
||||||
scope: &GraphQLScope,
|
scope: &diagnostic::Scope,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let orig_attrs = argument.attrs.clone();
|
let orig_attrs = argument.attrs.clone();
|
||||||
|
|
||||||
|
@ -462,8 +434,8 @@ impl OnMethod {
|
||||||
Some(Self::Regular(Box::new(OnField {
|
Some(Self::Regular(Box::new(OnField {
|
||||||
name,
|
name,
|
||||||
ty: argument.ty.as_ref().clone(),
|
ty: argument.ty.as_ref().clone(),
|
||||||
description: attr.description.as_ref().map(|d| d.as_ref().value()),
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
default: attr.default.as_ref().map(|v| v.as_ref().clone()),
|
default: attr.default.map(SpanContainer::into_inner),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,13 @@ use syn::{
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
deprecation, filter_attrs,
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
ParseBufferExt as _,
|
ParseBufferExt as _,
|
||||||
},
|
},
|
||||||
scalar,
|
scalar, Description, SpanContainer,
|
||||||
},
|
|
||||||
util::{filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use self::arg::OnMethod as MethodArgument;
|
pub(crate) use self::arg::OnMethod as MethodArgument;
|
||||||
|
@ -47,7 +45,7 @@ pub(crate) struct Attr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
pub(crate) description: Option<SpanContainer<syn::LitStr>>,
|
pub(crate) description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified [deprecation][2] of this [GraphQL field][1].
|
/// Explicitly specified [deprecation][2] of this [GraphQL field][1].
|
||||||
///
|
///
|
||||||
|
@ -56,7 +54,7 @@ pub(crate) struct Attr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Deprecation
|
/// [2]: https://spec.graphql.org/October2021#sec-Deprecation
|
||||||
pub(crate) deprecated: Option<SpanContainer<Option<syn::LitStr>>>,
|
pub(crate) deprecated: Option<SpanContainer<deprecation::Directive>>,
|
||||||
|
|
||||||
/// Explicitly specified marker indicating that this method (or struct
|
/// Explicitly specified marker indicating that this method (or struct
|
||||||
/// field) should be omitted by code generation and not considered as the
|
/// field) should be omitted by code generation and not considered as the
|
||||||
|
@ -81,22 +79,18 @@ impl Parse for Attr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"deprecated" => {
|
"deprecated" => {
|
||||||
let mut reason = None;
|
let directive = input.parse::<deprecation::Directive>()?;
|
||||||
if input.is_next::<token::Eq>() {
|
|
||||||
input.parse::<token::Eq>()?;
|
|
||||||
reason = Some(input.parse::<syn::LitStr>()?);
|
|
||||||
}
|
|
||||||
out.deprecated
|
out.deprecated
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(
|
||||||
ident.span(),
|
ident.span(),
|
||||||
reason.as_ref().map(|r| r.span()),
|
directive.reason.as_ref().map(|r| r.span()),
|
||||||
reason,
|
directive,
|
||||||
))
|
))
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
|
@ -145,17 +139,11 @@ impl Attr {
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr.description.is_none() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs).map(|sc| {
|
attr.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
let span = sc.span_ident();
|
|
||||||
sc.map(|desc| syn::LitStr::new(&desc, span))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr.deprecated.is_none() {
|
if attr.deprecated.is_none() {
|
||||||
attr.deprecated = get_deprecated(attrs).map(|sc| {
|
attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?;
|
||||||
let span = sc.span_ident();
|
|
||||||
sc.map(|depr| depr.reason.map(|rsn| syn::LitStr::new(&rsn, span)))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attr)
|
Ok(attr)
|
||||||
|
@ -182,16 +170,13 @@ pub(crate) struct Definition {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
pub(crate) description: Option<String>,
|
pub(crate) description: Option<Description>,
|
||||||
|
|
||||||
/// [Deprecation][2] of this [GraphQL field][1] to put into GraphQL schema.
|
/// [Deprecation][2] of this [GraphQL field][1] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
/// If inner [`Option`] is [`None`], then deprecation has no message
|
|
||||||
/// attached.
|
|
||||||
///
|
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Deprecation
|
/// [2]: https://spec.graphql.org/October2021#sec-Deprecation
|
||||||
pub(crate) deprecated: Option<Option<String>>,
|
pub(crate) deprecated: Option<deprecation::Directive>,
|
||||||
|
|
||||||
/// Ident of the Rust method (or struct field) representing this
|
/// Ident of the Rust method (or struct field) representing this
|
||||||
/// [GraphQL field][1].
|
/// [GraphQL field][1].
|
||||||
|
@ -305,18 +290,8 @@ impl Definition {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let description = self
|
let description = &self.description;
|
||||||
.description
|
let deprecated = &self.deprecated;
|
||||||
.as_ref()
|
|
||||||
.map(|desc| quote! { .description(#desc) });
|
|
||||||
|
|
||||||
let deprecated = self.deprecated.as_ref().map(|reason| {
|
|
||||||
let reason = reason
|
|
||||||
.as_ref()
|
|
||||||
.map(|rsn| quote! { Some(#rsn) })
|
|
||||||
.unwrap_or_else(|| quote! { None });
|
|
||||||
quote! { .deprecated(#reason) }
|
|
||||||
});
|
|
||||||
|
|
||||||
let args = self
|
let args = self
|
||||||
.arguments
|
.arguments
|
||||||
|
|
|
@ -1,7 +1,31 @@
|
||||||
//! Common functions, definitions and extensions for code generation, used by this crate.
|
//! Common functions, definitions and extensions for code generation, used by this crate.
|
||||||
|
|
||||||
pub(crate) mod default;
|
pub(crate) mod default;
|
||||||
|
pub(crate) mod deprecation;
|
||||||
|
mod description;
|
||||||
|
pub(crate) mod diagnostic;
|
||||||
pub(crate) mod field;
|
pub(crate) mod field;
|
||||||
pub(crate) mod gen;
|
pub(crate) mod gen;
|
||||||
pub(crate) mod parse;
|
pub(crate) mod parse;
|
||||||
|
pub(crate) mod rename;
|
||||||
pub(crate) mod scalar;
|
pub(crate) mod scalar;
|
||||||
|
mod span_container;
|
||||||
|
|
||||||
|
pub(crate) use self::{description::Description, span_container::SpanContainer};
|
||||||
|
|
||||||
|
/// Checks whether the specified [`syn::Path`] equals to one-segment string
|
||||||
|
/// `value`.
|
||||||
|
pub(crate) fn path_eq_single(path: &syn::Path, value: &str) -> bool {
|
||||||
|
path.segments.len() == 1 && path.segments[0].ident == value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filters the provided [`syn::Attribute`] to contain only ones with the
|
||||||
|
/// specified `name`.
|
||||||
|
pub(crate) fn filter_attrs<'a>(
|
||||||
|
name: &'a str,
|
||||||
|
attrs: &'a [syn::Attribute],
|
||||||
|
) -> impl Iterator<Item = &'a syn::Attribute> + 'a {
|
||||||
|
attrs
|
||||||
|
.iter()
|
||||||
|
.filter(move |attr| path_eq_single(&attr.path, name))
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use syn::parse_quote;
|
use syn::parse_quote;
|
||||||
|
|
||||||
use crate::util::path_eq_single;
|
use crate::common::path_eq_single;
|
||||||
|
|
||||||
/// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given
|
/// Prepends the given `attrs` collection with a new [`syn::Attribute`] generated from the given
|
||||||
/// `attr_path` and `attr_args`.
|
/// `attr_path` and `attr_args`.
|
||||||
|
|
164
juniper_codegen/src/common/rename.rs
Normal file
164
juniper_codegen/src/common/rename.rs
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
//! Common functions, definitions and extensions for parsing and code generation
|
||||||
|
//! of `#[graphql(rename_all = ...)]` attribute.
|
||||||
|
|
||||||
|
use std::{convert::TryFrom, str::FromStr};
|
||||||
|
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
|
||||||
|
/// Possible ways to rename all [GraphQL fields][1] or [GrqphQL enum values][2].
|
||||||
|
///
|
||||||
|
/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields
|
||||||
|
/// [2]: https://spec.graphql.org/October2021#sec-Enum-Value
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub(crate) enum Policy {
|
||||||
|
/// Do nothing, and use the default conventions renaming.
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// Rename in `camelCase` style.
|
||||||
|
CamelCase,
|
||||||
|
|
||||||
|
/// Rename in `SCREAMING_SNAKE_CASE` style.
|
||||||
|
ScreamingSnakeCase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Policy {
|
||||||
|
/// Applies this [`Policy`] to the given `name`.
|
||||||
|
pub(crate) fn apply(&self, name: &str) -> String {
|
||||||
|
match self {
|
||||||
|
Self::None => name.to_owned(),
|
||||||
|
Self::CamelCase => to_camel_case(name),
|
||||||
|
Self::ScreamingSnakeCase => to_upper_snake_case(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Policy {
|
||||||
|
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(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<syn::LitStr> for Policy {
|
||||||
|
type Error = syn::Error;
|
||||||
|
|
||||||
|
fn try_from(lit: syn::LitStr) -> syn::Result<Self> {
|
||||||
|
Self::from_str(&lit.value())
|
||||||
|
.map_err(|_| syn::Error::new(lit.span(), "unknown renaming policy"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Policy {
|
||||||
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
|
Self::try_from(input.parse::<syn::LitStr>()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: duplicated from juniper crate!
|
||||||
|
fn to_camel_case(s: &str) -> String {
|
||||||
|
let mut dest = String::new();
|
||||||
|
|
||||||
|
// Handle `_` and `__` to be more friendly with the `_var` convention for
|
||||||
|
// unused variables, and GraphQL introspection identifiers.
|
||||||
|
let s_iter = if let Some(s) = s.strip_prefix("__") {
|
||||||
|
dest.push_str("__");
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
s.strip_prefix('_').unwrap_or(s)
|
||||||
|
}
|
||||||
|
.split('_')
|
||||||
|
.enumerate();
|
||||||
|
|
||||||
|
for (i, part) in s_iter {
|
||||||
|
if i > 0 && part.len() == 1 {
|
||||||
|
dest.push_str(&part.to_uppercase());
|
||||||
|
} else if i > 0 && part.len() > 1 {
|
||||||
|
let first = part
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.to_uppercase()
|
||||||
|
.collect::<String>();
|
||||||
|
let second = &part[1..];
|
||||||
|
|
||||||
|
dest.push_str(&first);
|
||||||
|
dest.push_str(second);
|
||||||
|
} else if i == 0 {
|
||||||
|
dest.push_str(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dest
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_upper_snake_case(s: &str) -> String {
|
||||||
|
let mut last_lower = false;
|
||||||
|
let mut upper = String::new();
|
||||||
|
for c in s.chars() {
|
||||||
|
if c == '_' {
|
||||||
|
last_lower = false;
|
||||||
|
} else if c.is_lowercase() {
|
||||||
|
last_lower = true;
|
||||||
|
} else if c.is_uppercase() {
|
||||||
|
if last_lower {
|
||||||
|
upper.push('_');
|
||||||
|
}
|
||||||
|
last_lower = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for u in c.to_uppercase() {
|
||||||
|
upper.push(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
upper
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod to_camel_case_tests {
|
||||||
|
use super::to_camel_case;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn converts_correctly() {
|
||||||
|
for (input, expected) in [
|
||||||
|
("test", "test"),
|
||||||
|
("_test", "test"),
|
||||||
|
("__test", "__test"),
|
||||||
|
("first_second", "firstSecond"),
|
||||||
|
("first_", "first"),
|
||||||
|
("a_b_c", "aBC"),
|
||||||
|
("a_bc", "aBc"),
|
||||||
|
("a_b", "aB"),
|
||||||
|
("a", "a"),
|
||||||
|
("", ""),
|
||||||
|
] {
|
||||||
|
assert_eq!(to_camel_case(input), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod to_upper_snake_case_tests {
|
||||||
|
use super::to_upper_snake_case;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn converts_correctly() {
|
||||||
|
for (input, expected) in [
|
||||||
|
("abc", "ABC"),
|
||||||
|
("a_bc", "A_BC"),
|
||||||
|
("ABC", "ABC"),
|
||||||
|
("A_BC", "A_BC"),
|
||||||
|
("SomeInput", "SOME_INPUT"),
|
||||||
|
("someInput", "SOME_INPUT"),
|
||||||
|
("someINpuT", "SOME_INPU_T"),
|
||||||
|
("some_INpuT", "SOME_INPU_T"),
|
||||||
|
] {
|
||||||
|
assert_eq!(to_upper_snake_case(input), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,8 @@ use std::{
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct SpanContainer<T> {
|
pub(crate) struct SpanContainer<T> {
|
||||||
expr: Option<Span>,
|
expr: Option<Span>,
|
||||||
ident: Span,
|
ident: Span,
|
||||||
val: T,
|
val: T,
|
||||||
|
@ -20,15 +20,15 @@ impl<T: ToTokens> ToTokens for SpanContainer<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SpanContainer<T> {
|
impl<T> SpanContainer<T> {
|
||||||
pub fn new(ident: Span, expr: Option<Span>, val: T) -> Self {
|
pub(crate) fn new(ident: Span, expr: Option<Span>, val: T) -> Self {
|
||||||
Self { expr, ident, val }
|
Self { expr, ident, val }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span_ident(&self) -> Span {
|
pub(crate) fn span_ident(&self) -> Span {
|
||||||
self.ident
|
self.ident
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span_joined(&self) -> Span {
|
pub(crate) fn span_joined(&self) -> Span {
|
||||||
if let Some(s) = self.expr {
|
if let Some(s) = self.expr {
|
||||||
// TODO: Use `Span::join` once stabilized and available on stable:
|
// TODO: Use `Span::join` once stabilized and available on stable:
|
||||||
// https://github.com/rust-lang/rust/issues/54725
|
// https://github.com/rust-lang/rust/issues/54725
|
||||||
|
@ -41,17 +41,9 @@ impl<T> SpanContainer<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_inner(self) -> T {
|
pub(crate) fn into_inner(self) -> T {
|
||||||
self.val
|
self.val
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> SpanContainer<U> {
|
|
||||||
SpanContainer {
|
|
||||||
expr: self.expr,
|
|
||||||
ident: self.ident,
|
|
||||||
val: f(self.val),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AsRef<T> for SpanContainer<T> {
|
impl<T> AsRef<T> for SpanContainer<T> {
|
|
@ -6,16 +6,12 @@ use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens as _;
|
use quote::ToTokens as _;
|
||||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{diagnostic, rename, scalar, SpanContainer};
|
||||||
common::scalar,
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::{span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{ContainerAttr, Definition, ValueDefinition, VariantAttr};
|
use super::{ContainerAttr, Definition, ValueDefinition, VariantAttr};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[derive(GraphQLEnum)]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLEnum)]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::EnumDerive;
|
const ERR: diagnostic::Scope = diagnostic::Scope::EnumDerive;
|
||||||
|
|
||||||
/// Expands `#[derive(GraphQLEnum)]` macro into generated code.
|
/// Expands `#[derive(GraphQLEnum)]` macro into generated code.
|
||||||
pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
@ -32,7 +28,7 @@ pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
let renaming = attr
|
let renaming = attr
|
||||||
.rename_values
|
.rename_values
|
||||||
.map(SpanContainer::into_inner)
|
.map(SpanContainer::into_inner)
|
||||||
.unwrap_or(RenameRule::ScreamingSnakeCase);
|
.unwrap_or(rename::Policy::ScreamingSnakeCase);
|
||||||
let values = data
|
let values = data
|
||||||
.variants
|
.variants
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -80,8 +76,6 @@ pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
.context
|
.context
|
||||||
.map_or_else(|| parse_quote! { () }, SpanContainer::into_inner);
|
.map_or_else(|| parse_quote! { () }, SpanContainer::into_inner);
|
||||||
|
|
||||||
let description = attr.description.map(|d| d.into_inner().into_boxed_str());
|
|
||||||
|
|
||||||
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
|
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
|
||||||
|
|
||||||
proc_macro_error::abort_if_dirty();
|
proc_macro_error::abort_if_dirty();
|
||||||
|
@ -90,7 +84,7 @@ pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
ident: ast.ident,
|
ident: ast.ident,
|
||||||
generics: ast.generics,
|
generics: ast.generics,
|
||||||
name,
|
name,
|
||||||
description,
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
context,
|
context,
|
||||||
scalar,
|
scalar,
|
||||||
values,
|
values,
|
||||||
|
@ -103,7 +97,7 @@ pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
/// Parses a [`ValueDefinition`] from the given Rust enum variant definition.
|
/// Parses a [`ValueDefinition`] from the given Rust enum variant definition.
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if the parsing fails, or the enum variant is ignored.
|
/// Returns [`None`] if the parsing fails, or the enum variant is ignored.
|
||||||
fn parse_value(v: &syn::Variant, renaming: RenameRule) -> Option<ValueDefinition> {
|
fn parse_value(v: &syn::Variant, renaming: rename::Policy) -> Option<ValueDefinition> {
|
||||||
let attr = VariantAttr::from_attrs("graphql", &v.attrs)
|
let attr = VariantAttr::from_attrs("graphql", &v.attrs)
|
||||||
.map_err(|e| proc_macro_error::emit_error!(e))
|
.map_err(|e| proc_macro_error::emit_error!(e))
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
@ -124,19 +118,11 @@ fn parse_value(v: &syn::Variant, renaming: RenameRule) -> Option<ValueDefinition
|
||||||
)
|
)
|
||||||
.into_boxed_str();
|
.into_boxed_str();
|
||||||
|
|
||||||
let description = attr.description.map(|d| d.into_inner().into_boxed_str());
|
|
||||||
|
|
||||||
let deprecated = attr.deprecated.map(|desc| {
|
|
||||||
desc.into_inner()
|
|
||||||
.as_ref()
|
|
||||||
.map(|lit| lit.value().into_boxed_str())
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(ValueDefinition {
|
Some(ValueDefinition {
|
||||||
ident: v.ident.clone(),
|
ident: v.ident.clone(),
|
||||||
name,
|
name,
|
||||||
description,
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
deprecated,
|
deprecated: attr.deprecated.map(SpanContainer::into_inner),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,17 +16,13 @@ use syn::{
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
deprecation, filter_attrs,
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
ParseBufferExt as _,
|
ParseBufferExt as _,
|
||||||
},
|
},
|
||||||
scalar,
|
rename, scalar, Description, SpanContainer,
|
||||||
},
|
|
||||||
util::{
|
|
||||||
filter_attrs, get_deprecated, get_doc_comment, span_container::SpanContainer, RenameRule,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Available arguments behind `#[graphql]` attribute placed on a Rust enum
|
/// Available arguments behind `#[graphql]` attribute placed on a Rust enum
|
||||||
|
@ -49,7 +45,7 @@ struct ContainerAttr {
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Enums
|
/// [0]: https://spec.graphql.org/October2021#sec-Enums
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<SpanContainer<String>>,
|
description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified type of [`Context`] to use for resolving this
|
/// Explicitly specified type of [`Context`] to use for resolving this
|
||||||
/// [GraphQL enum][0] type with.
|
/// [GraphQL enum][0] type with.
|
||||||
|
@ -71,15 +67,15 @@ struct ContainerAttr {
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Enums
|
/// [0]: https://spec.graphql.org/October2021#sec-Enums
|
||||||
scalar: Option<SpanContainer<scalar::AttrValue>>,
|
scalar: Option<SpanContainer<scalar::AttrValue>>,
|
||||||
|
|
||||||
/// Explicitly specified [`RenameRule`] for all [values][1] of this
|
/// Explicitly specified [`rename::Policy`] for all [values][1] of this
|
||||||
/// [GraphQL enum][0].
|
/// [GraphQL enum][0].
|
||||||
///
|
///
|
||||||
/// If [`None`], then the [`RenameRule::ScreamingSnakeCase`] rule will be
|
/// If [`None`], then the [`rename::Policy::ScreamingSnakeCase`] will be
|
||||||
/// applied by default.
|
/// applied by default.
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Enums
|
/// [0]: https://spec.graphql.org/October2021#sec-Enums
|
||||||
/// [1]: https://spec.graphql.org/October2021#EnumValuesDefinition
|
/// [1]: https://spec.graphql.org/October2021#EnumValuesDefinition
|
||||||
rename_values: Option<SpanContainer<RenameRule>>,
|
rename_values: Option<SpanContainer<rename::Policy>>,
|
||||||
|
|
||||||
/// Indicator whether the generated code is intended to be used only inside
|
/// Indicator whether the generated code is intended to be used only inside
|
||||||
/// the [`juniper`] library.
|
/// the [`juniper`] library.
|
||||||
|
@ -105,13 +101,9 @@ impl Parse for ContainerAttr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
ident.span(),
|
|
||||||
Some(desc.span()),
|
|
||||||
desc.value(),
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"ctx" | "context" | "Context" => {
|
"ctx" | "context" | "Context" => {
|
||||||
|
@ -174,7 +166,7 @@ impl ContainerAttr {
|
||||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if attr.description.is_none() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs);
|
attr.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attr)
|
Ok(attr)
|
||||||
|
@ -202,19 +194,17 @@ struct VariantAttr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
|
/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<SpanContainer<String>>,
|
description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified [deprecation][2] of this [GraphQL enum value][1].
|
/// Explicitly specified [deprecation][2] of this [GraphQL enum value][1].
|
||||||
///
|
///
|
||||||
/// If [`None`], then Rust `#[deprecated]` attribute will be used as the
|
/// If [`None`], then Rust `#[deprecated]` attribute will be used as the
|
||||||
/// [deprecation][2], if any.
|
/// [deprecation][2], if any.
|
||||||
///
|
///
|
||||||
/// If the inner [`Option`] is [`None`], then no [reason][3] was provided.
|
|
||||||
///
|
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
|
/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec--deprecated
|
/// [2]: https://spec.graphql.org/October2021#sec--deprecated
|
||||||
/// [3]: https://spec.graphql.org/October2021#sel-GAHnBZDACEDDGAA_6L
|
/// [3]: https://spec.graphql.org/October2021#sel-GAHnBZDACEDDGAA_6L
|
||||||
deprecated: Option<SpanContainer<Option<syn::LitStr>>>,
|
deprecated: Option<SpanContainer<deprecation::Directive>>,
|
||||||
|
|
||||||
/// Explicitly specified marker for the Rust enum variant to be ignored and
|
/// Explicitly specified marker for the Rust enum variant to be ignored and
|
||||||
/// not included into the code generated for a [GraphQL enum][0]
|
/// not included into the code generated for a [GraphQL enum][0]
|
||||||
|
@ -243,26 +233,18 @@ impl Parse for VariantAttr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
ident.span(),
|
|
||||||
Some(desc.span()),
|
|
||||||
desc.value(),
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"deprecated" => {
|
"deprecated" => {
|
||||||
let mut reason = None;
|
let directive = input.parse::<deprecation::Directive>()?;
|
||||||
if input.is_next::<token::Eq>() {
|
|
||||||
input.parse::<token::Eq>()?;
|
|
||||||
reason = Some(input.parse::<syn::LitStr>()?);
|
|
||||||
}
|
|
||||||
out.deprecated
|
out.deprecated
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(
|
||||||
ident.span(),
|
ident.span(),
|
||||||
reason.as_ref().map(|r| r.span()),
|
directive.reason.as_ref().map(|r| r.span()),
|
||||||
reason,
|
directive,
|
||||||
))
|
))
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
|
@ -300,14 +282,11 @@ impl VariantAttr {
|
||||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if attr.description.is_none() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs);
|
attr.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr.deprecated.is_none() {
|
if attr.deprecated.is_none() {
|
||||||
attr.deprecated = get_deprecated(attrs).map(|sc| {
|
attr.deprecated = deprecation::Directive::parse_from_deprecated_attr(attrs)?;
|
||||||
let span = sc.span_ident();
|
|
||||||
sc.map(|depr| depr.reason.map(|rsn| syn::LitStr::new(&rsn, span)))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attr)
|
Ok(attr)
|
||||||
|
@ -335,18 +314,14 @@ struct ValueDefinition {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
|
/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<Box<str>>,
|
description: Option<Description>,
|
||||||
|
|
||||||
/// [Deprecation][2] of this [GraphQL enum value][1] to put into GraphQL
|
/// [Deprecation][2] of this [GraphQL enum value][1] to put into GraphQL
|
||||||
/// schema.
|
/// schema.
|
||||||
///
|
///
|
||||||
/// If the inner [`Option`] is [`None`], then [deprecation][2] has no
|
|
||||||
/// [reason][3] attached.
|
|
||||||
///
|
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
|
/// [1]: https://spec.graphql.org/October2021#sec-Enum-Value
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec--deprecated
|
/// [2]: https://spec.graphql.org/October2021#sec--deprecated
|
||||||
/// [3]: https://spec.graphql.org/October2021#sel-GAHnBZDACEDDGAA_6L
|
deprecated: Option<deprecation::Directive>,
|
||||||
deprecated: Option<Option<Box<str>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a [GraphQL enum][0] for code generation.
|
/// Representation of a [GraphQL enum][0] for code generation.
|
||||||
|
@ -373,7 +348,7 @@ struct Definition {
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Enums
|
/// [0]: https://spec.graphql.org/October2021#sec-Enums
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<Box<str>>,
|
description: Option<Description>,
|
||||||
|
|
||||||
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
||||||
/// for this [GraphQL enum][0].
|
/// for this [GraphQL enum][0].
|
||||||
|
@ -457,37 +432,17 @@ impl Definition {
|
||||||
let (_, ty_generics, _) = self.generics.split_for_impl();
|
let (_, ty_generics, _) = self.generics.split_for_impl();
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let description = self
|
let description = &self.description;
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|desc| quote! { .description(#desc) });
|
|
||||||
|
|
||||||
let variants_meta = self.values.iter().map(|v| {
|
let variants_meta = self.values.iter().map(|v| {
|
||||||
let name = &v.name;
|
let v_name = &v.name;
|
||||||
let description = v.description.as_ref().map_or_else(
|
let v_description = &v.description;
|
||||||
|| quote! { None },
|
let v_deprecation = &v.deprecated;
|
||||||
|desc| quote! { Some(String::from(#desc)) },
|
|
||||||
);
|
|
||||||
let deprecation_status = match &v.deprecated {
|
|
||||||
None => quote! { ::juniper::meta::DeprecationStatus::Current },
|
|
||||||
Some(None) => quote! {
|
|
||||||
::juniper::meta::DeprecationStatus::Deprecated(None)
|
|
||||||
},
|
|
||||||
Some(Some(reason)) => {
|
|
||||||
quote! {
|
|
||||||
::juniper::meta::DeprecationStatus::Deprecated(
|
|
||||||
Some(String::from(#reason))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
::juniper::meta::EnumValue {
|
::juniper::meta::EnumValue::new(#v_name)
|
||||||
name: String::from(#name),
|
#v_description
|
||||||
description: #description,
|
#v_deprecation
|
||||||
deprecation_status: #deprecation_status,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,16 +6,12 @@ use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens as _;
|
use quote::ToTokens as _;
|
||||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{diagnostic, rename, scalar, SpanContainer};
|
||||||
common::scalar,
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::{span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{ContainerAttr, Definition, FieldAttr, FieldDefinition};
|
use super::{ContainerAttr, Definition, FieldAttr, FieldDefinition};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[derive(GraphQLInputObject)]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLInputObject)]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::InputObjectDerive;
|
const ERR: diagnostic::Scope = diagnostic::Scope::InputObjectDerive;
|
||||||
|
|
||||||
/// Expands `#[derive(GraphQLInputObject)]` macro into generated code.
|
/// Expands `#[derive(GraphQLInputObject)]` macro into generated code.
|
||||||
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
@ -31,7 +27,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
let renaming = attr
|
let renaming = attr
|
||||||
.rename_fields
|
.rename_fields
|
||||||
.map(SpanContainer::into_inner)
|
.map(SpanContainer::into_inner)
|
||||||
.unwrap_or(RenameRule::CamelCase);
|
.unwrap_or(rename::Policy::CamelCase);
|
||||||
|
|
||||||
let is_internal = attr.is_internal;
|
let is_internal = attr.is_internal;
|
||||||
let fields = data
|
let fields = data
|
||||||
|
@ -73,8 +69,6 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
.context
|
.context
|
||||||
.map_or_else(|| parse_quote! { () }, SpanContainer::into_inner);
|
.map_or_else(|| parse_quote! { () }, SpanContainer::into_inner);
|
||||||
|
|
||||||
let description = attr.description.map(|d| d.into_inner().into_boxed_str());
|
|
||||||
|
|
||||||
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
|
let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics);
|
||||||
|
|
||||||
proc_macro_error::abort_if_dirty();
|
proc_macro_error::abort_if_dirty();
|
||||||
|
@ -83,7 +77,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
ident: ast.ident,
|
ident: ast.ident,
|
||||||
generics: ast.generics,
|
generics: ast.generics,
|
||||||
name,
|
name,
|
||||||
description,
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
context,
|
context,
|
||||||
scalar,
|
scalar,
|
||||||
fields,
|
fields,
|
||||||
|
@ -95,7 +89,11 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
/// Parses a [`FieldDefinition`] from the given struct field definition.
|
/// Parses a [`FieldDefinition`] from the given struct field definition.
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if the parsing fails.
|
/// Returns [`None`] if the parsing fails.
|
||||||
fn parse_field(f: &syn::Field, renaming: RenameRule, is_internal: bool) -> Option<FieldDefinition> {
|
fn parse_field(
|
||||||
|
f: &syn::Field,
|
||||||
|
renaming: rename::Policy,
|
||||||
|
is_internal: bool,
|
||||||
|
) -> Option<FieldDefinition> {
|
||||||
let field_attr = FieldAttr::from_attrs("graphql", &f.attrs)
|
let field_attr = FieldAttr::from_attrs("graphql", &f.attrs)
|
||||||
.map_err(|e| proc_macro_error::emit_error!(e))
|
.map_err(|e| proc_macro_error::emit_error!(e))
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
@ -113,17 +111,12 @@ fn parse_field(f: &syn::Field, renaming: RenameRule, is_internal: bool) -> Optio
|
||||||
ERR.no_double_underscore(f.span());
|
ERR.no_double_underscore(f.span());
|
||||||
}
|
}
|
||||||
|
|
||||||
let default = field_attr.default.map(SpanContainer::into_inner);
|
|
||||||
let description = field_attr
|
|
||||||
.description
|
|
||||||
.map(|d| d.into_inner().into_boxed_str());
|
|
||||||
|
|
||||||
Some(FieldDefinition {
|
Some(FieldDefinition {
|
||||||
ident: ident.clone(),
|
ident: ident.clone(),
|
||||||
ty: f.ty.clone(),
|
ty: f.ty.clone(),
|
||||||
default,
|
default: field_attr.default.map(SpanContainer::into_inner),
|
||||||
name,
|
name,
|
||||||
description,
|
description: field_attr.description.map(SpanContainer::into_inner),
|
||||||
ignored: field_attr.ignore.is_some(),
|
ignored: field_attr.ignore.is_some(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,13 @@ use syn::{
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
default, filter_attrs,
|
||||||
default,
|
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
ParseBufferExt as _,
|
ParseBufferExt as _,
|
||||||
},
|
},
|
||||||
scalar,
|
rename, scalar, Description, SpanContainer,
|
||||||
},
|
|
||||||
util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Available arguments behind `#[graphql]` attribute placed on a Rust struct
|
/// Available arguments behind `#[graphql]` attribute placed on a Rust struct
|
||||||
|
@ -48,7 +45,7 @@ struct ContainerAttr {
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
|
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<SpanContainer<String>>,
|
description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified type of [`Context`] to use for resolving this
|
/// Explicitly specified type of [`Context`] to use for resolving this
|
||||||
/// [GraphQL input object][0] type with.
|
/// [GraphQL input object][0] type with.
|
||||||
|
@ -71,14 +68,14 @@ struct ContainerAttr {
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
|
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
|
||||||
scalar: Option<SpanContainer<scalar::AttrValue>>,
|
scalar: Option<SpanContainer<scalar::AttrValue>>,
|
||||||
|
|
||||||
/// Explicitly specified [`RenameRule`] for all fields of this
|
/// Explicitly specified [`rename::Policy`] for all fields of this
|
||||||
/// [GraphQL input object][0].
|
/// [GraphQL input object][0].
|
||||||
///
|
///
|
||||||
/// If [`None`], then the [`RenameRule::CamelCase`] rule will be
|
/// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by
|
||||||
/// applied by default.
|
/// default.
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
|
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
|
||||||
rename_fields: Option<SpanContainer<RenameRule>>,
|
rename_fields: Option<SpanContainer<rename::Policy>>,
|
||||||
|
|
||||||
/// Indicator whether the generated code is intended to be used only inside
|
/// Indicator whether the generated code is intended to be used only inside
|
||||||
/// the [`juniper`] library.
|
/// the [`juniper`] library.
|
||||||
|
@ -104,13 +101,9 @@ impl Parse for ContainerAttr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
ident.span(),
|
|
||||||
Some(desc.span()),
|
|
||||||
desc.value(),
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"ctx" | "context" | "Context" => {
|
"ctx" | "context" | "Context" => {
|
||||||
|
@ -173,7 +166,7 @@ impl ContainerAttr {
|
||||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if attr.description.is_none() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs);
|
attr.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attr)
|
Ok(attr)
|
||||||
|
@ -212,7 +205,7 @@ struct FieldAttr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#InputValueDefinition
|
/// [1]: https://spec.graphql.org/October2021#InputValueDefinition
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<SpanContainer<String>>,
|
description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified marker for the Rust struct field to be ignored and
|
/// Explicitly specified marker for the Rust struct field to be ignored and
|
||||||
/// not included into the code generated for a [GraphQL input object][0]
|
/// not included into the code generated for a [GraphQL input object][0]
|
||||||
|
@ -251,13 +244,9 @@ impl Parse for FieldAttr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
ident.span(),
|
|
||||||
Some(desc.span()),
|
|
||||||
desc.value(),
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"ignore" | "skip" => out
|
"ignore" | "skip" => out
|
||||||
|
@ -294,7 +283,7 @@ impl FieldAttr {
|
||||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if attr.description.is_none() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs);
|
attr.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attr)
|
Ok(attr)
|
||||||
|
@ -337,7 +326,7 @@ struct FieldDefinition {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#InputValueDefinition
|
/// [1]: https://spec.graphql.org/October2021#InputValueDefinition
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<Box<str>>,
|
description: Option<Description>,
|
||||||
|
|
||||||
/// Indicator whether the Rust struct field behinds this
|
/// Indicator whether the Rust struct field behinds this
|
||||||
/// [GraphQL input object field][1] is being ignored and should not be
|
/// [GraphQL input object field][1] is being ignored and should not be
|
||||||
|
@ -378,7 +367,7 @@ struct Definition {
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
|
/// [0]: https://spec.graphql.org/October2021#sec-Input-Objects
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<Box<str>>,
|
description: Option<Description>,
|
||||||
|
|
||||||
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
||||||
/// for this [GraphQL input object][0].
|
/// for this [GraphQL input object][0].
|
||||||
|
@ -468,10 +457,7 @@ impl Definition {
|
||||||
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
let (impl_generics, _, where_clause) = generics.split_for_impl();
|
||||||
let (_, ty_generics, _) = self.generics.split_for_impl();
|
let (_, ty_generics, _) = self.generics.split_for_impl();
|
||||||
|
|
||||||
let description = self
|
let description = &self.description;
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|desc| quote! { .description(#desc) });
|
|
||||||
|
|
||||||
let fields = self.fields.iter().filter_map(|f| {
|
let fields = self.fields.iter().filter_map(|f| {
|
||||||
let ty = &f.ty;
|
let ty = &f.ty;
|
||||||
|
@ -483,10 +469,7 @@ impl Definition {
|
||||||
} else {
|
} else {
|
||||||
quote! { .arg::<#ty>(#name, info) }
|
quote! { .arg::<#ty>(#name, info) }
|
||||||
};
|
};
|
||||||
let description = f
|
let description = &f.description;
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|desc| quote! { .description(#desc) });
|
|
||||||
|
|
||||||
quote! { registry#arg#description }
|
quote! { registry#arg#description }
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,20 +6,16 @@ use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
diagnostic, field,
|
||||||
field,
|
|
||||||
parse::{self, TypeExt as _},
|
parse::{self, TypeExt as _},
|
||||||
scalar,
|
path_eq_single, rename, scalar, SpanContainer,
|
||||||
},
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::{path_eq_single, span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{enum_idents, Attr, Definition};
|
use super::{enum_idents, Attr, Definition};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[graphql_interface]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[graphql_interface]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::InterfaceAttr;
|
const ERR: diagnostic::Scope = diagnostic::Scope::InterfaceAttr;
|
||||||
|
|
||||||
/// Expands `#[graphql_interface]` macro into generated code.
|
/// Expands `#[graphql_interface]` macro into generated code.
|
||||||
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
@ -74,7 +70,7 @@ fn expand_on_trait(
|
||||||
.rename_fields
|
.rename_fields
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(RenameRule::CamelCase);
|
.unwrap_or(rename::Policy::CamelCase);
|
||||||
|
|
||||||
let fields = ast
|
let fields = ast
|
||||||
.items
|
.items
|
||||||
|
@ -121,7 +117,7 @@ fn expand_on_trait(
|
||||||
enum_ident,
|
enum_ident,
|
||||||
enum_alias_ident,
|
enum_alias_ident,
|
||||||
name,
|
name,
|
||||||
description: attr.description.map(|d| d.into_inner().into_boxed_str()),
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
context,
|
context,
|
||||||
scalar,
|
scalar,
|
||||||
fields,
|
fields,
|
||||||
|
@ -151,7 +147,7 @@ fn expand_on_trait(
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn parse_trait_method(
|
fn parse_trait_method(
|
||||||
method: &mut syn::TraitItemMethod,
|
method: &mut syn::TraitItemMethod,
|
||||||
renaming: &RenameRule,
|
renaming: &rename::Policy,
|
||||||
) -> Option<field::Definition> {
|
) -> Option<field::Definition> {
|
||||||
let method_ident = &method.sig.ident;
|
let method_ident = &method.sig.ident;
|
||||||
let method_attrs = method.attrs.clone();
|
let method_attrs = method.attrs.clone();
|
||||||
|
@ -205,17 +201,11 @@ fn parse_trait_method(
|
||||||
};
|
};
|
||||||
ty.lifetimes_anonymized();
|
ty.lifetimes_anonymized();
|
||||||
|
|
||||||
let description = attr.description.as_ref().map(|d| d.as_ref().value());
|
|
||||||
let deprecated = attr
|
|
||||||
.deprecated
|
|
||||||
.as_deref()
|
|
||||||
.map(|d| d.as_ref().map(syn::LitStr::value));
|
|
||||||
|
|
||||||
Some(field::Definition {
|
Some(field::Definition {
|
||||||
name,
|
name,
|
||||||
ty,
|
ty,
|
||||||
description,
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
deprecated,
|
deprecated: attr.deprecated.map(SpanContainer::into_inner),
|
||||||
ident: method_ident.clone(),
|
ident: method_ident.clone(),
|
||||||
arguments: Some(arguments),
|
arguments: Some(arguments),
|
||||||
has_receiver: method.sig.receiver().is_some(),
|
has_receiver: method.sig.receiver().is_some(),
|
||||||
|
@ -267,7 +257,7 @@ fn expand_on_derive_input(
|
||||||
.rename_fields
|
.rename_fields
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(RenameRule::CamelCase);
|
.unwrap_or(rename::Policy::CamelCase);
|
||||||
|
|
||||||
let fields = data
|
let fields = data
|
||||||
.fields
|
.fields
|
||||||
|
@ -308,7 +298,7 @@ fn expand_on_derive_input(
|
||||||
enum_ident,
|
enum_ident,
|
||||||
enum_alias_ident,
|
enum_alias_ident,
|
||||||
name,
|
name,
|
||||||
description: attr.description.map(|d| d.into_inner().into_boxed_str()),
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
context,
|
context,
|
||||||
scalar,
|
scalar,
|
||||||
fields,
|
fields,
|
||||||
|
@ -337,7 +327,10 @@ fn expand_on_derive_input(
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if the parsing fails, or the struct field is ignored.
|
/// Returns [`None`] if the parsing fails, or the struct field is ignored.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn parse_struct_field(field: &mut syn::Field, renaming: &RenameRule) -> Option<field::Definition> {
|
fn parse_struct_field(
|
||||||
|
field: &mut syn::Field,
|
||||||
|
renaming: &rename::Policy,
|
||||||
|
) -> Option<field::Definition> {
|
||||||
let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?;
|
let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?;
|
||||||
let field_attrs = field.attrs.clone();
|
let field_attrs = field.attrs.clone();
|
||||||
|
|
||||||
|
@ -373,17 +366,11 @@ fn parse_struct_field(field: &mut syn::Field, renaming: &RenameRule) -> Option<f
|
||||||
let mut ty = field.ty.clone();
|
let mut ty = field.ty.clone();
|
||||||
ty.lifetimes_anonymized();
|
ty.lifetimes_anonymized();
|
||||||
|
|
||||||
let description = attr.description.as_ref().map(|d| d.as_ref().value());
|
|
||||||
let deprecated = attr
|
|
||||||
.deprecated
|
|
||||||
.as_deref()
|
|
||||||
.map(|d| d.as_ref().map(syn::LitStr::value));
|
|
||||||
|
|
||||||
Some(field::Definition {
|
Some(field::Definition {
|
||||||
name,
|
name,
|
||||||
ty,
|
ty,
|
||||||
description,
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
deprecated,
|
deprecated: attr.deprecated.map(SpanContainer::into_inner),
|
||||||
ident: field_ident.clone(),
|
ident: field_ident.clone(),
|
||||||
arguments: None,
|
arguments: None,
|
||||||
has_receiver: false,
|
has_receiver: false,
|
||||||
|
|
|
@ -4,16 +4,12 @@ use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens as _;
|
use quote::ToTokens as _;
|
||||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{diagnostic, field, parse::TypeExt as _, rename, scalar, SpanContainer};
|
||||||
common::{field, parse::TypeExt as _, scalar},
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::{span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{attr::err_unnamed_field, enum_idents, Attr, Definition};
|
use super::{attr::err_unnamed_field, enum_idents, Attr, Definition};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[derive(GraphQLInterface)]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLInterface)]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::InterfaceDerive;
|
const ERR: diagnostic::Scope = diagnostic::Scope::InterfaceDerive;
|
||||||
|
|
||||||
/// Expands `#[derive(GraphQLInterface)]` macro into generated code.
|
/// Expands `#[derive(GraphQLInterface)]` macro into generated code.
|
||||||
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
@ -52,7 +48,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
.rename_fields
|
.rename_fields
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(RenameRule::CamelCase);
|
.unwrap_or(rename::Policy::CamelCase);
|
||||||
|
|
||||||
let fields = data
|
let fields = data
|
||||||
.fields
|
.fields
|
||||||
|
@ -94,7 +90,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
enum_ident,
|
enum_ident,
|
||||||
enum_alias_ident,
|
enum_alias_ident,
|
||||||
name,
|
name,
|
||||||
description: attr.description.map(|d| d.into_inner().into_boxed_str()),
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
context,
|
context,
|
||||||
scalar,
|
scalar,
|
||||||
fields,
|
fields,
|
||||||
|
@ -118,7 +114,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if the parsing fails, or the struct field is ignored.
|
/// Returns [`None`] if the parsing fails, or the struct field is ignored.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option<field::Definition> {
|
fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option<field::Definition> {
|
||||||
let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?;
|
let field_ident = field.ident.as_ref().or_else(|| err_unnamed_field(&field))?;
|
||||||
|
|
||||||
let attr = field::Attr::from_attrs("graphql", &field.attrs)
|
let attr = field::Attr::from_attrs("graphql", &field.attrs)
|
||||||
|
@ -147,17 +143,11 @@ fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option<field::Defin
|
||||||
let mut ty = field.ty.clone();
|
let mut ty = field.ty.clone();
|
||||||
ty.lifetimes_anonymized();
|
ty.lifetimes_anonymized();
|
||||||
|
|
||||||
let description = attr.description.as_ref().map(|d| d.as_ref().value());
|
|
||||||
let deprecated = attr
|
|
||||||
.deprecated
|
|
||||||
.as_deref()
|
|
||||||
.map(|d| d.as_ref().map(syn::LitStr::value));
|
|
||||||
|
|
||||||
Some(field::Definition {
|
Some(field::Definition {
|
||||||
name,
|
name,
|
||||||
ty,
|
ty,
|
||||||
description,
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
deprecated,
|
deprecated: attr.deprecated.map(SpanContainer::into_inner),
|
||||||
ident: field_ident.clone(),
|
ident: field_ident.clone(),
|
||||||
arguments: None,
|
arguments: None,
|
||||||
has_receiver: false,
|
has_receiver: false,
|
||||||
|
|
|
@ -19,16 +19,13 @@ use syn::{
|
||||||
visit::Visit,
|
visit::Visit,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
field, filter_attrs, gen,
|
||||||
field, gen,
|
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
GenericsExt as _, ParseBufferExt as _,
|
GenericsExt as _, ParseBufferExt as _,
|
||||||
},
|
},
|
||||||
scalar,
|
rename, scalar, Description, SpanContainer,
|
||||||
},
|
|
||||||
util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns [`syn::Ident`]s for a generic enum deriving [`Clone`] and [`Copy`]
|
/// Returns [`syn::Ident`]s for a generic enum deriving [`Clone`] and [`Copy`]
|
||||||
|
@ -71,7 +68,7 @@ struct Attr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
|
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<SpanContainer<String>>,
|
description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified identifier of the type alias of Rust enum type
|
/// Explicitly specified identifier of the type alias of Rust enum type
|
||||||
/// behind the trait or struct, being an actual implementation of a
|
/// behind the trait or struct, being an actual implementation of a
|
||||||
|
@ -126,13 +123,14 @@ struct Attr {
|
||||||
/// it contains async methods.
|
/// it contains async methods.
|
||||||
asyncness: Option<SpanContainer<syn::Ident>>,
|
asyncness: Option<SpanContainer<syn::Ident>>,
|
||||||
|
|
||||||
/// Explicitly specified [`RenameRule`] for all fields of this
|
/// Explicitly specified [`rename::Policy`] for all fields of this
|
||||||
/// [GraphQL interface][1] type.
|
/// [GraphQL interface][1] type.
|
||||||
///
|
///
|
||||||
/// If [`None`] then the default rule will be [`RenameRule::CamelCase`].
|
/// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by
|
||||||
|
/// default.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
|
/// [1]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
rename_fields: Option<SpanContainer<RenameRule>>,
|
rename_fields: Option<SpanContainer<rename::Policy>>,
|
||||||
|
|
||||||
/// Indicator whether the generated code is intended to be used only inside
|
/// Indicator whether the generated code is intended to be used only inside
|
||||||
/// the [`juniper`] library.
|
/// the [`juniper`] library.
|
||||||
|
@ -158,13 +156,9 @@ impl Parse for Attr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
ident.span(),
|
|
||||||
Some(desc.span()),
|
|
||||||
desc.value(),
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"ctx" | "context" | "Context" => {
|
"ctx" | "context" | "Context" => {
|
||||||
|
@ -268,7 +262,7 @@ impl Attr {
|
||||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if attr.description.is_none() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs);
|
attr.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attr)
|
Ok(attr)
|
||||||
|
@ -312,7 +306,7 @@ struct Definition {
|
||||||
/// Description of this [GraphQL interface][0] to put into GraphQL schema.
|
/// Description of this [GraphQL interface][0] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
/// [0]: https://spec.graphql.org/October2021#sec-Interfaces
|
/// [0]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
description: Option<Box<str>>,
|
description: Option<Description>,
|
||||||
|
|
||||||
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
||||||
/// for this [GraphQL interface][1].
|
/// for this [GraphQL interface][1].
|
||||||
|
@ -690,10 +684,7 @@ impl Definition {
|
||||||
let (_, ty_generics, _) = self.generics.split_for_impl();
|
let (_, ty_generics, _) = self.generics.split_for_impl();
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let description = self
|
let description = &self.description;
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|desc| quote! { .description(#desc) });
|
|
||||||
|
|
||||||
// Sorting is required to preserve/guarantee the order of implementers registered in schema.
|
// Sorting is required to preserve/guarantee the order of implementers registered in schema.
|
||||||
let mut implemented_for = self.implemented_for.clone();
|
let mut implemented_for = self.implemented_for.clone();
|
||||||
|
|
|
@ -6,20 +6,16 @@ use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
diagnostic, field,
|
||||||
field,
|
|
||||||
parse::{self, TypeExt as _},
|
parse::{self, TypeExt as _},
|
||||||
scalar,
|
path_eq_single, rename, scalar, SpanContainer,
|
||||||
},
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::{path_eq_single, span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{Attr, Definition, Query};
|
use super::{Attr, Definition, Query};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[graphql_object]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[graphql_object]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::ObjectAttr;
|
const ERR: diagnostic::Scope = diagnostic::Scope::ObjectAttr;
|
||||||
|
|
||||||
/// Expands `#[graphql_object]` macro into generated code.
|
/// Expands `#[graphql_object]` macro into generated code.
|
||||||
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
@ -73,7 +69,7 @@ where
|
||||||
.rename_fields
|
.rename_fields
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(RenameRule::CamelCase);
|
.unwrap_or(rename::Policy::CamelCase);
|
||||||
|
|
||||||
let async_only = TypeId::of::<Operation>() != TypeId::of::<Query>();
|
let async_only = TypeId::of::<Operation>() != TypeId::of::<Query>();
|
||||||
let fields: Vec<_> = ast
|
let fields: Vec<_> = ast
|
||||||
|
@ -143,7 +139,7 @@ where
|
||||||
fn parse_field(
|
fn parse_field(
|
||||||
method: &mut syn::ImplItemMethod,
|
method: &mut syn::ImplItemMethod,
|
||||||
async_only: bool,
|
async_only: bool,
|
||||||
renaming: &RenameRule,
|
renaming: &rename::Policy,
|
||||||
) -> Option<field::Definition> {
|
) -> Option<field::Definition> {
|
||||||
let method_attrs = method.attrs.clone();
|
let method_attrs = method.attrs.clone();
|
||||||
|
|
||||||
|
@ -216,17 +212,11 @@ fn parse_field(
|
||||||
};
|
};
|
||||||
ty.lifetimes_anonymized();
|
ty.lifetimes_anonymized();
|
||||||
|
|
||||||
let description = attr.description.as_ref().map(|d| d.as_ref().value());
|
|
||||||
let deprecated = attr
|
|
||||||
.deprecated
|
|
||||||
.as_deref()
|
|
||||||
.map(|d| d.as_ref().map(syn::LitStr::value));
|
|
||||||
|
|
||||||
Some(field::Definition {
|
Some(field::Definition {
|
||||||
name,
|
name,
|
||||||
ty,
|
ty,
|
||||||
description,
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
deprecated,
|
deprecated: attr.deprecated.map(SpanContainer::into_inner),
|
||||||
ident: method_ident.clone(),
|
ident: method_ident.clone(),
|
||||||
arguments: Some(arguments),
|
arguments: Some(arguments),
|
||||||
has_receiver: method.sig.receiver().is_some(),
|
has_receiver: method.sig.receiver().is_some(),
|
||||||
|
|
|
@ -7,16 +7,12 @@ use proc_macro_error::ResultExt as _;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
|
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{diagnostic, field, parse::TypeExt as _, rename, scalar, SpanContainer};
|
||||||
common::{field, parse::TypeExt as _, scalar},
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::{span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{Attr, Definition, Query};
|
use super::{Attr, Definition, Query};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[derive(GraphQLObject)]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLObject)]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::ObjectDerive;
|
const ERR: diagnostic::Scope = diagnostic::Scope::ObjectDerive;
|
||||||
|
|
||||||
/// Expands `#[derive(GraphQLObject)]` macro into generated code.
|
/// Expands `#[derive(GraphQLObject)]` macro into generated code.
|
||||||
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
@ -62,7 +58,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition<Query>> {
|
||||||
.rename_fields
|
.rename_fields
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(RenameRule::CamelCase);
|
.unwrap_or(rename::Policy::CamelCase);
|
||||||
|
|
||||||
let mut fields = vec![];
|
let mut fields = vec![];
|
||||||
if let syn::Data::Struct(data) = &ast.data {
|
if let syn::Data::Struct(data) = &ast.data {
|
||||||
|
@ -112,7 +108,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition<Query>> {
|
||||||
///
|
///
|
||||||
/// Returns [`None`] if parsing fails, or the struct field is ignored.
|
/// Returns [`None`] if parsing fails, or the struct field is ignored.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option<field::Definition> {
|
fn parse_field(field: &syn::Field, renaming: &rename::Policy) -> Option<field::Definition> {
|
||||||
let attr = field::Attr::from_attrs("graphql", &field.attrs)
|
let attr = field::Attr::from_attrs("graphql", &field.attrs)
|
||||||
.map_err(|e| proc_macro_error::emit_error!(e))
|
.map_err(|e| proc_macro_error::emit_error!(e))
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
@ -141,17 +137,11 @@ fn parse_field(field: &syn::Field, renaming: &RenameRule) -> Option<field::Defin
|
||||||
let mut ty = field.ty.unparenthesized().clone();
|
let mut ty = field.ty.unparenthesized().clone();
|
||||||
ty.lifetimes_anonymized();
|
ty.lifetimes_anonymized();
|
||||||
|
|
||||||
let description = attr.description.as_ref().map(|d| d.as_ref().value());
|
|
||||||
let deprecated = attr
|
|
||||||
.deprecated
|
|
||||||
.as_deref()
|
|
||||||
.map(|d| d.as_ref().map(syn::LitStr::value));
|
|
||||||
|
|
||||||
Some(field::Definition {
|
Some(field::Definition {
|
||||||
name,
|
name,
|
||||||
ty,
|
ty,
|
||||||
description,
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
deprecated,
|
deprecated: attr.deprecated.map(SpanContainer::into_inner),
|
||||||
ident: field_ident.clone(),
|
ident: field_ident.clone(),
|
||||||
arguments: None,
|
arguments: None,
|
||||||
has_receiver: false,
|
has_receiver: false,
|
||||||
|
|
|
@ -10,24 +10,21 @@ use std::{any::TypeId, collections::HashSet, convert::TryInto as _, marker::Phan
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::{
|
use syn::{
|
||||||
|
ext::IdentExt as _,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
parse_quote,
|
parse_quote,
|
||||||
spanned::Spanned as _,
|
spanned::Spanned as _,
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
field, filter_attrs, gen,
|
||||||
field, gen,
|
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
GenericsExt as _, ParseBufferExt as _, TypeExt,
|
GenericsExt as _, ParseBufferExt as _, TypeExt,
|
||||||
},
|
},
|
||||||
scalar,
|
rename, scalar, Description, SpanContainer,
|
||||||
},
|
|
||||||
util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule},
|
|
||||||
};
|
};
|
||||||
use syn::ext::IdentExt;
|
|
||||||
|
|
||||||
/// Available arguments behind `#[graphql]` (or `#[graphql_object]`) attribute
|
/// Available arguments behind `#[graphql]` (or `#[graphql_object]`) attribute
|
||||||
/// when generating code for [GraphQL object][1] type.
|
/// when generating code for [GraphQL object][1] type.
|
||||||
|
@ -49,7 +46,7 @@ pub(crate) struct Attr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Objects
|
/// [1]: https://spec.graphql.org/October2021#sec-Objects
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
pub(crate) description: Option<SpanContainer<String>>,
|
pub(crate) description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified type of [`Context`] to use for resolving this
|
/// Explicitly specified type of [`Context`] to use for resolving this
|
||||||
/// [GraphQL object][1] type with.
|
/// [GraphQL object][1] type with.
|
||||||
|
@ -81,13 +78,14 @@ pub(crate) struct Attr {
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
|
/// [2]: https://spec.graphql.org/October2021#sec-Interfaces
|
||||||
pub(crate) interfaces: HashSet<SpanContainer<syn::Type>>,
|
pub(crate) interfaces: HashSet<SpanContainer<syn::Type>>,
|
||||||
|
|
||||||
/// Explicitly specified [`RenameRule`] for all fields of this
|
/// Explicitly specified [`rename::Policy`] for all fields of this
|
||||||
/// [GraphQL object][1] type.
|
/// [GraphQL object][1] type.
|
||||||
///
|
///
|
||||||
/// If [`None`] then the default rule will be [`RenameRule::CamelCase`].
|
/// If [`None`], then the [`rename::Policy::CamelCase`] will be applied by
|
||||||
|
/// default.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Objects
|
/// [1]: https://spec.graphql.org/October2021#sec-Objects
|
||||||
pub(crate) rename_fields: Option<SpanContainer<RenameRule>>,
|
pub(crate) rename_fields: Option<SpanContainer<rename::Policy>>,
|
||||||
|
|
||||||
/// Indicator whether the generated code is intended to be used only inside
|
/// Indicator whether the generated code is intended to be used only inside
|
||||||
/// the [`juniper`] library.
|
/// the [`juniper`] library.
|
||||||
|
@ -113,13 +111,9 @@ impl Parse for Attr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
ident.span(),
|
|
||||||
Some(desc.span()),
|
|
||||||
desc.value(),
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"ctx" | "context" | "Context" => {
|
"ctx" | "context" | "Context" => {
|
||||||
|
@ -195,7 +189,7 @@ impl Attr {
|
||||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if attr.description.is_none() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs);
|
attr.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attr)
|
Ok(attr)
|
||||||
|
@ -228,7 +222,7 @@ pub(crate) struct Definition<Operation: ?Sized> {
|
||||||
/// Description of this [GraphQL object][1] to put into GraphQL schema.
|
/// Description of this [GraphQL object][1] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Objects
|
/// [1]: https://spec.graphql.org/October2021#sec-Objects
|
||||||
pub(crate) description: Option<String>,
|
pub(crate) description: Option<Description>,
|
||||||
|
|
||||||
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
||||||
/// for this [GraphQL object][1].
|
/// for this [GraphQL object][1].
|
||||||
|
@ -437,10 +431,7 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
|
||||||
let ty = &self.ty;
|
let ty = &self.ty;
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let description = self
|
let description = &self.description;
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|desc| quote! { .description(#desc) });
|
|
||||||
|
|
||||||
let extract_stream_type = TypeId::of::<Operation>() != TypeId::of::<Query>();
|
let extract_stream_type = TypeId::of::<Operation>() != TypeId::of::<Query>();
|
||||||
let fields_meta = self
|
let fields_meta = self
|
||||||
|
|
|
@ -4,15 +4,12 @@ use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse_quote, spanned::Spanned};
|
use syn::{parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{diagnostic, parse, scalar, SpanContainer};
|
||||||
common::{parse, scalar},
|
|
||||||
graphql_scalar::TypeOrIdent,
|
|
||||||
GraphQLScope,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken};
|
use super::{derive::parse_derived_methods, Attr, Definition, Methods, ParseToken, TypeOrIdent};
|
||||||
|
|
||||||
const ERR: GraphQLScope = GraphQLScope::ScalarAttr;
|
/// [`diagnostic::Scope`] of errors for `#[graphql_scalar]` macro.
|
||||||
|
const ERR: diagnostic::Scope = diagnostic::Scope::ScalarAttr;
|
||||||
|
|
||||||
/// Expands `#[graphql_scalar]` macro into generated code.
|
/// Expands `#[graphql_scalar]` macro into generated code.
|
||||||
pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
pub(crate) fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
@ -58,11 +55,10 @@ fn expand_on_type_alias(
|
||||||
methods,
|
methods,
|
||||||
name: attr
|
name: attr
|
||||||
.name
|
.name
|
||||||
.as_deref()
|
.map(SpanContainer::into_inner)
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| ast.ident.to_string()),
|
.unwrap_or_else(|| ast.ident.to_string()),
|
||||||
description: attr.description.as_deref().cloned(),
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
specified_by_url: attr.specified_by_url.as_deref().cloned(),
|
specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
|
||||||
scalar,
|
scalar,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -90,11 +86,10 @@ fn expand_on_derive_input(
|
||||||
methods,
|
methods,
|
||||||
name: attr
|
name: attr
|
||||||
.name
|
.name
|
||||||
.as_deref()
|
.map(SpanContainer::into_inner)
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| ast.ident.to_string()),
|
.unwrap_or_else(|| ast.ident.to_string()),
|
||||||
description: attr.description.as_deref().cloned(),
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
specified_by_url: attr.specified_by_url.as_deref().cloned(),
|
specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
|
||||||
scalar,
|
scalar,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@ use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::{parse_quote, spanned::Spanned};
|
use syn::{parse_quote, spanned::Spanned};
|
||||||
|
|
||||||
use crate::{common::scalar, result::GraphQLScope};
|
use crate::common::{diagnostic, scalar, SpanContainer};
|
||||||
|
|
||||||
use super::{Attr, Definition, Field, Methods, ParseToken, TypeOrIdent};
|
use super::{Attr, Definition, Field, Methods, ParseToken, TypeOrIdent};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[derive(GraphQLScalar)]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLScalar)]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::ScalarDerive;
|
const ERR: diagnostic::Scope = diagnostic::Scope::ScalarDerive;
|
||||||
|
|
||||||
/// Expands `#[derive(GraphQLScalar)]` macro into generated code.
|
/// Expands `#[derive(GraphQLScalar)]` macro into generated code.
|
||||||
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
@ -27,11 +27,10 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
methods,
|
methods,
|
||||||
name: attr
|
name: attr
|
||||||
.name
|
.name
|
||||||
.as_deref()
|
.map(SpanContainer::into_inner)
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| ast.ident.to_string()),
|
.unwrap_or_else(|| ast.ident.to_string()),
|
||||||
description: attr.description.as_deref().cloned(),
|
description: attr.description.map(SpanContainer::into_inner),
|
||||||
specified_by_url: attr.specified_by_url.as_deref().cloned(),
|
specified_by_url: attr.specified_by_url.map(SpanContainer::into_inner),
|
||||||
scalar,
|
scalar,
|
||||||
}
|
}
|
||||||
.to_token_stream())
|
.to_token_stream())
|
||||||
|
|
|
@ -14,15 +14,13 @@ use syn::{
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
filter_attrs,
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
ParseBufferExt as _,
|
ParseBufferExt as _,
|
||||||
},
|
},
|
||||||
scalar,
|
scalar, Description, SpanContainer,
|
||||||
},
|
|
||||||
util::{filter_attrs, get_doc_comment, span_container::SpanContainer},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod attr;
|
pub mod attr;
|
||||||
|
@ -42,7 +40,7 @@ struct Attr {
|
||||||
/// Description of this [GraphQL scalar][1] to put into GraphQL schema.
|
/// Description of this [GraphQL scalar][1] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||||
description: Option<SpanContainer<String>>,
|
description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema.
|
/// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
|
@ -112,13 +110,9 @@ impl Parse for Attr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
ident.span(),
|
|
||||||
Some(desc.span()),
|
|
||||||
desc.value(),
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"specified_by_url" => {
|
"specified_by_url" => {
|
||||||
|
@ -255,7 +249,7 @@ impl Attr {
|
||||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if attr.description.is_none() {
|
if attr.description.is_none() {
|
||||||
attr.description = get_doc_comment(attrs);
|
attr.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attr)
|
Ok(attr)
|
||||||
|
@ -304,7 +298,7 @@ struct Definition {
|
||||||
/// Description of this [GraphQL scalar][1] to put into GraphQL schema.
|
/// Description of this [GraphQL scalar][1] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||||
description: Option<String>,
|
description: Option<Description>,
|
||||||
|
|
||||||
/// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema.
|
/// Spec [`Url`] of this [GraphQL scalar][1] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
|
@ -365,12 +359,9 @@ impl Definition {
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
/// [1]: https://spec.graphql.org/October2021#sec-Scalars
|
||||||
fn impl_type_tokens(&self) -> TokenStream {
|
fn impl_type_tokens(&self) -> TokenStream {
|
||||||
let scalar = &self.scalar;
|
let scalar = &self.scalar;
|
||||||
let name = &self.name;
|
|
||||||
|
|
||||||
let description = self
|
let name = &self.name;
|
||||||
.description
|
let description = &self.description;
|
||||||
.as_ref()
|
|
||||||
.map(|val| quote! { .description(#val) });
|
|
||||||
let specified_by_url = self.specified_by_url.as_ref().map(|url| {
|
let specified_by_url = self.specified_by_url.as_ref().map(|url| {
|
||||||
let url_lit = url.as_str();
|
let url_lit = url.as_str();
|
||||||
quote! { .specified_by_url(#url_lit) }
|
quote! { .specified_by_url(#url_lit) }
|
||||||
|
|
|
@ -6,19 +6,15 @@ use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, ToTokens as _};
|
use quote::{quote, ToTokens as _};
|
||||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
|
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{diagnostic, parse, path_eq_single, scalar, SpanContainer};
|
||||||
common::{parse, scalar},
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::{path_eq_single, span_container::SpanContainer},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr,
|
all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr,
|
||||||
VariantDefinition,
|
VariantDefinition,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[graphql_union]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[graphql_union]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::UnionAttr;
|
const ERR: diagnostic::Scope = diagnostic::Scope::UnionAttr;
|
||||||
|
|
||||||
/// Expands `#[graphql_union]` macro into generated code.
|
/// Expands `#[graphql_union]` macro into generated code.
|
||||||
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
|
|
@ -5,19 +5,15 @@ use proc_macro_error::ResultExt as _;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields};
|
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _, Data, Fields};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{diagnostic, parse::TypeExt as _, scalar, SpanContainer};
|
||||||
common::{parse::TypeExt as _, scalar},
|
|
||||||
result::GraphQLScope,
|
|
||||||
util::span_container::SpanContainer,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr,
|
all_variants_different, emerge_union_variants_from_attr, Attr, Definition, VariantAttr,
|
||||||
VariantDefinition,
|
VariantDefinition,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[derive(GraphQLUnion)]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[derive(GraphQLUnion)]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::UnionDerive;
|
const ERR: diagnostic::Scope = diagnostic::Scope::UnionDerive;
|
||||||
|
|
||||||
/// Expands `#[derive(GraphQLUnion)]` macro into generated code.
|
/// Expands `#[derive(GraphQLUnion)]` macro into generated code.
|
||||||
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
|
|
@ -17,16 +17,13 @@ use syn::{
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::{
|
filter_attrs, gen,
|
||||||
gen,
|
|
||||||
parse::{
|
parse::{
|
||||||
attr::{err, OptionExt as _},
|
attr::{err, OptionExt as _},
|
||||||
ParseBufferExt as _,
|
ParseBufferExt as _,
|
||||||
},
|
},
|
||||||
scalar,
|
scalar, Description, SpanContainer,
|
||||||
},
|
|
||||||
util::{filter_attrs, get_doc_comment, span_container::SpanContainer},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper alias for the type of [`Attr::external_resolvers`] field.
|
/// Helper alias for the type of [`Attr::external_resolvers`] field.
|
||||||
|
@ -52,7 +49,7 @@ struct Attr {
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Unions
|
/// [1]: https://spec.graphql.org/October2021#sec-Unions
|
||||||
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
/// [2]: https://spec.graphql.org/October2021#sec-Descriptions
|
||||||
description: Option<SpanContainer<String>>,
|
description: Option<SpanContainer<Description>>,
|
||||||
|
|
||||||
/// Explicitly specified type of [`Context`] to use for resolving this
|
/// Explicitly specified type of [`Context`] to use for resolving this
|
||||||
/// [GraphQL union][1] type with.
|
/// [GraphQL union][1] type with.
|
||||||
|
@ -112,13 +109,9 @@ impl Parse for Attr {
|
||||||
}
|
}
|
||||||
"desc" | "description" => {
|
"desc" | "description" => {
|
||||||
input.parse::<token::Eq>()?;
|
input.parse::<token::Eq>()?;
|
||||||
let desc = input.parse::<syn::LitStr>()?;
|
let desc = input.parse::<Description>()?;
|
||||||
out.description
|
out.description
|
||||||
.replace(SpanContainer::new(
|
.replace(SpanContainer::new(ident.span(), Some(desc.span()), desc))
|
||||||
ident.span(),
|
|
||||||
Some(desc.span()),
|
|
||||||
desc.value(),
|
|
||||||
))
|
|
||||||
.none_or_else(|_| err::dup_arg(&ident))?
|
.none_or_else(|_| err::dup_arg(&ident))?
|
||||||
}
|
}
|
||||||
"ctx" | "context" | "Context" => {
|
"ctx" | "context" | "Context" => {
|
||||||
|
@ -182,7 +175,7 @@ impl Attr {
|
||||||
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
.try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?;
|
||||||
|
|
||||||
if meta.description.is_none() {
|
if meta.description.is_none() {
|
||||||
meta.description = get_doc_comment(attrs);
|
meta.description = Description::parse_from_doc_attrs(attrs)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(meta)
|
Ok(meta)
|
||||||
|
@ -284,7 +277,7 @@ struct Definition {
|
||||||
/// Description of this [GraphQL union][1] to put into GraphQL schema.
|
/// Description of this [GraphQL union][1] to put into GraphQL schema.
|
||||||
///
|
///
|
||||||
/// [1]: https://spec.graphql.org/October2021#sec-Unions
|
/// [1]: https://spec.graphql.org/October2021#sec-Unions
|
||||||
description: Option<String>,
|
description: Option<Description>,
|
||||||
|
|
||||||
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
/// Rust type of [`Context`] to generate [`GraphQLType`] implementation with
|
||||||
/// for this [GraphQL union][1].
|
/// for this [GraphQL union][1].
|
||||||
|
@ -471,10 +464,7 @@ impl Definition {
|
||||||
let (impl_generics, ty_full, where_clause) = self.impl_generics(false);
|
let (impl_generics, ty_full, where_clause) = self.impl_generics(false);
|
||||||
|
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let description = self
|
let description = &self.description;
|
||||||
.description
|
|
||||||
.as_ref()
|
|
||||||
.map(|desc| quote! { .description(#desc) });
|
|
||||||
|
|
||||||
let variant_tys = self.variants.iter().map(|var| &var.ty);
|
let variant_tys = self.variants.iter().map(|var| &var.ty);
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
#![recursion_limit = "1024"]
|
#![recursion_limit = "1024"]
|
||||||
|
|
||||||
mod result;
|
|
||||||
mod util;
|
|
||||||
|
|
||||||
// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust
|
// NOTICE: Unfortunately this macro MUST be defined here, in the crate's root module, because Rust
|
||||||
// doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently,
|
// doesn't allow to export `macro_rules!` macros from a `proc-macro` crate type currently,
|
||||||
// and so we cannot move the definition into a sub-module and use the `#[macro_export]`
|
// and so we cannot move the definition into a sub-module and use the `#[macro_export]`
|
||||||
|
@ -16,8 +13,8 @@ mod util;
|
||||||
/// By default, [`SpanContainer::span_ident`] is used.
|
/// By default, [`SpanContainer::span_ident`] is used.
|
||||||
///
|
///
|
||||||
/// [`Span`]: proc_macro2::Span
|
/// [`Span`]: proc_macro2::Span
|
||||||
/// [`SpanContainer`]: crate::util::span_container::SpanContainer
|
/// [`SpanContainer`]: crate::common::SpanContainer
|
||||||
/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
|
/// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident
|
||||||
macro_rules! try_merge_opt {
|
macro_rules! try_merge_opt {
|
||||||
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
||||||
if let Some(v) = $self.$field {
|
if let Some(v) = $self.$field {
|
||||||
|
@ -47,8 +44,8 @@ macro_rules! try_merge_opt {
|
||||||
///
|
///
|
||||||
/// [`HashMap`]: std::collections::HashMap
|
/// [`HashMap`]: std::collections::HashMap
|
||||||
/// [`Span`]: proc_macro2::Span
|
/// [`Span`]: proc_macro2::Span
|
||||||
/// [`SpanContainer`]: crate::util::span_container::SpanContainer
|
/// [`SpanContainer`]: crate::common::SpanContainer
|
||||||
/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
|
/// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident
|
||||||
macro_rules! try_merge_hashmap {
|
macro_rules! try_merge_hashmap {
|
||||||
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
||||||
if !$self.$field.is_empty() {
|
if !$self.$field.is_empty() {
|
||||||
|
@ -80,8 +77,8 @@ macro_rules! try_merge_hashmap {
|
||||||
///
|
///
|
||||||
/// [`HashSet`]: std::collections::HashSet
|
/// [`HashSet`]: std::collections::HashSet
|
||||||
/// [`Span`]: proc_macro2::Span
|
/// [`Span`]: proc_macro2::Span
|
||||||
/// [`SpanContainer`]: crate::util::span_container::SpanContainer
|
/// [`SpanContainer`]: crate::common::SpanContainer
|
||||||
/// [`SpanContainer::span_ident`]: crate::util::span_container::SpanContainer::span_ident
|
/// [`SpanContainer::span_ident`]: crate::common::SpanContainer::span_ident
|
||||||
macro_rules! try_merge_hashset {
|
macro_rules! try_merge_hashset {
|
||||||
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
($field:ident: $self:ident, $another:ident => $span:ident) => {{
|
||||||
if !$self.$field.is_empty() {
|
if !$self.$field.is_empty() {
|
||||||
|
@ -112,7 +109,6 @@ mod scalar_value;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro_error::{proc_macro_error, ResultExt as _};
|
use proc_macro_error::{proc_macro_error, ResultExt as _};
|
||||||
use result::GraphQLScope;
|
|
||||||
|
|
||||||
/// `#[derive(GraphQLInputObject)]` macro for deriving a
|
/// `#[derive(GraphQLInputObject)]` macro for deriving a
|
||||||
/// [GraphQL input object][0] implementation for a Rust struct. Each
|
/// [GraphQL input object][0] implementation for a Rust struct. Each
|
||||||
|
@ -257,6 +253,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
|
||||||
/// attribute's argument, or with regular a Rust `#[deprecated]` attribute.
|
/// attribute's argument, or with regular a Rust `#[deprecated]` attribute.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # #![allow(deprecated)]
|
||||||
|
/// #
|
||||||
/// # use juniper::GraphQLEnum;
|
/// # use juniper::GraphQLEnum;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(GraphQLEnum)]
|
/// #[derive(GraphQLEnum)]
|
||||||
|
|
|
@ -12,14 +12,14 @@ use syn::{
|
||||||
visit::Visit,
|
visit::Visit,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::common::{
|
||||||
common::parse::{attr::err, ParseBufferExt as _},
|
diagnostic, filter_attrs,
|
||||||
util::{filter_attrs, span_container::SpanContainer},
|
parse::{attr::err, ParseBufferExt as _},
|
||||||
GraphQLScope,
|
SpanContainer,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// [`GraphQLScope`] of errors for `#[derive(ScalarValue)]` macro.
|
/// [`diagnostic::Scope`] of errors for `#[derive(ScalarValue)]` macro.
|
||||||
const ERR: GraphQLScope = GraphQLScope::ScalarValueDerive;
|
const ERR: diagnostic::Scope = diagnostic::Scope::ScalarValueDerive;
|
||||||
|
|
||||||
/// Expands `#[derive(ScalarValue)]` macro into generated code.
|
/// Expands `#[derive(ScalarValue)]` macro into generated code.
|
||||||
pub fn expand_derive(input: TokenStream) -> syn::Result<TokenStream> {
|
pub fn expand_derive(input: TokenStream) -> syn::Result<TokenStream> {
|
||||||
|
|
|
@ -1,431 +0,0 @@
|
||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
pub mod span_container;
|
|
||||||
|
|
||||||
use std::{convert::TryFrom, str::FromStr};
|
|
||||||
|
|
||||||
use proc_macro_error::abort;
|
|
||||||
use span_container::SpanContainer;
|
|
||||||
use syn::{
|
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
spanned::Spanned,
|
|
||||||
Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Compares a path to a one-segment string value,
|
|
||||||
/// return true if equal.
|
|
||||||
pub fn path_eq_single(path: &syn::Path, value: &str) -> bool {
|
|
||||||
path.segments.len() == 1 && path.segments[0].ident == value
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DeprecationAttr {
|
|
||||||
pub reason: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Filters given `attrs` to contain attributes only with the given `name`.
|
|
||||||
pub fn filter_attrs<'a>(
|
|
||||||
name: &'a str,
|
|
||||||
attrs: &'a [Attribute],
|
|
||||||
) -> impl Iterator<Item = &'a Attribute> + 'a {
|
|
||||||
attrs
|
|
||||||
.iter()
|
|
||||||
.filter(move |attr| path_eq_single(&attr.path, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_deprecated(attrs: &[Attribute]) -> Option<SpanContainer<DeprecationAttr>> {
|
|
||||||
attrs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|attr| match attr.parse_meta() {
|
|
||||||
Ok(Meta::List(ref list)) if list.path.is_ident("deprecated") => {
|
|
||||||
let val = get_deprecated_meta_list(list);
|
|
||||||
Some(SpanContainer::new(list.path.span(), None, val))
|
|
||||||
}
|
|
||||||
Ok(Meta::Path(ref path)) if path.is_ident("deprecated") => Some(SpanContainer::new(
|
|
||||||
path.span(),
|
|
||||||
None,
|
|
||||||
DeprecationAttr { reason: None },
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_deprecated_meta_list(list: &MetaList) -> DeprecationAttr {
|
|
||||||
for meta in &list.nested {
|
|
||||||
if let NestedMeta::Meta(Meta::NameValue(ref nv)) = *meta {
|
|
||||||
if nv.path.is_ident("note") {
|
|
||||||
match nv.lit {
|
|
||||||
Lit::Str(ref strlit) => {
|
|
||||||
return DeprecationAttr {
|
|
||||||
reason: Some(strlit.value()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => abort!(syn::Error::new(
|
|
||||||
nv.lit.span(),
|
|
||||||
"only strings are allowed for deprecation",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
abort!(syn::Error::new(
|
|
||||||
nv.path.span(),
|
|
||||||
"unrecognized setting on #[deprecated(..)] attribute",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DeprecationAttr { reason: None }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets doc comment.
|
|
||||||
pub fn get_doc_comment(attrs: &[Attribute]) -> Option<SpanContainer<String>> {
|
|
||||||
if let Some(items) = get_doc_attr(attrs) {
|
|
||||||
if let Some(doc_strings) = get_doc_strings(&items) {
|
|
||||||
return Some(doc_strings.map(|strings| join_doc_strings(&strings)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenates doc strings into one string.
|
|
||||||
fn join_doc_strings(docs: &[String]) -> String {
|
|
||||||
// Note: this is guaranteed since this function is only called
|
|
||||||
// from get_doc_strings().
|
|
||||||
debug_assert!(!docs.is_empty());
|
|
||||||
|
|
||||||
let last_index = docs.len() - 1;
|
|
||||||
docs.iter()
|
|
||||||
.map(|s| s.as_str().trim_end())
|
|
||||||
// Trim leading space.
|
|
||||||
.map(|s| s.strip_prefix(' ').unwrap_or(s))
|
|
||||||
// Add newline, exept when string ends in a continuation backslash or is the last line.
|
|
||||||
.enumerate()
|
|
||||||
.fold(String::new(), |mut buffer, (index, s)| {
|
|
||||||
if index == last_index {
|
|
||||||
buffer.push_str(s);
|
|
||||||
} else if s.ends_with('\\') {
|
|
||||||
buffer.push_str(s.trim_end_matches('\\'));
|
|
||||||
buffer.push(' ');
|
|
||||||
} else {
|
|
||||||
buffer.push_str(s);
|
|
||||||
buffer.push('\n');
|
|
||||||
}
|
|
||||||
buffer
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets doc strings from doc comment attributes.
|
|
||||||
fn get_doc_strings(items: &[MetaNameValue]) -> Option<SpanContainer<Vec<String>>> {
|
|
||||||
let mut span = None;
|
|
||||||
let comments = items
|
|
||||||
.iter()
|
|
||||||
.filter_map(|item| {
|
|
||||||
if item.path.is_ident("doc") {
|
|
||||||
match item.lit {
|
|
||||||
Lit::Str(ref strlit) => {
|
|
||||||
if span.is_none() {
|
|
||||||
span = Some(strlit.span());
|
|
||||||
}
|
|
||||||
Some(strlit.value())
|
|
||||||
}
|
|
||||||
_ => abort!(syn::Error::new(
|
|
||||||
item.lit.span(),
|
|
||||||
"doc attributes only have string literal"
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
span.map(|span| SpanContainer::new(span, None, comments))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets doc comment attributes.
|
|
||||||
fn get_doc_attr(attrs: &[Attribute]) -> Option<Vec<MetaNameValue>> {
|
|
||||||
let mut docs = Vec::new();
|
|
||||||
for attr in attrs {
|
|
||||||
match attr.parse_meta() {
|
|
||||||
Ok(Meta::NameValue(ref nv)) if nv.path.is_ident("doc") => docs.push(nv.clone()),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !docs.is_empty() {
|
|
||||||
return Some(docs);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: duplicated from juniper crate!
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn to_camel_case(s: &str) -> String {
|
|
||||||
let mut dest = String::new();
|
|
||||||
|
|
||||||
// Handle `_` and `__` to be more friendly with the `_var` convention for unused variables, and
|
|
||||||
// GraphQL introspection identifiers.
|
|
||||||
let s_iter = if let Some(s) = s.strip_prefix("__") {
|
|
||||||
dest.push_str("__");
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
s.strip_prefix('_').unwrap_or(s)
|
|
||||||
}
|
|
||||||
.split('_')
|
|
||||||
.enumerate();
|
|
||||||
|
|
||||||
for (i, part) in s_iter {
|
|
||||||
if i > 0 && part.len() == 1 {
|
|
||||||
dest.push_str(&part.to_uppercase());
|
|
||||||
} else if i > 0 && part.len() > 1 {
|
|
||||||
let first = part
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.to_uppercase()
|
|
||||||
.collect::<String>();
|
|
||||||
let second = &part[1..];
|
|
||||||
|
|
||||||
dest.push_str(&first);
|
|
||||||
dest.push_str(second);
|
|
||||||
} else if i == 0 {
|
|
||||||
dest.push_str(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dest
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn to_upper_snake_case(s: &str) -> String {
|
|
||||||
let mut last_lower = false;
|
|
||||||
let mut upper = String::new();
|
|
||||||
for c in s.chars() {
|
|
||||||
if c == '_' {
|
|
||||||
last_lower = false;
|
|
||||||
} else if c.is_lowercase() {
|
|
||||||
last_lower = true;
|
|
||||||
} else if c.is_uppercase() {
|
|
||||||
if last_lower {
|
|
||||||
upper.push('_');
|
|
||||||
}
|
|
||||||
last_lower = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for u in c.to_uppercase() {
|
|
||||||
upper.push(u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
upper
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The different possible ways to change case of fields in a struct, or variants in an enum.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
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(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<syn::LitStr> for RenameRule {
|
|
||||||
type Error = syn::Error;
|
|
||||||
|
|
||||||
fn try_from(lit: syn::LitStr) -> syn::Result<Self> {
|
|
||||||
Self::from_str(&lit.value()).map_err(|_| syn::Error::new(lit.span(), "unknown rename rule"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for RenameRule {
|
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
|
||||||
Self::try_from(input.parse::<syn::LitStr>()?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use proc_macro2::Span;
|
|
||||||
use syn::{Ident, LitStr};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn is_valid_name(field_name: &str) -> bool {
|
|
||||||
let mut chars = field_name.chars();
|
|
||||||
|
|
||||||
match chars.next() {
|
|
||||||
// first char can't be a digit
|
|
||||||
Some(c) if c.is_ascii_alphabetic() || c == '_' => (),
|
|
||||||
// can't be an empty string or any other character
|
|
||||||
_ => return false,
|
|
||||||
};
|
|
||||||
|
|
||||||
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
quote::format_ident!("{}", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod test_get_doc_strings {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_single() {
|
|
||||||
let result = get_doc_strings(&[MetaNameValue {
|
|
||||||
path: 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(&[
|
|
||||||
MetaNameValue {
|
|
||||||
path: ident("doc").into(),
|
|
||||||
eq_token: Default::default(),
|
|
||||||
lit: litstr("foo"),
|
|
||||||
},
|
|
||||||
MetaNameValue {
|
|
||||||
path: ident("doc").into(),
|
|
||||||
eq_token: Default::default(),
|
|
||||||
lit: litstr("\n"),
|
|
||||||
},
|
|
||||||
MetaNameValue {
|
|
||||||
path: 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(&[MetaNameValue {
|
|
||||||
path: 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\nbar");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_trims_spaces() {
|
|
||||||
let result = join_doc_strings(&strs_to_strings(vec![" foo ", "bar ", " baz"]));
|
|
||||||
assert_eq!(&result, "foo\nbar\nbaz");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty() {
|
|
||||||
let result = join_doc_strings(&strs_to_strings(vec!["foo", "", "bar"]));
|
|
||||||
assert_eq!(&result, "foo\n\nbar");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_newline_spaces() {
|
|
||||||
let result = join_doc_strings(&strs_to_strings(vec!["foo ", "", " bar"]));
|
|
||||||
assert_eq!(&result, "foo\n\nbar");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_continuation_backslash() {
|
|
||||||
let result = join_doc_strings(&strs_to_strings(vec!["foo\\", "x\\", "y", "bar"]));
|
|
||||||
assert_eq!(&result, "foo x y\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("__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…
Reference in a new issue