use crate::{client::ClientImpl, GraphQLQuery, QueryBody, QueryError, Response};
#[cfg(feature = "observable")]
use futures::{channel::mpsc::Receiver, task::Context, Stream};
use serde::{de::DeserializeOwned, Serialize};
use std::{
any::{Any, TypeId},
collections::HashMap,
fmt,
pin::Pin,
sync::Arc,
task::Poll
};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsValue;
use std::marker::PhantomData;
pub type ExchangeResult<R> = Result<OperationResult<R>, QueryError>;
#[async_trait]
pub trait Exchange: Send + Sync + 'static {
async fn run<Q: GraphQLQuery, C: Client>(
&self,
operation: Operation<Q::Variables>,
client: C
) -> ExchangeResult<Q::ResponseData>;
}
pub trait ExchangeFactory<TNext: Exchange> {
type Output: Exchange;
fn build(self, next: TNext) -> Self::Output;
}
pub trait QueryInfo<TVars> {
fn selection(variables: &TVars) -> Vec<FieldSelector>;
}
#[derive(PartialEq, Debug, Clone)]
pub enum OperationType {
Query,
Mutation,
Subscription
}
impl OperationType {
pub fn to_str(&self) -> &'static str {
match self {
OperationType::Query => "Query",
OperationType::Mutation => "Mutation",
OperationType::Subscription => "Subscription"
}
}
}
impl From<u8> for OperationType {
fn from(u: u8) -> Self {
match u {
1 => OperationType::Mutation,
2 => OperationType::Subscription,
_ => OperationType::Query
}
}
}
impl ToString for OperationType {
fn to_string(&self) -> String {
let str = match self {
OperationType::Query => "Query",
OperationType::Mutation => "Mutation",
OperationType::Subscription => "Subscription"
};
str.to_string()
}
}
#[repr(u8)]
#[derive(Debug, Clone, PartialEq)]
pub enum RequestPolicy {
CacheFirst = 1,
CacheOnly = 2,
NetworkOnly = 3,
CacheAndNetwork = 4
}
impl From<u8> for RequestPolicy {
fn from(value: u8) -> Self {
match value {
1 => RequestPolicy::CacheFirst,
2 => RequestPolicy::CacheOnly,
3 => RequestPolicy::NetworkOnly,
4 => RequestPolicy::CacheAndNetwork,
_ => unreachable!()
}
}
}
pub struct HeaderPair(pub String, pub String);
#[derive(Clone)]
pub enum FieldSelector {
Scalar(&'static str, String),
Object(&'static str, String, &'static str, Vec<FieldSelector>),
Union(
&'static str,
String,
Arc<dyn Fn(&str) -> Vec<FieldSelector>>
)
}
impl fmt::Debug for FieldSelector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FieldSelector::Scalar(field_name, args) => {
write!(f, "Scalar(field name: {}, args: {})", field_name, args)
}
FieldSelector::Object(field_name, args, typename, _) => write!(
f,
"Object(field name: {}, args: {}, typename: {})",
field_name, args, typename
),
FieldSelector::Union(field_name, args, _) => {
write!(f, "Union(field name: {}, args: {})", field_name, args)
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OperationMeta {
pub query_key: u32,
pub operation_type: OperationType,
pub involved_types: Vec<&'static str>
}
#[derive(Clone)]
pub struct OperationOptions {
pub url: String,
pub extra_headers: Option<Arc<dyn Fn() -> Vec<HeaderPair> + Send + Sync>>,
pub request_policy: RequestPolicy,
pub extensions: Option<Extensions>,
#[cfg(target_arch = "wasm32")]
pub fetch: Option<js_sys::Function>
}
unsafe impl Send for OperationOptions {}
unsafe impl Sync for OperationOptions {}
#[derive(Clone)]
pub struct Operation<V: Serialize + Clone + Send + Sync> {
pub key: u64,
pub meta: OperationMeta,
pub query: QueryBody<V>,
pub options: OperationOptions
}
#[derive(Clone, Debug, PartialEq, Copy, Serialize)]
pub enum ResultSource {
Cache,
Network
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct DebugInfo {
pub source: ResultSource,
#[serde(rename = "didDedup")]
pub did_dedup: bool
}
#[derive(Clone, Debug, PartialEq)]
pub struct OperationResult<R: DeserializeOwned + Send + Sync + Clone> {
pub key: u64,
pub meta: OperationMeta,
pub response: Response<R>
}
#[cfg(feature = "observable")]
pub struct Observable<T, M: Exchange> {
inner: Receiver<Arc<dyn Any + Send + Sync>>,
client: Arc<ClientImpl<M>>,
key: u64,
index: usize,
t: PhantomData<T>
}
#[cfg(feature = "observable")]
impl<T: Clone, M: Exchange> Observable<T, M> {
pub(crate) fn new(
key: u64,
inner: Receiver<Arc<dyn Any + Send + Sync>>,
client: Arc<ClientImpl<M>>,
index: usize
) -> Self {
Observable {
inner,
client,
key,
index,
t: PhantomData
}
}
}
#[cfg(feature = "observable")]
impl<T: Clone, M: Exchange> Observable<T, M> {
pub fn rerun(&self) {
self.client.rerun_query(self.key);
}
}
#[cfg(feature = "observable")]
impl<T, M: Exchange> Stream for Observable<T, M>
where
T: 'static + Unpin + Clone
{
type Item = T;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let inner = &mut self.get_mut().inner;
let poll = <Receiver<Arc<dyn Any + Send + Sync>> as Stream>::poll_next(Pin::new(inner), cx);
match poll {
Poll::Ready(Some(boxed)) => {
let cast: &T = (&*boxed).downcast_ref::<T>().unwrap();
Poll::Ready(Some(cast.clone()))
}
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending
}
}
}
#[cfg(feature = "observable")]
impl<T, M: Exchange> Drop for Observable<T, M> {
fn drop(&mut self) {
self.client.clear_observable(self.key, self.index)
}
}
pub trait Extension: Sized + Clone + Send + Sync + 'static {
#[cfg(target_arch = "wasm32")]
fn from_js(value: JsValue) -> Option<Self>;
}
pub struct ExtensionMap {
rust: HashMap<TypeId, Box<dyn Any>>,
#[cfg(target_arch = "wasm32")]
js: JsValue
}
unsafe impl Send for ExtensionMap {}
unsafe impl Sync for ExtensionMap {}
impl Default for ExtensionMap {
fn default() -> Self {
Self {
rust: HashMap::new(),
#[cfg(target_arch = "wasm32")]
js: JsValue::NULL
}
}
}
impl ExtensionMap {
pub fn new() -> Self {
Self::default()
}
#[cfg(target_arch = "wasm32")]
pub fn from_js(value: JsValue) -> Option<Self> {
if value.is_object() {
Some(Self {
rust: HashMap::new(),
js: value
})
} else {
None
}
}
pub fn insert<T: Extension>(&mut self, value: T) {
self.rust.insert(TypeId::of::<T>(), Box::new(value));
}
pub fn get<T: Extension, S: Into<String>>(&self, js_key: S) -> Option<T> {
self.get_rust().or_else(|| self.get_js(js_key.into()))
}
#[cfg(target_arch = "wasm32")]
fn get_js<T: Extension>(&self, js_key: String) -> Option<T> {
let key: JsValue = js_key.into();
js_sys::Reflect::get(&self.js, &key)
.ok()
.and_then(|value| T::from_js(value))
}
#[cfg(not(target_arch = "wasm32"))]
fn get_js<T: Extension>(&self, _js_key: String) -> Option<T> {
None
}
fn get_rust<T: Extension>(&self) -> Option<T> {
self.rust
.get(&TypeId::of::<T>())
.map(|boxed| (&*boxed).downcast_ref().unwrap())
.map(Clone::clone)
}
}
pub type Extensions = Arc<ExtensionMap>;
#[derive(Default, Clone)]
pub struct QueryOptions {
pub url: Option<String>,
pub extra_headers: Option<Arc<dyn Fn() -> Vec<HeaderPair> + Send + Sync>>,
pub request_policy: Option<RequestPolicy>,
pub extensions: Option<Extensions>
}
pub trait Client: Clone + Send + Sync + 'static {
fn rerun_query(&self, query_key: u64);
fn push_result<R>(&self, query_key: u64, result: ExchangeResult<R>)
where
R: DeserializeOwned + Send + Sync + Clone + 'static;
}