use std::{borrow::Cow, collections::HashSet, marker::PhantomData, rc::Rc, sync::mpsc};
use crate::{
queries::{SelectionBuilder, SelectionSet},
schema::{MutationRoot, QueryRoot, SubscriptionRoot},
QueryFragment, QueryVariables,
};
use super::{variables::VariableDefinitions, Operation};
pub struct OperationBuilder<QueryFragment, Variables = ()> {
variables: Option<Variables>,
operation_kind: OperationKind,
operation_name: Option<Cow<'static, str>>,
features: HashSet<String>,
phantom: PhantomData<fn() -> QueryFragment>,
}
impl<Fragment, Variables> OperationBuilder<Fragment, Variables>
where
Fragment: QueryFragment,
Variables: QueryVariables,
{
fn new(operation_kind: OperationKind) -> Self {
OperationBuilder {
variables: None,
operation_kind,
operation_name: Fragment::name(),
features: HashSet::new(),
phantom: PhantomData,
}
}
pub fn query() -> Self
where
Fragment::SchemaType: QueryRoot,
{
Self::new(OperationKind::Query)
}
pub fn mutation() -> Self
where
Fragment::SchemaType: MutationRoot,
{
Self::new(OperationKind::Mutation)
}
pub fn subscription() -> Self
where
Fragment::SchemaType: SubscriptionRoot,
{
Self::new(OperationKind::Subscription)
}
pub fn with_variables(self, variables: Variables) -> Self {
Self {
variables: Some(variables),
..self
}
}
pub fn set_variables(&mut self, variables: Variables) {
self.variables = Some(variables);
}
pub fn with_feature_enabled(mut self, feature: &str) -> Self {
self.enable_feature(feature);
self
}
pub fn enable_feature(&mut self, feature: &str) {
self.features.insert(feature.to_string());
}
pub fn with_operation_name(self, name: &str) -> Self {
OperationBuilder {
operation_name: Some(Cow::Owned(name.to_string())),
..self
}
}
pub fn set_operation_name(&mut self, name: &str) {
self.operation_name = Some(Cow::Owned(name.to_string()));
}
pub fn build(self) -> Result<super::Operation<Fragment, Variables>, OperationBuildError> {
use std::fmt::Write;
let features_enabled = Rc::new(self.features);
let mut selection_set = SelectionSet::default();
let (variable_tx, variable_rx) = mpsc::channel();
let builder = SelectionBuilder::<_, Fragment::VariablesFields>::new(
&mut selection_set,
&variable_tx,
&features_enabled,
);
Fragment::query(builder);
let vars = VariableDefinitions::new::<Variables>(variable_rx.try_iter().collect());
let name_str = self.operation_name.as_deref().unwrap_or("");
let declaration_str = match self.operation_kind {
OperationKind::Query => "query",
OperationKind::Mutation => "mutation",
OperationKind::Subscription => "subscription",
};
let mut query = String::new();
writeln!(
&mut query,
"{declaration_str} {name_str}{vars}{selection_set}"
)?;
Ok(Operation {
query,
variables: self.variables.ok_or(OperationBuildError::VariablesNotSet)?,
operation_name: self.operation_name,
phantom: PhantomData,
})
}
}
#[derive(thiserror::Error, Debug)]
pub enum OperationBuildError {
#[error("You need to call with_variables or set_variables before calling build")]
VariablesNotSet,
#[error("Couldn't format the query into a string: {0}")]
CouldntBuildQueryString(#[from] std::fmt::Error),
}
enum OperationKind {
Query,
Mutation,
Subscription,
}