linera_base/
graphql.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/// Defines a GraphQL scalar with a description string.
5///
6/// This is equivalent to `scalar!` but always uses the stringified identifier as the name.
7#[macro_export]
8macro_rules! doc_scalar {
9    ($ty:ty, $desc:literal) => {
10        $crate::async_graphql::scalar_internal!(
11            $ty,
12            ::std::stringify!($ty),
13            ::std::option::Option::Some(::std::string::ToString::to_string($desc)),
14            ::std::option::Option::None
15        );
16    };
17}
18
19/// An error trying to parse the hex-digits of a BCS-encoded value.
20#[derive(thiserror::Error, Debug)]
21#[allow(missing_docs)]
22pub enum BcsHexParseError {
23    #[error(transparent)]
24    BcsError(#[from] bcs::Error),
25    #[error("Invalid hexadecimal: {0}")]
26    Hex(#[from] hex::FromHexError),
27}
28
29/// Defines a GraphQL scalar type using the hex-representation of the value's BCS-serialized form.
30///
31/// This is a modified implementation of [`async_graphql::scalar`].
32/// In addition, it implements `Display` and `FromStr`, also using hex-representation.
33#[macro_export]
34macro_rules! bcs_scalar {
35    ($ty:ty, $desc:literal) => {
36        impl $crate::async_graphql::ScalarType for $ty {
37            fn parse(
38                value: $crate::async_graphql::Value,
39            ) -> $crate::async_graphql::InputValueResult<Self> {
40                let hex: String = $crate::async_graphql::from_value(value)?;
41                let bytes = $crate::hex::decode(&hex)?;
42                let result = $crate::bcs::from_bytes(&bytes)?;
43                ::std::result::Result::Ok(result)
44            }
45
46            fn to_value(&self) -> $crate::async_graphql::Value {
47                let ::std::result::Result::Ok(bytes) = $crate::bcs::to_bytes(self) else {
48                    return $crate::async_graphql::Value::Null;
49                };
50                let hex = $crate::hex::encode(&bytes);
51                $crate::async_graphql::to_value(hex)
52                    .unwrap_or_else(|_| $crate::async_graphql::Value::Null)
53            }
54        }
55
56        impl $crate::async_graphql::InputType for $ty {
57            type RawValueType = Self;
58
59            fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
60                ::std::borrow::Cow::Borrowed(::std::stringify!($ty))
61            }
62
63            fn create_type_info(
64                registry: &mut $crate::async_graphql::registry::Registry,
65            ) -> ::std::string::String {
66                registry.create_input_type::<$ty, _>(
67                    $crate::async_graphql::registry::MetaTypeId::Scalar,
68                    |_| $crate::async_graphql::registry::MetaType::Scalar {
69                        name: ::std::borrow::ToOwned::to_owned(::std::stringify!($ty)),
70                        description: ::std::option::Option::Some(
71                            ::std::string::ToString::to_string($desc),
72                        ),
73                        is_valid: ::std::option::Option::Some(::std::sync::Arc::new(|value| {
74                            <$ty as $crate::async_graphql::ScalarType>::is_valid(value)
75                        })),
76                        visible: ::std::option::Option::None,
77                        inaccessible: false,
78                        tags: ::std::default::Default::default(),
79                        specified_by_url: ::std::option::Option::None,
80                        directive_invocations: ::std::default::Default::default(),
81                        requires_scopes: ::std::default::Default::default(),
82                    },
83                )
84            }
85
86            fn parse(
87                value: ::std::option::Option<$crate::async_graphql::Value>,
88            ) -> $crate::async_graphql::InputValueResult<Self> {
89                <$ty as $crate::async_graphql::ScalarType>::parse(value.unwrap_or_default())
90            }
91
92            fn to_value(&self) -> $crate::async_graphql::Value {
93                <$ty as $crate::async_graphql::ScalarType>::to_value(self)
94            }
95
96            fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
97                ::std::option::Option::Some(self)
98            }
99        }
100
101        impl $crate::async_graphql::OutputType for $ty {
102            fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
103                ::std::borrow::Cow::Borrowed(::std::stringify!($ty))
104            }
105
106            fn create_type_info(
107                registry: &mut $crate::async_graphql::registry::Registry,
108            ) -> ::std::string::String {
109                registry.create_output_type::<$ty, _>(
110                    $crate::async_graphql::registry::MetaTypeId::Scalar,
111                    |_| $crate::async_graphql::registry::MetaType::Scalar {
112                        name: ::std::borrow::ToOwned::to_owned(::std::stringify!($ty)),
113                        description: ::std::option::Option::Some(
114                            ::std::string::ToString::to_string($desc),
115                        ),
116                        is_valid: ::std::option::Option::Some(::std::sync::Arc::new(|value| {
117                            <$ty as $crate::async_graphql::ScalarType>::is_valid(value)
118                        })),
119                        visible: ::std::option::Option::None,
120                        inaccessible: false,
121                        tags: ::std::default::Default::default(),
122                        specified_by_url: ::std::option::Option::None,
123                        directive_invocations: ::std::default::Default::default(),
124                        requires_scopes: ::std::default::Default::default(),
125                    },
126                )
127            }
128
129            async fn resolve(
130                &self,
131                _: &$crate::async_graphql::ContextSelectionSet<'_>,
132                _field: &$crate::async_graphql::Positioned<
133                    $crate::async_graphql::parser::types::Field,
134                >,
135            ) -> $crate::async_graphql::ServerResult<$crate::async_graphql::Value> {
136                ::std::result::Result::Ok($crate::async_graphql::ScalarType::to_value(self))
137            }
138        }
139
140        impl ::std::fmt::Display for $ty {
141            fn fmt(
142                &self,
143                f: &mut ::std::fmt::Formatter<'_>,
144            ) -> ::std::result::Result<(), ::std::fmt::Error> {
145                match $crate::bcs::to_bytes(self) {
146                    ::std::result::Result::Ok(bytes) => {
147                        ::std::fmt::Display::fmt(&$crate::hex::encode(&bytes), f)
148                    }
149                    ::std::result::Result::Err(_) => {
150                        ::std::write!(f, "invalid {}", ::std::stringify!($ty))
151                    }
152                }
153            }
154        }
155
156        impl ::std::str::FromStr for $ty {
157            type Err = $crate::BcsHexParseError;
158
159            fn from_str(s: &str) -> Result<Self, Self::Err> {
160                let bytes = $crate::hex::decode(s)?;
161                ::std::result::Result::Ok($crate::bcs::from_bytes(&bytes)?)
162            }
163        }
164    };
165}