apollo-language-server 0.7.0

A GraphQL language server with first-class support for Apollo Federation
Documentation
use apollo_compiler::ast::{InputObjectTypeDefinition, ScalarTypeDefinition};
use once_cell::sync::Lazy;
use std::collections::HashMap;

use super::{Spec, SpecDirective, SpecStatus, SpecType, SpecsByVersion};

pub const CONNECT_SPEC_NAME: &str = "connect";
pub const CONNECT_DIRECTIVE: &str = "connect";
// TODO: better description with a link to docs
pub const CONNECT_DESCRIPTION: &str =
    "The `@connect` directive specifies how to fetch data from a remote source.";

pub const SOURCE_DIRECTIVE: &str = "source";
// TODO: better description with a link to docs
pub const SOURCE_DESCRIPTION: &str =
    "The `@source` directive specifies the base URL and headers for a remote source.";

pub const JSON_SELECTION_SCALAR: &str = "JSONSelection";
pub const URL_PATH_TEMPLATE_SCALAR: &str = "URLPathTemplate";
pub const BATCH_SETTINGS_INPUT: &str = "ConnectBatch";
pub const ERROR_MAPPING_INPUT: &str = "ConnectorErrors";
pub const CONNECT_HTTP_INPUT: &str = "ConnectHTTP";
pub const HTTP_HEADER_MAPPING_INPUT: &str = "HTTPHeaderMapping";
pub const SOURCE_HTTP_INPUT: &str = "SourceHTTP";

