Merge branch 'master' into fix-codegen-tests
This commit is contained in:
commit
c10ac894b8
24 changed files with 453 additions and 15 deletions
|
@ -0,0 +1,7 @@
|
|||
use juniper::GraphQLScalarValue;
|
||||
|
||||
#[derive(GraphQLScalarValue)]
|
||||
#[graphql(specified_by_url = "not an url")]
|
||||
struct ScalarSpecifiedByUrl(i64);
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,5 @@
|
|||
error: Invalid URL: relative URL without a base
|
||||
--> fail/scalar/derive_invalid_url.rs:4:30
|
||||
|
|
||||
4 | #[graphql(specified_by_url = "not an url")]
|
||||
| ^^^^^^^^^^^^
|
|
@ -0,0 +1,22 @@
|
|||
use juniper::graphql_scalar;
|
||||
|
||||
struct ScalarSpecifiedByUrl(i32);
|
||||
|
||||
#[graphql_scalar(specified_by_url = "not an url")]
|
||||
impl GraphQLScalar for ScalarSpecifiedByUrl {
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<ScalarSpecifiedByUrl, String> {
|
||||
v.as_int_value()
|
||||
.map(ScalarSpecifiedByUrl)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
|
||||
<i32 as ParseScalarValue>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
|
@ -0,0 +1,5 @@
|
|||
error: Invalid URL: relative URL without a base
|
||||
--> fail/scalar/impl_invalid_url.rs:5:22
|
||||
|
|
||||
5 | #[graphql_scalar(specified_by_url = "not an url")]
|
||||
| ^^^^^^^^^^^^^^^^
|
|
@ -6,7 +6,11 @@ use juniper::{
|
|||
use crate::custom_scalar::MyScalarValue;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, juniper::GraphQLScalarValue)]
|
||||
#[graphql(transparent, scalar = MyScalarValue)]
|
||||
#[graphql(
|
||||
transparent,
|
||||
scalar = MyScalarValue,
|
||||
specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||
)]
|
||||
pub struct LargeId(i64);
|
||||
|
||||
#[derive(juniper::GraphQLObject)]
|
||||
|
@ -49,6 +53,29 @@ fn test_scalar_value_large_id() {
|
|||
assert_eq!(output, InputValue::scalar(num));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_scalar_value_large_specified_url() {
|
||||
let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value(
|
||||
Query,
|
||||
EmptyMutation::<()>::new(),
|
||||
EmptySubscription::<()>::new(),
|
||||
);
|
||||
|
||||
let doc = r#"{
|
||||
__type(name: "LargeId") {
|
||||
specifiedByUrl
|
||||
}
|
||||
}"#;
|
||||
|
||||
assert_eq!(
|
||||
execute(doc, None, &schema, &Variables::<MyScalarValue>::new(), &()).await,
|
||||
Ok((
|
||||
graphql_value!({"__type": {"specifiedByUrl": "https://tools.ietf.org/html/rfc4122"}}),
|
||||
vec![],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_scalar_value_large_query() {
|
||||
let schema = RootNode::<'_, _, _, _, MyScalarValue>::new_with_scalar_value(
|
||||
|
|
|
@ -9,6 +9,7 @@ struct DefaultName(i32);
|
|||
struct OtherOrder(i32);
|
||||
struct Named(i32);
|
||||
struct ScalarDescription(i32);
|
||||
struct ScalarSpecifiedByUrl(i32);
|
||||
struct Generated(String);
|
||||
|
||||
struct Root;
|
||||
|
@ -93,6 +94,23 @@ impl GraphQLScalar for ScalarDescription {
|
|||
}
|
||||
}
|
||||
|
||||
#[graphql_scalar(specified_by_url = "https://tools.ietf.org/html/rfc4122")]
|
||||
impl GraphQLScalar for ScalarSpecifiedByUrl {
|
||||
fn resolve(&self) -> Value {
|
||||
Value::scalar(self.0)
|
||||
}
|
||||
|
||||
fn from_input_value(v: &InputValue) -> Result<ScalarSpecifiedByUrl, String> {
|
||||
v.as_int_value()
|
||||
.map(ScalarSpecifiedByUrl)
|
||||
.ok_or_else(|| format!("Expected `Int`, found: {}", v))
|
||||
}
|
||||
|
||||
fn from_str<'a>(value: ScalarToken<'a>) -> ParseScalarResult<'a, DefaultScalarValue> {
|
||||
<i32 as ParseScalarValue>::from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_scalar {
|
||||
($name: ident) => {
|
||||
#[graphql_scalar]
|
||||
|
@ -134,6 +152,9 @@ impl Root {
|
|||
fn scalar_description() -> ScalarDescription {
|
||||
ScalarDescription(0)
|
||||
}
|
||||
fn scalar_specified_by_url() -> ScalarSpecifiedByUrl {
|
||||
ScalarSpecifiedByUrl(0)
|
||||
}
|
||||
fn generated() -> Generated {
|
||||
Generated("foo".to_owned())
|
||||
}
|
||||
|
@ -297,6 +318,7 @@ async fn scalar_description_introspection() {
|
|||
__type(name: "ScalarDescription") {
|
||||
name
|
||||
description
|
||||
specifiedByUrl
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
@ -312,6 +334,32 @@ async fn scalar_description_introspection() {
|
|||
"A sample scalar, represented as an integer",
|
||||
)),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("specifiedByUrl"),
|
||||
Some(&graphql_value!(null)),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn scalar_specified_by_url_introspection() {
|
||||
let doc = r#"{
|
||||
__type(name: "ScalarSpecifiedByUrl") {
|
||||
name
|
||||
specifiedByUrl
|
||||
}
|
||||
}"#;
|
||||
|
||||
run_type_info_query(doc, |type_info| {
|
||||
assert_eq!(
|
||||
type_info.get_field_value("name"),
|
||||
Some(&graphql_value!("ScalarSpecifiedByUrl")),
|
||||
);
|
||||
assert_eq!(
|
||||
type_info.get_field_value("specifiedByUrl"),
|
||||
Some(&graphql_value!("https://tools.ietf.org/html/rfc4122")),
|
||||
);
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ struct CustomUserId(String);
|
|||
|
||||
/// The doc comment...
|
||||
#[derive(GraphQLScalarValue, Debug, Eq, PartialEq)]
|
||||
#[graphql(transparent)]
|
||||
#[graphql(transparent, specified_by_url = "https://tools.ietf.org/html/rfc4122")]
|
||||
struct IdWithDocComment(i32);
|
||||
|
||||
#[derive(GraphQLObject)]
|
||||
|
@ -64,6 +64,7 @@ fn test_scalar_value_custom() {
|
|||
let meta = CustomUserId::meta(&(), &mut registry);
|
||||
assert_eq!(meta.name(), Some("MyUserId"));
|
||||
assert_eq!(meta.description(), Some("custom description..."));
|
||||
assert_eq!(meta.specified_by_url(), None);
|
||||
|
||||
let input: InputValue = serde_json::from_value(serde_json::json!("userId1")).unwrap();
|
||||
let output: CustomUserId = FromInputValue::from_input_value(&input).unwrap();
|
||||
|
@ -79,4 +80,8 @@ fn test_scalar_value_doc_comment() {
|
|||
let mut registry: Registry = Registry::new(FnvHashMap::default());
|
||||
let meta = IdWithDocComment::meta(&(), &mut registry);
|
||||
assert_eq!(meta.description(), Some("The doc comment..."));
|
||||
assert_eq!(
|
||||
meta.specified_by_url(),
|
||||
Some("https://tools.ietf.org/html/rfc4122"),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
- Add `From` impls to `InputValue` mirroring the ones for `Value` and provide better support for `Option` handling. ([#996](https://github.com/graphql-rust/juniper/pull/996))
|
||||
- Implement `graphql_input_value!` and `graphql_vars!` macros. ([#996](https://github.com/graphql-rust/juniper/pull/996))
|
||||
- Support [`time` crate](https://docs.rs/time) types as GraphQL scalars behind `time` feature. ([#1006](https://github.com/graphql-rust/juniper/pull/1006))
|
||||
- Add `specified_by_url` attribute argument to `#[derive(GraphQLScalarValue)]` and `#[graphql_scalar]` macros. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
|
||||
- Support `isRepeatable` field on directives. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
|
||||
- Support `__Schema.description`, `__Type.specifiedByURL` and `__Directive.isRepeatable` fields in introspection. ([#1003](https://github.com/graphql-rust/juniper/pull/1003), [#1000](https://github.com/graphql-rust/juniper/pull/1000))
|
||||
|
||||
## Fixes
|
||||
|
||||
|
|
|
@ -492,6 +492,7 @@ async fn scalar_introspection() {
|
|||
name
|
||||
kind
|
||||
description
|
||||
specifiedByUrl
|
||||
fields { name }
|
||||
interfaces { name }
|
||||
possibleTypes { name }
|
||||
|
@ -527,6 +528,7 @@ async fn scalar_introspection() {
|
|||
"name": "SampleScalar",
|
||||
"kind": "SCALAR",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"interfaces": null,
|
||||
"possibleTypes": null,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/// From <https://github.com/graphql/graphql-js/blob/8c96dc8276f2de27b8af9ffbd71a4597d483523f/src/utilities/introspectionQuery.js#L21>
|
||||
/// From <https://github.com/graphql/graphql-js/blob/90bd6ff72625173dd39a1f82cfad9336cfad8f65/src/utilities/getIntrospectionQuery.ts#L62>
|
||||
pub(crate) const INTROSPECTION_QUERY: &str = include_str!("./query.graphql");
|
||||
pub(crate) const INTROSPECTION_QUERY_WITHOUT_DESCRIPTIONS: &str =
|
||||
include_str!("./query_without_descriptions.graphql");
|
||||
|
||||
/// The desired GraphQL introspection format for the canonical query
|
||||
/// (<https://github.com/graphql/graphql-js/blob/8c96dc8276f2de27b8af9ffbd71a4597d483523f/src/utilities/introspectionQuery.js#L21>)
|
||||
/// (<https://github.com/graphql/graphql-js/blob/90bd6ff72625173dd39a1f82cfad9336cfad8f65/src/utilities/getIntrospectionQuery.ts#L62>)
|
||||
pub enum IntrospectionFormat {
|
||||
/// The canonical GraphQL introspection query.
|
||||
All,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
query IntrospectionQuery {
|
||||
__schema {
|
||||
description
|
||||
queryType {
|
||||
name
|
||||
}
|
||||
|
@ -15,6 +16,7 @@ query IntrospectionQuery {
|
|||
directives {
|
||||
name
|
||||
description
|
||||
isRepeatable
|
||||
locations
|
||||
args {
|
||||
...InputValue
|
||||
|
@ -26,6 +28,7 @@ fragment FullType on __Type {
|
|||
kind
|
||||
name
|
||||
description
|
||||
specifiedByUrl
|
||||
fields(includeDeprecated: true) {
|
||||
name
|
||||
description
|
||||
|
|
|
@ -14,6 +14,7 @@ query IntrospectionQuery {
|
|||
}
|
||||
directives {
|
||||
name
|
||||
isRepeatable
|
||||
locations
|
||||
args {
|
||||
...InputValue
|
||||
|
@ -24,6 +25,7 @@ query IntrospectionQuery {
|
|||
fragment FullType on __Type {
|
||||
kind
|
||||
name
|
||||
specifiedByUrl
|
||||
fields(includeDeprecated: true) {
|
||||
name
|
||||
args {
|
||||
|
|
|
@ -48,6 +48,8 @@ pub struct ScalarMeta<'a, S> {
|
|||
pub name: Cow<'a, str>,
|
||||
#[doc(hidden)]
|
||||
pub description: Option<String>,
|
||||
#[doc(hidden)]
|
||||
pub specified_by_url: Option<Cow<'a, str>>,
|
||||
pub(crate) try_parse_fn: for<'b> fn(&'b InputValue<S>) -> Result<(), FieldError<S>>,
|
||||
pub(crate) parse_fn: for<'b> fn(ScalarToken<'b>) -> Result<S, ParseError<'b>>,
|
||||
}
|
||||
|
@ -250,9 +252,24 @@ impl<'a, S> MetaType<'a, S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Accesses the [specification URL][0], if applicable.
|
||||
///
|
||||
/// Only custom GraphQL scalars can have a [specification URL][0].
|
||||
///
|
||||
/// [0]: https://spec.graphql.org/October2021#sec--specifiedBy
|
||||
pub fn specified_by_url(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Scalar(ScalarMeta {
|
||||
specified_by_url, ..
|
||||
}) => specified_by_url.as_deref(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a `TypeKind` for a given type
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the type represents a placeholder or nullable type.
|
||||
pub fn type_kind(&self) -> TypeKind {
|
||||
match *self {
|
||||
|
@ -421,6 +438,7 @@ impl<'a, S> ScalarMeta<'a, S> {
|
|||
Self {
|
||||
name,
|
||||
description: None,
|
||||
specified_by_url: None,
|
||||
try_parse_fn: try_parse_fn::<S, T>,
|
||||
parse_fn: <T as ParseScalarValue<S>>::from_str,
|
||||
}
|
||||
|
@ -434,6 +452,16 @@ impl<'a, S> ScalarMeta<'a, S> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the [specification URL][0] for this [`ScalarMeta`] type.
|
||||
///
|
||||
/// Overwrites any previously set [specification URL][0].
|
||||
///
|
||||
/// [0]: https://spec.graphql.org/October2021#sec--specifiedBy
|
||||
pub fn specified_by_url(mut self, url: impl Into<Cow<'a, str>>) -> Self {
|
||||
self.specified_by_url = Some(url.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Wraps this [`ScalarMeta`] type into a generic [`MetaType`].
|
||||
pub fn into_meta(self) -> MetaType<'a, S> {
|
||||
MetaType::Scalar(self)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::fmt;
|
||||
use std::{borrow::Cow, fmt};
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
#[cfg(feature = "graphql-parser-integration")]
|
||||
|
@ -49,6 +49,7 @@ pub struct RootNode<
|
|||
/// Metadata for a schema
|
||||
#[derive(Debug)]
|
||||
pub struct SchemaType<'a, S> {
|
||||
pub(crate) description: Option<Cow<'a, str>>,
|
||||
pub(crate) types: FnvHashMap<Name, MetaType<'a, S>>,
|
||||
pub(crate) query_type_name: String,
|
||||
pub(crate) mutation_type_name: Option<String>,
|
||||
|
@ -71,6 +72,7 @@ pub struct DirectiveType<'a, S> {
|
|||
pub description: Option<String>,
|
||||
pub locations: Vec<DirectiveLocation>,
|
||||
pub arguments: Vec<Argument<'a, S>>,
|
||||
pub is_repeatable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, GraphQLEnum)]
|
||||
|
@ -80,6 +82,7 @@ pub enum DirectiveLocation {
|
|||
Mutation,
|
||||
Subscription,
|
||||
Field,
|
||||
Scalar,
|
||||
#[graphql(name = "FRAGMENT_DEFINITION")]
|
||||
FragmentDefinition,
|
||||
#[graphql(name = "FRAGMENT_SPREAD")]
|
||||
|
@ -211,6 +214,10 @@ impl<'a, S> SchemaType<'a, S> {
|
|||
"include".to_owned(),
|
||||
DirectiveType::new_include(&mut registry),
|
||||
);
|
||||
directives.insert(
|
||||
"specifiedBy".to_owned(),
|
||||
DirectiveType::new_specified_by(&mut registry),
|
||||
);
|
||||
|
||||
let mut meta_fields = vec![
|
||||
registry.field::<SchemaType<S>>("__schema", &()),
|
||||
|
@ -235,6 +242,7 @@ impl<'a, S> SchemaType<'a, S> {
|
|||
}
|
||||
}
|
||||
SchemaType {
|
||||
description: None,
|
||||
types: registry.types,
|
||||
query_type_name,
|
||||
mutation_type_name: if &mutation_type_name != "_EmptyMutation" {
|
||||
|
@ -251,6 +259,11 @@ impl<'a, S> SchemaType<'a, S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Add a description.
|
||||
pub fn set_description(&mut self, description: impl Into<Cow<'a, str>>) {
|
||||
self.description = Some(description.into());
|
||||
}
|
||||
|
||||
/// Add a directive like `skip` or `include`.
|
||||
pub fn add_directive(&mut self, directive: DirectiveType<'a, S>) {
|
||||
self.directives.insert(directive.name.clone(), directive);
|
||||
|
@ -489,12 +502,14 @@ where
|
|||
name: &str,
|
||||
locations: &[DirectiveLocation],
|
||||
arguments: &[Argument<'a, S>],
|
||||
is_repeatable: bool,
|
||||
) -> DirectiveType<'a, S> {
|
||||
DirectiveType {
|
||||
name: name.to_owned(),
|
||||
description: None,
|
||||
locations: locations.to_vec(),
|
||||
arguments: arguments.to_vec(),
|
||||
is_repeatable,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,6 +525,7 @@ where
|
|||
DirectiveLocation::InlineFragment,
|
||||
],
|
||||
&[registry.arg::<bool>("if", &())],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -525,6 +541,19 @@ where
|
|||
DirectiveLocation::InlineFragment,
|
||||
],
|
||||
&[registry.arg::<bool>("if", &())],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn new_specified_by(registry: &mut Registry<'a, S>) -> DirectiveType<'a, S>
|
||||
where
|
||||
S: ScalarValue,
|
||||
{
|
||||
Self::new(
|
||||
"specifiedBy",
|
||||
&[DirectiveLocation::Scalar],
|
||||
&[registry.arg::<String>("url", &())],
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -544,6 +573,7 @@ impl fmt::Display for DirectiveLocation {
|
|||
DirectiveLocation::FragmentDefinition => "fragment definition",
|
||||
DirectiveLocation::FragmentSpread => "fragment spread",
|
||||
DirectiveLocation::InlineFragment => "inline fragment",
|
||||
DirectiveLocation::Scalar => "scalar",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,6 +137,10 @@ where
|
|||
internal,
|
||||
)]
|
||||
impl<'a, S: ScalarValue + 'a> SchemaType<'a, S> {
|
||||
fn description(&self) -> Option<&str> {
|
||||
self.description.as_deref()
|
||||
}
|
||||
|
||||
fn types(&self) -> Vec<TypeType<S>> {
|
||||
self.type_list()
|
||||
.into_iter()
|
||||
|
@ -192,6 +196,13 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
|
|||
}
|
||||
}
|
||||
|
||||
fn specified_by_url(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Concrete(t) => t.specified_by_url(),
|
||||
Self::NonNull(_) | Self::List(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn kind(&self) -> TypeKind {
|
||||
match self {
|
||||
TypeType::Concrete(t) => t.type_kind(),
|
||||
|
@ -401,6 +412,10 @@ impl<'a, S: ScalarValue + 'a> DirectiveType<'a, S> {
|
|||
&self.locations
|
||||
}
|
||||
|
||||
fn is_repeatable(&self) -> bool {
|
||||
self.is_repeatable
|
||||
}
|
||||
|
||||
fn args(&self) -> &[Argument<S>] {
|
||||
&self.arguments
|
||||
}
|
||||
|
|
|
@ -208,6 +208,12 @@ async fn test_introspection_directives() {
|
|||
"INLINE_FRAGMENT",
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "specifiedBy",
|
||||
"locations": [
|
||||
"SCALAR",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
@ -36,6 +36,7 @@ pub(super) fn sort_schema_value(value: &mut Value) {
|
|||
pub(crate) fn schema_introspection_result() -> Value {
|
||||
let mut v = graphql_value!({
|
||||
"__schema": {
|
||||
"description": null,
|
||||
"queryType": {
|
||||
"name": "Query"
|
||||
},
|
||||
|
@ -46,6 +47,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "Human",
|
||||
"description": "A humanoid creature in the Star Wars universe.",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -151,6 +153,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -161,6 +164,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "__InputValue",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -228,6 +232,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -238,6 +243,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "__Field",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -345,6 +351,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "ENUM",
|
||||
"name": "__TypeKind",
|
||||
"description": "GraphQL type kind\n\nThe GraphQL specification defines a number of type kinds - the meta type of a type.",
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -404,6 +411,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "__Type",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -548,6 +556,18 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "specifiedByUrl",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "enumValues",
|
||||
"description": null,
|
||||
|
@ -589,6 +609,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "__Schema",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "types",
|
||||
|
@ -614,6 +635,18 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "queryType",
|
||||
"description": null,
|
||||
|
@ -688,6 +721,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "Droid",
|
||||
"description": "A mechanical creature in the Star Wars universe.",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -793,6 +827,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "Query",
|
||||
"description": "The root query object of the schema",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "human",
|
||||
|
@ -882,6 +917,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "__EnumValue",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -949,6 +985,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "ENUM",
|
||||
"name": "Episode",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -978,6 +1015,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "ENUM",
|
||||
"name": "__DirectiveLocation",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -1023,6 +1061,12 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "SCALAR",
|
||||
"description": null,
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
|
@ -1031,6 +1075,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "INTERFACE",
|
||||
"name": "Character",
|
||||
"description": "A character in the Star Wars Trilogy",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -1129,6 +1174,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"kind": "OBJECT",
|
||||
"name": "__Directive",
|
||||
"description": null,
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -1158,6 +1204,22 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "isRepeatable",
|
||||
"description": null,
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "locations",
|
||||
"description": null,
|
||||
|
@ -1265,6 +1327,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
{
|
||||
"name": "skip",
|
||||
"description": null,
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"FIELD",
|
||||
"FRAGMENT_SPREAD",
|
||||
|
@ -1290,6 +1353,7 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
{
|
||||
"name": "include",
|
||||
"description": null,
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"FIELD",
|
||||
"FRAGMENT_SPREAD",
|
||||
|
@ -1311,6 +1375,30 @@ pub(crate) fn schema_introspection_result() -> Value {
|
|||
"defaultValue": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "specifiedBy",
|
||||
"description": null,
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"SCALAR"
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "url",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1331,6 +1419,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Human",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -1430,6 +1519,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -1439,6 +1529,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "__InputValue",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -1501,6 +1592,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -1510,6 +1602,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "__Field",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -1610,6 +1703,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "ENUM",
|
||||
"name": "__TypeKind",
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -1660,6 +1754,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "__Type",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -1795,6 +1890,17 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "specifiedByUrl",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "enumValues",
|
||||
"args": [
|
||||
|
@ -1833,7 +1939,19 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "__Schema",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "description",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "types",
|
||||
"args": [],
|
||||
|
@ -1926,6 +2044,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Droid",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -2025,6 +2144,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Query",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "human",
|
||||
|
@ -2106,6 +2226,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "__EnumValue",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -2168,6 +2289,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "ENUM",
|
||||
"name": "Episode",
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -2193,6 +2315,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "ENUM",
|
||||
"name": "__DirectiveLocation",
|
||||
"specifiedByUrl": null,
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
|
@ -2231,6 +2354,11 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
"name": "INLINE_FRAGMENT",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "SCALAR",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
|
@ -2238,6 +2366,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "Character",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -2331,6 +2460,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "__Directive",
|
||||
"specifiedByUrl": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
|
@ -2358,6 +2488,21 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "isRepeatable",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "locations",
|
||||
"args": [],
|
||||
|
@ -2459,6 +2604,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
"directives": [
|
||||
{
|
||||
"name": "skip",
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"FIELD",
|
||||
"FRAGMENT_SPREAD",
|
||||
|
@ -2482,6 +2628,7 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
},
|
||||
{
|
||||
"name": "include",
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"FIELD",
|
||||
"FRAGMENT_SPREAD",
|
||||
|
@ -2502,6 +2649,28 @@ pub(crate) fn schema_introspection_result_without_descriptions() -> Value {
|
|||
"defaultValue": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "specifiedBy",
|
||||
"isRepeatable": false,
|
||||
"locations": [
|
||||
"SCALAR"
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "url",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -855,31 +855,37 @@ where
|
|||
"onQuery",
|
||||
&[DirectiveLocation::Query],
|
||||
&[],
|
||||
false,
|
||||
));
|
||||
root.schema.add_directive(DirectiveType::new(
|
||||
"onMutation",
|
||||
&[DirectiveLocation::Mutation],
|
||||
&[],
|
||||
false,
|
||||
));
|
||||
root.schema.add_directive(DirectiveType::new(
|
||||
"onField",
|
||||
&[DirectiveLocation::Field],
|
||||
&[],
|
||||
false,
|
||||
));
|
||||
root.schema.add_directive(DirectiveType::new(
|
||||
"onFragmentDefinition",
|
||||
&[DirectiveLocation::FragmentDefinition],
|
||||
&[],
|
||||
false,
|
||||
));
|
||||
root.schema.add_directive(DirectiveType::new(
|
||||
"onFragmentSpread",
|
||||
&[DirectiveLocation::FragmentSpread],
|
||||
&[],
|
||||
false,
|
||||
));
|
||||
root.schema.add_directive(DirectiveType::new(
|
||||
"onInlineFragment",
|
||||
&[DirectiveLocation::InlineFragment],
|
||||
&[],
|
||||
false,
|
||||
));
|
||||
|
||||
let doc =
|
||||
|
|
|
@ -22,6 +22,7 @@ proc-macro-error = "1.0.2"
|
|||
proc-macro2 = "1.0.1"
|
||||
quote = "1.0.3"
|
||||
syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing"], default-features = false }
|
||||
url = "2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
derive_more = "0.99.7"
|
||||
|
|
|
@ -6,12 +6,14 @@ use crate::{
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{spanned::Spanned, token, Data, Fields, Ident, Variant};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TransparentAttributes {
|
||||
transparent: Option<bool>,
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
specified_by_url: Option<Url>,
|
||||
scalar: Option<syn::Type>,
|
||||
}
|
||||
|
||||
|
@ -21,6 +23,7 @@ impl syn::parse::Parse for TransparentAttributes {
|
|||
transparent: None,
|
||||
name: None,
|
||||
description: None,
|
||||
specified_by_url: None,
|
||||
scalar: None,
|
||||
};
|
||||
|
||||
|
@ -37,6 +40,14 @@ impl syn::parse::Parse for TransparentAttributes {
|
|||
let val = input.parse::<syn::LitStr>()?;
|
||||
output.description = Some(val.value());
|
||||
}
|
||||
"specified_by_url" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let val: syn::LitStr = input.parse::<syn::LitStr>()?;
|
||||
output.specified_by_url =
|
||||
Some(val.value().parse().map_err(|e| {
|
||||
syn::Error::new(val.span(), format!("Invalid URL: {}", e))
|
||||
})?);
|
||||
}
|
||||
"transparent" => {
|
||||
output.transparent = Some(true);
|
||||
}
|
||||
|
@ -101,10 +112,11 @@ fn impl_scalar_struct(
|
|||
let inner_ty = &field.ty;
|
||||
let name = attrs.name.unwrap_or_else(|| ident.to_string());
|
||||
|
||||
let description = match attrs.description {
|
||||
Some(val) => quote!( .description( #val ) ),
|
||||
None => quote!(),
|
||||
};
|
||||
let description = attrs.description.map(|val| quote!(.description(#val)));
|
||||
let specified_by_url = attrs.specified_by_url.map(|url| {
|
||||
let url_lit = url.as_str();
|
||||
quote!(.specified_by_url(#url_lit))
|
||||
});
|
||||
|
||||
let scalar = attrs
|
||||
.scalar
|
||||
|
@ -159,6 +171,7 @@ fn impl_scalar_struct(
|
|||
{
|
||||
registry.build_scalar_type::<Self>(info)
|
||||
#description
|
||||
#specified_by_url
|
||||
.into_meta()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,10 +202,11 @@ pub fn build_scalar(
|
|||
.name
|
||||
.map(SpanContainer::into_inner)
|
||||
.unwrap_or_else(|| impl_for_type.ident.to_string());
|
||||
let description = match attrs.description {
|
||||
Some(val) => quote!(.description(#val)),
|
||||
None => quote!(),
|
||||
};
|
||||
let description = attrs.description.map(|val| quote!(.description(#val)));
|
||||
let specified_by_url = attrs.specified_by_url.map(|url| {
|
||||
let url_lit = url.as_str();
|
||||
quote!(.specified_by_url(#url_lit))
|
||||
});
|
||||
let async_generic_type = match input.custom_data_type_is_struct {
|
||||
true => quote!(__S),
|
||||
_ => quote!(#custom_data_type),
|
||||
|
@ -273,6 +274,7 @@ pub fn build_scalar(
|
|||
{
|
||||
registry.build_scalar_type::<Self>(info)
|
||||
#description
|
||||
#specified_by_url
|
||||
.into_meta()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,6 +177,8 @@ pub fn derive_input_object(input: TokenStream) -> TokenStream {
|
|||
/// // A description can also specified in the attribute.
|
||||
/// // This will the doc comment, if one exists.
|
||||
/// description = "...",
|
||||
/// // A specification URL.
|
||||
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||
/// )]
|
||||
/// struct UserId(String);
|
||||
/// ```
|
||||
|
@ -221,7 +223,10 @@ pub fn derive_scalar_value(input: TokenStream) -> TokenStream {
|
|||
/// name = "MyName",
|
||||
/// // You can also specify a description here.
|
||||
/// // If present, doc comments will be ignored.
|
||||
/// description = "An opaque identifier, represented as a string")]
|
||||
/// description = "An opaque identifier, represented as a string",
|
||||
/// // A specification URL.
|
||||
/// specified_by_url = "https://tools.ietf.org/html/rfc4122",
|
||||
/// )]
|
||||
/// impl<S> GraphQLScalar for UserID
|
||||
/// where
|
||||
/// S: juniper::ScalarValue
|
||||
|
|
|
@ -17,6 +17,7 @@ use syn::{
|
|||
spanned::Spanned,
|
||||
token, Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use crate::common::parse::ParseBufferExt as _;
|
||||
|
||||
|
@ -454,6 +455,7 @@ pub enum FieldAttributeParseMode {
|
|||
enum FieldAttribute {
|
||||
Name(SpanContainer<syn::LitStr>),
|
||||
Description(SpanContainer<syn::LitStr>),
|
||||
SpecifiedByUrl(SpanContainer<syn::LitStr>),
|
||||
Deprecation(SpanContainer<DeprecationAttr>),
|
||||
Skip(SpanContainer<syn::Ident>),
|
||||
Arguments(HashMap<String, FieldAttributeArgument>),
|
||||
|
@ -488,6 +490,15 @@ impl Parse for FieldAttribute {
|
|||
lit,
|
||||
)))
|
||||
}
|
||||
"specified_by_url" => {
|
||||
input.parse::<token::Eq>()?;
|
||||
let lit = input.parse::<syn::LitStr>()?;
|
||||
Ok(FieldAttribute::SpecifiedByUrl(SpanContainer::new(
|
||||
ident.span(),
|
||||
Some(lit.span()),
|
||||
lit,
|
||||
)))
|
||||
}
|
||||
"deprecated" | "deprecation" => {
|
||||
let reason = if input.peek(token::Eq) {
|
||||
input.parse::<token::Eq>()?;
|
||||
|
@ -542,7 +553,9 @@ pub struct FieldAttributes {
|
|||
pub name: Option<SpanContainer<String>>,
|
||||
pub description: Option<SpanContainer<String>>,
|
||||
pub deprecation: Option<SpanContainer<DeprecationAttr>>,
|
||||
// Only relevant for GraphQLObject derive.
|
||||
/// Only relevant for scalar impl macro.
|
||||
pub specified_by_url: Option<SpanContainer<Url>>,
|
||||
/// Only relevant for GraphQLObject derive.
|
||||
pub skip: Option<SpanContainer<syn::Ident>>,
|
||||
/// Only relevant for object macro.
|
||||
pub arguments: HashMap<String, FieldAttributeArgument>,
|
||||
|
@ -564,6 +577,18 @@ impl Parse for FieldAttributes {
|
|||
FieldAttribute::Description(name) => {
|
||||
output.description = Some(name.map(|val| val.value()));
|
||||
}
|
||||
FieldAttribute::SpecifiedByUrl(url) => {
|
||||
output.specified_by_url = Some(
|
||||
url.map(|val| Url::parse(&val.value()))
|
||||
.transpose()
|
||||
.map_err(|e| {
|
||||
syn::Error::new(
|
||||
e.span_ident(),
|
||||
format!("Invalid URL: {}", e.inner()),
|
||||
)
|
||||
})?,
|
||||
);
|
||||
}
|
||||
FieldAttribute::Deprecation(attr) => {
|
||||
output.deprecation = Some(attr);
|
||||
}
|
||||
|
|
|
@ -58,6 +58,15 @@ impl<T> SpanContainer<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, E> SpanContainer<Result<T, E>> {
|
||||
pub fn transpose(self) -> Result<SpanContainer<T>, SpanContainer<E>> {
|
||||
match self.val {
|
||||
Ok(v) => Ok(SpanContainer::new(self.ident, self.expr, v)),
|
||||
Err(e) => Err(SpanContainer::new(self.ident, self.expr, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for SpanContainer<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.val
|
||||
|
|
Loading…
Reference in a new issue