graphql-composition 0.12.2

An implementation of GraphQL federated schema composition
Documentation
use std::fmt;

use crate::federated_graph::{
    Directive, EXTENSION_DIRECTIVE_DIRECTIVE, FederatedGraph, OverrideLabel, OverrideSource, StringId, Value,
    directives::*, render_sdl::display_utils::render_field_type,
};

use super::{
    display_utils::{AnyValue, DirectiveWriter, SelectionSetDisplay},
    render_federated_sdl::ListSizeRender,
};

pub(crate) fn write_directive<'a, 'b: 'a>(
    f: &'a mut fmt::Formatter<'b>,
    directive: &Directive,
    graph: &'a FederatedGraph,
) -> fmt::Result {
    match directive {
        Directive::Authenticated => {
            DirectiveWriter::new("authenticated", f, graph)?;
        }
        Directive::Inaccessible => {
            DirectiveWriter::new("inaccessible", f, graph)?;
        }
        Directive::OneOf => {
            DirectiveWriter::new("oneOf", f, graph)?;
        }
        Directive::CompositeLookup { graph: subgraph_id } => {
            DirectiveWriter::new("composite__lookup", f, graph)?
                .arg("graph", Value::EnumValue(graph.at(*subgraph_id).join_graph_enum_value))?;
        }
        Directive::CompositeDerive { graph: subgraph_id } => {
            DirectiveWriter::new("composite__derive", f, graph)?
                .arg("graph", Value::EnumValue(graph.at(*subgraph_id).join_graph_enum_value))?;
        }
        Directive::CompositeInternal { graph: subgraph_id } => {
            DirectiveWriter::new("composite__internal", f, graph)?
                .arg("graph", Value::EnumValue(graph.at(*subgraph_id).join_graph_enum_value))?;
        }
        Directive::CompositeRequire {
            graph: subgraph_id,
            field,
        } => {
            DirectiveWriter::new("composite__require", f, graph)?
                .arg("graph", Value::EnumValue(graph.at(*subgraph_id).join_graph_enum_value))?
                .arg("field", Value::String(*field))?;
        }
        Directive::CompositeIs {
            graph: subgraph_id,
            field,
        } => {
            DirectiveWriter::new("composite__is", f, graph)?
                .arg("graph", Value::EnumValue(graph.at(*subgraph_id).join_graph_enum_value))?
                .arg("field", Value::String(*field))?;
        }
        Directive::Deprecated { reason } => {
            let directive = DirectiveWriter::new("deprecated", f, graph)?;

            if let Some(reason) = reason {
                directive.arg("reason", Value::String(*reason))?;
            }
        }
        Directive::Policy(policies) => {
            let policies = Value::List(
                policies
                    .iter()
                    .map(|p| Value::List(p.iter().map(|p| Value::String(*p)).collect()))
                    .collect(),
            );

            DirectiveWriter::new("policy", f, graph)?.arg("policies", policies)?;
        }
        Directive::RequiresScopes(scopes) => {
            let scopes = Value::List(
                scopes
                    .iter()
                    .map(|p| Value::List(p.iter().map(|p| Value::String(*p)).collect()))
                    .collect(),
            );

            DirectiveWriter::new("requiresScopes", f, graph)?.arg("scopes", scopes)?;
        }
        Directive::Cost { weight } => {
            DirectiveWriter::new("cost", f, graph)?.arg("weight", Value::Int(*weight as i64))?;
        }
        Directive::JoinGraph(JoinGraphDirective { name, url }) => {
            let directive = DirectiveWriter::new("join__graph", f, graph)?.arg("name", Value::String(*name))?;

            if let Some(url) = url {
                directive.arg("url", Value::String(*url))?;
            }
        }
        Directive::Other(OtherDirective { name, arguments }) => {
            let mut directive = DirectiveWriter::new(&graph[*name], f, graph)?;

            for (name, value) in arguments {
                directive = directive.arg(&graph[*name], value.clone())?;
            }
        }
        Directive::ExtensionDirective(directive) => {
            render_extension_directive(f, directive, graph)?;
        }
        Directive::JoinEnumValue(JoinEnumValueDirective { graph: subgraph_id }) => {
            DirectiveWriter::new("join__enumValue", f, graph)?
                .arg("graph", Value::EnumValue(graph.at(*subgraph_id).join_graph_enum_value))?;
        }
        Directive::JoinField(directive) => {
            render_join_field_directive(f, directive, graph)?;
        }
        Directive::JoinType(directive) => {
            render_join_type_directive(f, directive, graph)?;
        }
        Directive::JoinUnionMember(directive) => {
            render_join_union_member_directive(f, directive, graph)?;
        }
        Directive::JoinImplements(directive) => {
            render_join_implements_directive(f, directive, graph)?;
        }
        Directive::ListSize(list_size) => {
            f.write_fmt(format_args!("{}", ListSizeRender { list_size, graph }))?;
        }
    }

    Ok(())
}

