use std::fmt::Write;
use crate::casings::CasingExt;
use super::indented;
use crate::{query_parsing::TypedValue, schema::OutputFieldType, Error};
#[derive(Debug, PartialEq)]
pub struct QueryFragment<'query, 'schema> {
pub fields: Vec<OutputField<'query, 'schema>>,
pub target_type: String,
pub variable_struct_name: Option<String>,
pub name: String,
}
impl std::fmt::Display for QueryFragment<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "#[derive(cynic::QueryFragment, Debug)]")?;
if self.target_type != self.name || self.variable_struct_name.is_some() {
write!(f, "#[cynic(")?;
if self.target_type != self.name {
write!(f, "graphql_type = \"{}\"", self.target_type)?;
}
if let Some(name) = &self.variable_struct_name {
if self.target_type != self.name {
write!(f, ", ")?;
}
write!(f, "variables = \"{}\"", name)?;
}
writeln!(f, ")]",)?;
}
writeln!(f, "pub struct {} {{", self.name)?;
for field in &self.fields {
write!(indented(f, 4), "{}", field)?;
}
writeln!(f, "}}")
}
}
#[derive(Debug, PartialEq)]
pub struct OutputField<'query, 'schema> {
pub name: &'schema str,
pub rename: Option<&'schema str>,
pub field_type: RustOutputFieldType,
pub arguments: Vec<FieldArgument<'query, 'schema>>,
}
impl std::fmt::Display for OutputField<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !self.arguments.is_empty() {
let arguments_string = self
.arguments
.iter()
.map(|arg| Ok(format!("{}: {}", arg.name, arg.to_literal()?)))
.collect::<Result<Vec<_>, Error>>()
.unwrap()
.join(", ");
writeln!(f, "#[arguments({})]", arguments_string)?;
}
let name = self.name.to_snake_case();
let type_spec = self.field_type.type_spec();
let mut output = super::Field::new(&name, &type_spec);
if let Some(rename) = self.rename {
output.add_rename(rename);
}
write!(f, "{}", output)
}
}
#[derive(Debug, PartialEq)]
#[allow(clippy::enum_variant_names)]
pub enum RustOutputFieldType {
NamedType(String),
ListType(Box<RustOutputFieldType>),
NonNullType(Box<RustOutputFieldType>),
}
impl RustOutputFieldType {
pub fn from_schema_type(
schema_type: &OutputFieldType<'_>,
name_override: Option<String>,
) -> RustOutputFieldType {
match schema_type {
OutputFieldType::NonNullType(inner) => RustOutputFieldType::NonNullType(Box::new(
RustOutputFieldType::from_schema_type(inner, name_override),
)),
OutputFieldType::ListType(inner) => RustOutputFieldType::ListType(Box::new(
RustOutputFieldType::from_schema_type(inner, name_override),
)),
OutputFieldType::NamedType(type_ref) => RustOutputFieldType::NamedType(
name_override
.or_else(|| {
let ty = type_ref.lookup().ok()?;
Some(ty.name().to_pascal_case())
})
.unwrap_or_else(|| "Unknown".to_string()),
),
}
}
pub fn type_spec(&self) -> String {
self.output_type_spec_imp(true)
}
fn output_type_spec_imp(&self, nullable: bool) -> String {
if let RustOutputFieldType::NonNullType(inner) = self {
return inner.output_type_spec_imp(false);
}
if nullable {
return format!("Option<{}>", self.output_type_spec_imp(false));
}
match self {
RustOutputFieldType::ListType(inner) => {
format!("Vec<{}>", inner.output_type_spec_imp(true))
}
RustOutputFieldType::NonNullType(_) => panic!("NonNullType somehow got past an if let"),
RustOutputFieldType::NamedType(s) => {
match s.as_ref() {
"Int" => return "i32".into(),
"Float" => return "f64".into(),
"Boolean" => return "bool".into(),
"Id" => return "cynic::Id".into(),
_ => {}
}
s.clone()
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct FieldArgument<'query, 'schema> {
pub name: &'schema str,
value: TypedValue<'query, 'schema>,
}
impl<'query, 'schema> FieldArgument<'query, 'schema> {
pub fn new(name: &'schema str, value: TypedValue<'query, 'schema>) -> Self {
FieldArgument { name, value }
}
pub fn to_literal(&self) -> Result<String, Error> {
use crate::query_parsing::LiteralContext;
self.value.to_literal(LiteralContext::Argument)
}
}