docql 0.3.2

Generate static HTML documentation for GraphQL APIs.
Documentation
use super::{handlebars_helpers, schema, Result};
use chrono::NaiveDate;
use serde::Serialize;

pub struct Renderer<'a> {
    schema_name: String,
    date: NaiveDate,
    schema: &'a schema::Schema,
    handlebars: handlebars::Handlebars<'a>,
}

impl<'a> Renderer<'a> {
    pub fn new(schema_name: String, date: NaiveDate, schema: &'a schema::Schema) -> Result<Self> {
        let mut handlebars = handlebars::Handlebars::new();
        handlebars.set_strict_mode(true);
        handlebars
            .register_template_string("layout", include_str!("templates/layout.handlebars"))?;
        handlebars.register_template_string("index", include_str!("templates/index.handlebars"))?;
        handlebars
            .register_template_string("object", include_str!("templates/object.handlebars"))?;
        handlebars.register_template_string(
            "input_object",
            include_str!("templates/input_object.handlebars"),
        )?;
        handlebars
            .register_template_string("scalar", include_str!("templates/scalar.handlebars"))?;
        handlebars.register_template_string("enum", include_str!("templates/enum.handlebars"))?;
        handlebars.register_template_string(
            "interface",
            include_str!("templates/interface.handlebars"),
        )?;
        handlebars.register_template_string("union", include_str!("templates/union.handlebars"))?;

        handlebars.register_template_string(
            "fields",
            include_str!("templates/partials/fields.handlebars"),
        )?;
        handlebars.register_template_string(
            "possible_types",
            include_str!("templates/partials/possible_types.handlebars"),
        )?;
        handlebars
            .register_template_string("uses", include_str!("templates/partials/uses.handlebars"))?;

        handlebars.register_helper(
            "t",
            Box::new(handlebars_helpers::TypeRefRenderer::new(&schema)),
        );
        handlebars.register_helper(
            "docblock",
            Box::new(handlebars_helpers::Docblock::default()),
        );
        handlebars.register_helper("kind", Box::new(handlebars_helpers::Kind::default()));

        Ok(Self {
            schema_name,
            date,
            schema,
            handlebars,
        })
    }
}

impl Renderer<'_> {
    pub fn render_index(&self) -> Result<String> {
        self.render(
            "index",
            &self.schema_name,
            &IndexContext::new(&self.schema_name, self.schema),
        )
    }

    pub fn render_object(&self, object: &schema::FullType) -> Result<String> {
        self.render(
            "object",
            &object.name,
            &ObjectContext::new(&self.schema_name, object, self.schema.find_uses(object)),
        )
    }

    pub fn render_input_object(&self, input_object: &schema::FullType) -> Result<String> {
        self.render(
            "input_object",
            &input_object.name,
            &InputObjectContext::new(
                &self.schema_name,
                input_object,
                self.schema.find_uses(input_object),
            ),
        )
    }

    pub fn render_scalar(&self, scalar: &schema::FullType) -> Result<String> {
        self.render(
            "scalar",
            &scalar.name,
            &ScalarContext::new(&self.schema_name, scalar, self.schema.find_uses(scalar)),
        )
    }

    pub fn render_enum(&self, enum_type: &schema::FullType) -> Result<String> {
        self.render(
            "enum",
            &enum_type.name,
            &EnumContext::new(
                &self.schema_name,
                enum_type,
                self.schema.find_uses(enum_type),
            ),
        )
    }

    pub fn render_interface(&self, interface: &schema::FullType) -> Result<String> {
        self.render(
            "interface",
            &interface.name,
            &InterfaceContext::new(
                &self.schema_name,
                interface,
                self.schema.find_uses(interface),
            ),
        )
    }

    pub fn render_union(&self, union: &schema::FullType) -> Result<String> {
        self.render(
            "union",
            &union.name,
            &UnionContext::new(&self.schema_name, union, self.schema.find_uses(union)),
        )
    }

    #[inline]
    fn render<T>(&self, template: &str, title: &str, t: &T) -> Result<String>
    where
        T: Serialize,
    {
        let rendered = self.handlebars.render(template, &t)?;
        let html = self.handlebars.render(
            "layout",
            &LayoutContext {
                title: title,
                content: &rendered,
                date_iso: self.date.format("%Y-%m-%d").to_string(),
                date_human: self.date.format("%-e %b %Y").to_string(),
            },
        )?;
        Ok(html)
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct LayoutContext<'a> {
    title: &'a str,
    content: &'a str,
    date_iso: String,
    date_human: String,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct IndexContext<'a> {
    schema_name: &'a str,
    query_type: Option<&'a str>,
    mutation_type: Option<&'a str>,
}

impl<'a> IndexContext<'a> {
    fn new(schema_name: &'a str, schema: &'a schema::Schema) -> Self {
        Self {
            schema_name,
            query_type: schema.query_type.as_ref().map(|t| t.name.as_str()),
            mutation_type: schema.mutation_type.as_ref().map(|t| t.name.as_str()),
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct ObjectContext<'a> {
    schema_name: &'a str,
    object: &'a schema::FullType,
    uses: Vec<schema::TypeUse<'a>>,
}

impl<'a> ObjectContext<'a> {
    fn new(
        schema_name: &'a str,
        object: &'a schema::FullType,
        uses: Vec<schema::TypeUse<'a>>,
    ) -> Self {
        Self {
            schema_name,
            object,
            uses,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct InputObjectContext<'a> {
    schema_name: &'a str,
    input_object: &'a schema::FullType,
    uses: Vec<schema::TypeUse<'a>>,
}

impl<'a> InputObjectContext<'a> {
    fn new(
        schema_name: &'a str,
        input_object: &'a schema::FullType,
        uses: Vec<schema::TypeUse<'a>>,
    ) -> Self {
        Self {
            schema_name,
            input_object,
            uses,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct ScalarContext<'a> {
    schema_name: &'a str,
    scalar: &'a schema::FullType,
    uses: Vec<schema::TypeUse<'a>>,
}

impl<'a> ScalarContext<'a> {
    fn new(
        schema_name: &'a str,
        scalar: &'a schema::FullType,
        uses: Vec<schema::TypeUse<'a>>,
    ) -> Self {
        Self {
            schema_name,
            scalar,
            uses,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct EnumContext<'a> {
    schema_name: &'a str,
    #[serde(rename = "enum")]
    enum_type: &'a schema::FullType,
    uses: Vec<schema::TypeUse<'a>>,
}

impl<'a> EnumContext<'a> {
    fn new(
        schema_name: &'a str,
        enum_type: &'a schema::FullType,
        uses: Vec<schema::TypeUse<'a>>,
    ) -> Self {
        Self {
            schema_name,
            enum_type,
            uses,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct InterfaceContext<'a> {
    schema_name: &'a str,
    interface: &'a schema::FullType,
    uses: Vec<schema::TypeUse<'a>>,
}

impl<'a> InterfaceContext<'a> {
    fn new(
        schema_name: &'a str,
        interface: &'a schema::FullType,
        uses: Vec<schema::TypeUse<'a>>,
    ) -> Self {
        Self {
            schema_name,
            interface,
            uses,
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct UnionContext<'a> {
    schema_name: &'a str,
    union: &'a schema::FullType,
    uses: Vec<schema::TypeUse<'a>>,
}

impl<'a> UnionContext<'a> {
    fn new(
        schema_name: &'a str,
        union: &'a schema::FullType,
        uses: Vec<schema::TypeUse<'a>>,
    ) -> Self {
        Self {
            schema_name,
            union,
            uses,
        }
    }
}