pub static CONNECT_SPECS_BY_VERSION: Lazy<SpecsByVersion> = {
    Lazy::new(|| {
        let mut connect_versions: SpecsByVersion = HashMap::new();

        let connect_json_selection_scalar =
            SpecType::<ScalarTypeDefinition>::new(JSON_SELECTION_SCALAR);
        let connect_url_path_template_scalar =
            SpecType::<ScalarTypeDefinition>::new(URL_PATH_TEMPLATE_SCALAR);

        // introduced in 0.1 specification: https://github.com/apollographql/router/blob/v2.0.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L406-L410
        // unchanged in 0.4 specification: https://github.com/apollographql/router/blob/v2.11.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L543-L547
        let http_header_mapping_input = SpecType::<InputObjectTypeDefinition>::new(
            "input HTTPHeaderMapping { name: String!, from: String, value: [String!] }",
        );

        // introduced in 0.2 specification: https://github.com/apollographql/router/blob/v2.2.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L519-L521
        // unchanged in 0.4 specification: https://github.com/apollographql/router/blob/v2.11.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L561-L563
        let batch_settings_input =
            SpecType::<InputObjectTypeDefinition>::new("input ConnectBatch { maxSize: Int }");

        // introduced in 0.3 specification: https://github.com/apollographql/router/blob/v2.9.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L565-L568
        // unchanged in 0.4 specification: https://github.com/apollographql/router/blob/v2.11.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L565-L568
        let error_mapping_input = SpecType::<InputObjectTypeDefinition>::new(
            "input ConnectorErrors { message: JSONSelection extensions: JSONSelection }",
        );

        // 0.1 specification: https://github.com/apollographql/router/blob/v2.0.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L412-L420
        let connect_http_input_v0_1 = SpecType::<InputObjectTypeDefinition>::new(
            "input ConnectHTTP { GET: URLPathTemplate, POST: URLPathTemplate, PUT: URLPathTemplate, PATCH: URLPathTemplate, DELETE: URLPathTemplate, body: JSONSelection, headers: [HTTPHeaderMapping!] }",
        );

        // 0.2+ specification adds path and queryParams: https://github.com/apollographql/router/blob/v2.2.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L507-L517
        // unchanged in 0.4 specification: https://github.com/apollographql/router/blob/v2.11.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L549-L559
        let connect_http_input_v0_2_latest = SpecType::<InputObjectTypeDefinition>::new(
            "input ConnectHTTP { GET: URLPathTemplate, POST: URLPathTemplate, PUT: URLPathTemplate, PATCH: URLPathTemplate, DELETE: URLPathTemplate, body: JSONSelection, headers: [HTTPHeaderMapping!], path: JSONSelection, queryParams: JSONSelection }",
        );

        // 0.1 specification: https://github.com/apollographql/router/blob/v2.0.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L422-L425
        let source_http_input_v0_1 = SpecType::<InputObjectTypeDefinition>::new(
            "input SourceHTTP { baseURL: String!, headers: [HTTPHeaderMapping!] }",
        );

        // 0.2+ specification adds path and queryParams: https://github.com/apollographql/router/blob/v2.2.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L523-L528
        // unchanged until 0.4 specification: https://github.com/apollographql/router/blob/v2.11.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L570-L575
        let source_http_input_v0_2_latest = SpecType::<InputObjectTypeDefinition>::new(
            "input SourceHTTP { baseURL: String!, headers: [HTTPHeaderMapping!], path: JSONSelection, queryParams: JSONSelection }",
        );

        // 0.1 specification: https://github.com/apollographql/router/blob/v2.0.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L387
        let connect_directive_v0_1 = SpecDirective::new(
            // technically this did not have ` | OBJECT` at the time, but the language server has treated that as valid for a long time, even for 0.1 and 0.2
            "directive @connect(source: String, http: ConnectHTTP, selection: JSONSelection!, entity: Boolean) repeatable on FIELD_DEFINITION | OBJECT",
            CONNECT_DESCRIPTION,
        );

        // 0.2 specification: https://github.com/apollographql/router/blob/v2.2.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L482
        let connect_directive_v0_2 = SpecDirective::new(
            // technically this did not have ` | OBJECT` at the time, but the language server has treated that as valid for a long time, even for 0.1 and 0.2
            "directive @connect(source: String, http: ConnectHTTP, batch: ConnectBatch, selection: JSONSelection!, entity: Boolean) repeatable on FIELD_DEFINITION | OBJECT",
            CONNECT_DESCRIPTION,
        );

        // 0.3 specification: https://github.com/apollographql/router/blob/v2.9.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L524
        // 0.4 specification: https://github.com/apollographql/router/blob/v2.11.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L524
        let connect_directive_v0_3_latest = SpecDirective::new(
            "directive @connect(source: String, http: ConnectHTTP, batch: ConnectBatch, errors: ConnectorErrors, isSuccess: JSONSelection, selection: JSONSelection!, entity: Boolean, id: String) repeatable on FIELD_DEFINITION | OBJECT",
            CONNECT_DESCRIPTION,
        );

        // 0.1 specification: https://github.com/apollographql/router/blob/v2.0.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L389
        // 0.2 specification: https://github.com/apollographql/router/blob/v2.2.0/apollo-federation/src/sources/connect/spec/type_and_directive_specifications.rs#L484
        let source_directive_v0_1_0_2 = SpecDirective::new(
            "directive @source(name: String!, http: SourceHTTP) repeatable on SCHEMA",
            SOURCE_DESCRIPTION,
        );

        // 0.3 specification: https://github.com/apollographql/router/blob/v2.9.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L526
        // 0.4 specification: https://github.com/apollographql/router/blob/v2.11.0/apollo-federation/src/connectors/spec/type_and_directive_specifications.rs#L526
        let source_directive_v0_3_latest = SpecDirective::new(
            "directive @source(name: String!, http: SourceHTTP, errors: ConnectorErrors, isSuccess: JSONSelection) repeatable on SCHEMA",
            SOURCE_DESCRIPTION,
        );

        // --- Build version specs ---

        let connect0_1 = Spec {
            status: SpecStatus::Supported,
            scalars: HashMap::from([
                (
                    JSON_SELECTION_SCALAR.to_string(),
                    connect_json_selection_scalar.clone(),
                ),
                (
                    URL_PATH_TEMPLATE_SCALAR.to_string(),
                    connect_url_path_template_scalar.clone(),
                ),
            ]),
            input_types: HashMap::from([
                (CONNECT_HTTP_INPUT.to_string(), connect_http_input_v0_1),
                (SOURCE_HTTP_INPUT.to_string(), source_http_input_v0_1),
                (
                    HTTP_HEADER_MAPPING_INPUT.to_string(),
                    http_header_mapping_input.clone(),
                ),
            ]),
            directives: HashMap::from([
                (CONNECT_DIRECTIVE.to_string(), connect_directive_v0_1),
                (
                    SOURCE_DIRECTIVE.to_string(),
                    source_directive_v0_1_0_2.clone(),
                ),
            ]),
            enums: HashMap::new(),
        };

        let connect0_2 = Spec {
            status: SpecStatus::Supported,
            scalars: HashMap::from([
                (
                    JSON_SELECTION_SCALAR.to_string(),
                    connect_json_selection_scalar.clone(),
                ),
                (
                    URL_PATH_TEMPLATE_SCALAR.to_string(),
                    connect_url_path_template_scalar.clone(),
                ),
            ]),
            input_types: HashMap::from([
                (
                    BATCH_SETTINGS_INPUT.to_string(),
                    batch_settings_input.clone(),
                ),
                (
                    CONNECT_HTTP_INPUT.to_string(),
                    connect_http_input_v0_2_latest.clone(),
                ),
                (
                    SOURCE_HTTP_INPUT.to_string(),
                    source_http_input_v0_2_latest.clone(),
                ),
                (
                    HTTP_HEADER_MAPPING_INPUT.to_string(),
                    http_header_mapping_input.clone(),
                ),
            ]),
            directives: HashMap::from([
                (CONNECT_DIRECTIVE.to_string(), connect_directive_v0_2),
                (SOURCE_DIRECTIVE.to_string(), source_directive_v0_1_0_2),
            ]),
            enums: HashMap::new(),
        };

        let connect0_3 = Spec {
            status: SpecStatus::Latest,
            scalars: HashMap::from([
                (
                    JSON_SELECTION_SCALAR.to_string(),
                    connect_json_selection_scalar.clone(),
                ),
                (
                    URL_PATH_TEMPLATE_SCALAR.to_string(),
                    connect_url_path_template_scalar.clone(),
                ),
            ]),
            input_types: HashMap::from([
                (
                    BATCH_SETTINGS_INPUT.to_string(),
                    batch_settings_input.clone(),
                ),
                (ERROR_MAPPING_INPUT.to_string(), error_mapping_input.clone()),
                (
                    CONNECT_HTTP_INPUT.to_string(),
                    connect_http_input_v0_2_latest.clone(),
                ),
                (
                    SOURCE_HTTP_INPUT.to_string(),
                    source_http_input_v0_2_latest.clone(),
                ),
                (
                    HTTP_HEADER_MAPPING_INPUT.to_string(),
                    http_header_mapping_input.clone(),
                ),
            ]),
            directives: HashMap::from([
                (
                    CONNECT_DIRECTIVE.to_string(),
                    connect_directive_v0_3_latest.clone(),
                ),
                (
                    SOURCE_DIRECTIVE.to_string(),
                    source_directive_v0_3_latest.clone(),
                ),
            ]),
            enums: HashMap::new(),
        };

        let connect0_4 = Spec {
            status: SpecStatus::Experimental,
            scalars: HashMap::from([
                (
                    JSON_SELECTION_SCALAR.to_string(),
                    connect_json_selection_scalar,
                ),
                (
                    URL_PATH_TEMPLATE_SCALAR.to_string(),
                    connect_url_path_template_scalar,
                ),
            ]),
            input_types: HashMap::from([
                (BATCH_SETTINGS_INPUT.to_string(), batch_settings_input),
                (ERROR_MAPPING_INPUT.to_string(), error_mapping_input),
                (
                    CONNECT_HTTP_INPUT.to_string(),
                    connect_http_input_v0_2_latest,
                ),
                (SOURCE_HTTP_INPUT.to_string(), source_http_input_v0_2_latest),
                (
                    HTTP_HEADER_MAPPING_INPUT.to_string(),
                    http_header_mapping_input,
                ),
            ]),
            directives: HashMap::from([
                (CONNECT_DIRECTIVE.to_string(), connect_directive_v0_3_latest),
                (SOURCE_DIRECTIVE.to_string(), source_directive_v0_3_latest),
            ]),
            enums: HashMap::new(),
        };

        connect_versions.insert("0.1".to_string(), connect0_1);
        connect_versions.insert("0.2".to_string(), connect0_2);
        connect_versions.insert("0.3".to_string(), connect0_3);
        connect_versions.insert("0.4".to_string(), connect0_4);
        connect_versions
    })
};