use reed_compiler::response::ExecutionResponse;
pub use reed_compiler::{
Schema,
ast::Value as ScalarLiteral,
response::{GraphQLError, JsonMap, JsonValue},
validation::Valid,
};
use serde::Serialize;
mod execution;
mod input;
pub use execution::{
Dispatcher, ExecutionOptions, FieldCoordinate, execute, execute_with_options, subscribe,
subscribe_with_options,
};
pub type ScalarError = ResolverError;
pub type ScalarLiteralMap = std::collections::HashMap<String, ScalarLiteral>;
pub trait Scalar: Sized {
fn parse_literal(value: &ScalarLiteral) -> Result<Self, ScalarError>;
fn parse_value(value: &JsonValue) -> Result<Self, ScalarError>;
fn serialize(&self) -> Result<JsonValue, ScalarError>;
}
pub trait ResolvedObject<C>: Send + Sync {
fn type_name(&self) -> &'static str;
fn resolve<'a>(
&'a self,
context: &'a C,
coordinate: FieldCoordinate<'a>,
arguments: &'a JsonMap,
) -> ::core::pin::Pin<
Box<
dyn ::core::future::Future<Output = Result<ResolvedValue<C>, ResolverError>>
+ Send
+ 'a,
>,
>;
fn resolve_with_literals<'a>(
&'a self,
context: &'a C,
coordinate: FieldCoordinate<'a>,
arguments: &'a JsonMap,
_argument_literals: &'a ScalarLiteralMap,
) -> ::core::pin::Pin<
Box<
dyn ::core::future::Future<Output = Result<ResolvedValue<C>, ResolverError>>
+ Send
+ 'a,
>,
> {
self.resolve(context, coordinate, arguments)
}
}
pub enum ResolvedValue<C = ()> {
Json(JsonValue),
List(Vec<ResolvedValue<C>>),
Object(Box<dyn ResolvedObject<C>>),
Error(ResolverError),
}
#[derive(Debug)]
pub struct ResolverError {
message: String,
extensions: Option<JsonMap>,
}
impl ResolverError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
extensions: None,
}
}
pub fn with_extension(mut self, key: &str, value: impl Into<JsonValue>) -> Self {
self.extensions
.get_or_insert_default()
.insert(key, value.into());
self
}
pub fn message(&self) -> &str {
&self.message
}
pub fn extensions(&self) -> Option<&JsonMap> {
self.extensions.as_ref()
}
}
#[derive(Serialize)]
#[serde(transparent)]
pub struct Response(ResponseKind);
#[derive(Serialize)]
#[serde(untagged)]
enum ResponseKind {
RequestError {
errors: Vec<GraphQLError>,
#[serde(skip)]
syntax_error: bool,
},
Execution(ExecutionResponse),
}
impl Response {
pub fn is_request_error(&self) -> bool {
matches!(self.0, ResponseKind::RequestError { .. })
}
pub fn is_syntax_error(&self) -> bool {
matches!(
self.0,
ResponseKind::RequestError {
syntax_error: true,
..
}
)
}
pub fn request_error(error: GraphQLError) -> Self {
Self::request_errors(vec![error])
}
pub fn request_errors(errors: Vec<GraphQLError>) -> Self {
Self(ResponseKind::RequestError {
errors,
syntax_error: false,
})
}
pub fn execution(data: Option<JsonMap>, errors: Vec<GraphQLError>) -> Self {
Self(ResponseKind::Execution(ExecutionResponse { data, errors }))
}
}
impl From<request::RequestError> for Response {
fn from(error: request::RequestError) -> Self {
Self(ResponseKind::RequestError {
errors: error.errors,
syntax_error: error.syntax_error,
})
}
}
mod request;
pub use request::Request;
pub enum GraphQLInput<T> {
Undefined,
Null,
Value(T),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id(String);
impl Id {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for Id {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for Id {
fn from(value: &str) -> Self {
Self(value.to_owned())
}
}
pub trait Field {
type Args: Send;
}
pub trait Resolver<F, C>: Send + Sync
where
F: Field,
C: Sync,
{
type Output: Send;
fn resolve<'a>(
&'a self,
context: &'a C,
args: F::Args,
) -> impl ::core::future::Future<Output = ::core::result::Result<Self::Output, ResolverError>>
+ Send
+ 'a;
}
pub trait Subscriber<F, C>: Send + Sync
where
F: Field,
C: Sync,
{
type Output: Send;
fn resolve<'a>(
&'a self,
context: &'a C,
args: F::Args,
) -> impl ::core::future::Future<
Output = ::core::result::Result<SourceStream<'a, Self::Output>, ResolverError>,
> + Send
+ 'a;
}
pub enum SourceEvent<T> {
Value(T),
FieldError(ResolverError),
SourceError(ResolverError),
}
pub struct SourceStream<'a, T> {
stream:
Option<::core::pin::Pin<Box<dyn futures_util::Stream<Item = SourceEvent<T>> + Send + 'a>>>,
}
impl<'a, T: Send + 'a> SourceStream<'a, T> {
pub fn from_stream(
stream: impl futures_util::Stream<Item = SourceEvent<T>> + Send + 'a,
) -> Self {
Self {
stream: Some(Box::pin(stream)),
}
}
pub fn map_values<U: Send + 'a>(
self,
mut map: impl FnMut(T) -> U + Send + 'a,
) -> SourceStream<'a, U> {
SourceStream::from_stream(futures_util::StreamExt::map(
self,
move |event| match event {
SourceEvent::Value(value) => SourceEvent::Value(map(value)),
SourceEvent::FieldError(error) => SourceEvent::FieldError(error),
SourceEvent::SourceError(error) => SourceEvent::SourceError(error),
},
))
}
}
impl<T> futures_util::Stream for SourceStream<'_, T> {
type Item = SourceEvent<T>;
fn poll_next(
mut self: ::core::pin::Pin<&mut Self>,
cx: &mut ::core::task::Context<'_>,
) -> ::core::task::Poll<Option<Self::Item>> {
let Some(stream) = self.stream.as_mut() else {
return ::core::task::Poll::Ready(None);
};
let next = stream.as_mut().poll_next(cx);
if matches!(
next,
::core::task::Poll::Ready(None | Some(SourceEvent::SourceError(_)))
) {
self.stream = None;
}
next
}
}
#[derive(Debug)]
pub enum SubscriptionError {
Source(ResolverError),
Internal,
}
impl ::core::fmt::Display for SubscriptionError {
fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
Self::Source(error) => formatter.write_str(error.message()),
Self::Internal => formatter.write_str("Internal subscription error"),
}
}
}
impl std::error::Error for SubscriptionError {}
type BoxResponseStream<'a> = ::core::pin::Pin<
Box<dyn futures_util::Stream<Item = Result<Response, SubscriptionError>> + Send + 'a>,
>;
pub struct ResponseStream<'a> {
stream: Option<BoxResponseStream<'a>>,
}
impl<'a> ResponseStream<'a> {
pub(crate) fn from_stream(
stream: impl futures_util::Stream<Item = Result<Response, SubscriptionError>> + Send + 'a,
) -> Self {
Self {
stream: Some(Box::pin(stream)),
}
}
}
impl futures_util::Stream for ResponseStream<'_> {
type Item = Result<Response, SubscriptionError>;
fn poll_next(
mut self: ::core::pin::Pin<&mut Self>,
cx: &mut ::core::task::Context<'_>,
) -> ::core::task::Poll<Option<Self::Item>> {
let Some(stream) = self.stream.as_mut() else {
return ::core::task::Poll::Ready(None);
};
let next = stream.as_mut().poll_next(cx);
if matches!(next, ::core::task::Poll::Ready(None | Some(Err(_)))) {
self.stream = None;
}
next
}
}
#[cfg(test)]
mod source_stream_tests {
use std::{
cell::Cell,
sync::atomic::{AtomicUsize, Ordering},
task::Poll,
};
use futures_util::{StreamExt, stream};
use super::{ResolverError, SourceEvent, SourceStream};
struct DropCount<'a>(&'a AtomicUsize);
impl Drop for DropCount<'_> {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
fn send<T: Send>(value: T) -> T {
value
}
fn unpin<T: Unpin>(_: &T) {}
#[tokio::test]
async fn accepts_borrowing_non_sync_non_unpin_streams() {
let label = String::from("Sheri");
let borrowed = label.as_str();
let state = Cell::new(false);
let input = stream::unfold(state, move |state| async move {
if state.get() {
None
} else {
state.set(true);
Some((SourceEvent::Value(borrowed), state))
}
});
let mut source = send(SourceStream::from_stream(input));
unpin(&source);
assert!(matches!(
source.next().await,
Some(SourceEvent::Value("Sheri"))
));
assert!(source.next().await.is_none());
assert!(source.next().await.is_none());
}
#[tokio::test]
async fn releases_and_fuses_after_source_error() {
let drops = AtomicUsize::new(0);
let guard = DropCount(&drops);
let mut emitted = false;
let input = stream::poll_fn(move |_| {
let _keep_alive = &guard;
assert!(!emitted, "source polled after terminal failure");
emitted = true;
Poll::Ready(Some(SourceEvent::<()>::SourceError(ResolverError::new(
"closed",
))))
});
let mut source = SourceStream::from_stream(input);
assert!(matches!(
source.next().await,
Some(SourceEvent::SourceError(_))
));
assert_eq!(drops.load(Ordering::SeqCst), 1);
assert!(source.next().await.is_none());
assert!(source.next().await.is_none());
}
#[tokio::test]
async fn maps_values_without_changing_error_kinds() {
let mut source = SourceStream::from_stream(stream::iter([
SourceEvent::Value(7),
SourceEvent::FieldError(ResolverError::new("field")),
SourceEvent::SourceError(ResolverError::new("source")),
]))
.map_values(|value| value.to_string());
assert!(matches!(source.next().await, Some(SourceEvent::Value(value)) if value == "7"));
assert!(matches!(
source.next().await,
Some(SourceEvent::FieldError(error)) if error.message() == "field"
));
assert!(matches!(
source.next().await,
Some(SourceEvent::SourceError(error)) if error.message() == "source"
));
assert!(source.next().await.is_none());
}
}
#[cfg(test)]
mod input_type_tests {
use super::{GraphQLInput, Id};
#[test]
fn graphql_input_distinguishes_undefined_null_and_value() {
assert!(matches!(
GraphQLInput::<i32>::Undefined,
GraphQLInput::Undefined
));
assert!(matches!(GraphQLInput::<i32>::Null, GraphQLInput::Null));
let input = GraphQLInput::Value(String::from("Sheri"));
let GraphQLInput::Value(value) = input else {
panic!("value input must preserve its payload");
};
assert_eq!(value, "Sheri");
}
#[test]
fn id_preserves_supplied_string_contents() {
let id = Id::from(String::from("001"));
assert_eq!(id.as_str(), "001");
}
}