fn render_extension_directive(
    f: &mut fmt::Formatter<'_>,
    directive: &ExtensionDirective,
    graph: &FederatedGraph,
) -> fmt::Result {
    let writer = DirectiveWriter::new(EXTENSION_DIRECTIVE_DIRECTIVE, f, graph)?
        .arg(
            "graph",
            Value::EnumValue(graph[directive.subgraph_id].join_graph_enum_value),
        )?
        .arg(
            "extension",
            Value::EnumValue(graph[directive.extension_id].enum_value_id),
        )?
        .arg("name", Value::String(directive.name))?;
    if let Some(arguments) = directive.arguments.as_ref() {
        writer.arg("arguments", arguments)?;
    }
    Ok(())
}

fn render_join_union_member_directive(
    f: &mut fmt::Formatter<'_>,
    directive: &JoinUnionMemberDirective,
    graph: &FederatedGraph,
) -> fmt::Result {
    DirectiveWriter::new("join__unionMember", f, graph)?
        .arg(
            "graph",
            Value::EnumValue(graph.at(directive.subgraph_id).join_graph_enum_value),
        )?
        .arg("member", Value::String(graph.view(directive.object_id).name))?;

    Ok(())
}

fn render_join_field_directive(
    f: &mut fmt::Formatter<'_>,
    directive: &JoinFieldDirective,
    graph: &FederatedGraph,
) -> fmt::Result {
    let mut writer = DirectiveWriter::new("join__field", f, graph)?;
    if let Some(subgraph_id) = directive.subgraph_id {
        writer = writer.arg("graph", Value::EnumValue(graph.at(subgraph_id).join_graph_enum_value))?;
    }

    if let Some(requires) = directive.requires.as_ref().filter(|requires| !requires.is_empty()) {
        writer = writer.arg("requires", SelectionSetDisplay(requires, graph))?;
    }

    if let Some(provides) = directive.provides.as_ref().filter(|provides| !provides.is_empty()) {
        writer = writer.arg("provides", SelectionSetDisplay(provides, graph))?;
    }

    if let Some(ty) = &directive.r#type {
        writer = writer.arg("type", render_field_type(ty, graph))?;
    }

    if directive.external {
        writer = writer.arg("external", Value::Boolean(true))?;
    }

    if let Some(r#override) = &directive.r#override {
        let name = match r#override {
            OverrideSource::Subgraph(subgraph_id) => &graph.at(*subgraph_id).then(|subgraph| subgraph.name),
            OverrideSource::Missing(string) => &graph[*string],
        };
        writer = writer.arg("override", name.as_str())?;
    }

    if let Some(override_label) = &directive.override_label {
        match override_label {
            OverrideLabel::Percent(_) => writer.arg("overrideLabel", format!("{override_label}")),
            OverrideLabel::Unknown(unknown) => writer.arg("overrideLabel", unknown.clone()),
        }?;
    }

    Ok(())
}

fn render_join_type_directive(
    f: &mut fmt::Formatter<'_>,
    directive: &JoinTypeDirective,
    graph: &FederatedGraph,
) -> fmt::Result {
    let mut writer = DirectiveWriter::new("join__type", f, graph)?.arg(
        "graph",
        Value::EnumValue(graph.at(directive.subgraph_id).join_graph_enum_value),
    )?;

    if let Some(key) = directive.key.as_ref().filter(|key| !key.is_empty()) {
        writer = writer.arg("key", SelectionSetDisplay(key, graph))?;
    }

    if !directive.resolvable {
        writer = writer.arg("resolvable", Value::Boolean(false))?;
    }

    if directive.is_interface_object {
        writer.arg("isInterfaceObject", Value::Boolean(true))?;
    }

    Ok(())
}

fn render_join_implements_directive(
    f: &mut fmt::Formatter<'_>,
    directive: &JoinImplementsDirective,
    graph: &FederatedGraph,
) -> fmt::Result {
    DirectiveWriter::new("join__implements", f, graph)?
        .arg(
            "graph",
            Value::EnumValue(graph.at(directive.subgraph_id).join_graph_enum_value),
        )?
        .arg("interface", Value::String(graph.view(directive.interface_id).name))?;

    Ok(())
}

pub(crate) fn render_extension_link_directive(
    f: &mut fmt::Formatter<'_>,
    url: StringId,
    schema_directives: &[ExtensionLinkSchemaDirective],
    graph: &FederatedGraph,
) -> fmt::Result {
    f.write_str(" ")?;
    let directive = DirectiveWriter::new("extension__link", f, graph)?.arg("url", Value::String(url))?;

    if !schema_directives.is_empty() {
        let directives = schema_directives
            .iter()
            .map(
                |ExtensionLinkSchemaDirective {
                     subgraph_id,
                     name,
                     arguments,
                 }| {
                    let mut fields: Vec<(&'static str, AnyValue<'_>)> = Vec::with_capacity(3);

                    fields.push((
                        "graph",
                        Value::EnumValue(graph.at(*subgraph_id).join_graph_enum_value).into(),
                    ));

                    fields.push(("name", Value::String(*name).into()));

                    if let Some(arguments) = arguments {
                        fields.push(("arguments", AnyValue::DirectiveArguments(arguments.as_slice())));
                    }

                    AnyValue::Object(fields)
                },
            )
            .collect::<Vec<_>>();

        directive.arg("schemaDirectives", directives)?;
    }

    Ok(())
}