All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.expediagroup.graphql.generator.federation.directives.LinkDirective.kt Maven / Gradle / Ivy

/*
 * Copyright 2024 Expedia, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.expediagroup.graphql.generator.federation.directives

import com.expediagroup.graphql.generator.annotations.GraphQLDirective
import graphql.Scalars
import graphql.introspection.Introspection.DirectiveLocation
import graphql.schema.GraphQLArgument
import graphql.schema.GraphQLList
import graphql.schema.GraphQLNonNull
import graphql.schema.GraphQLScalarType

const val APOLLO_SPEC_URL = "https://specs.apollo.dev"

const val LINK_SPEC = "link"
const val LINK_SPEC_LATEST_VERSION = "1.0"
const val LINK_SPEC_URL_PREFIX = "$APOLLO_SPEC_URL/$LINK_SPEC"
const val LINK_SPEC_LATEST_URL = "$LINK_SPEC_URL_PREFIX/v$LINK_SPEC_LATEST_VERSION"

const val FEDERATION_SPEC = "federation"
const val FEDERATION_SPEC_LATEST_VERSION = "2.6"
const val FEDERATION_SPEC_URL_PREFIX = "$APOLLO_SPEC_URL/$FEDERATION_SPEC"
const val FEDERATION_SPEC_LATEST_URL = "$FEDERATION_SPEC_URL_PREFIX/v$FEDERATION_SPEC_LATEST_VERSION"

/**
 * ```graphql
 * directive @link(url: String!, as: String, import: [Import]) repeatable on SCHEMA
 * ```
 *
 * The `@link` directive links definitions within the document to external schemas.
 *
 * External schemas are identified by their url, which should end with a specification name and version with the following format: `{NAME}/v{MAJOR}.{MINOR}`, e.g. `url = "https://specs.apollo.dev/federation/v2.5"`.
 *
 * External types are associated with the target specification by annotating it with `@LinkedSpec` meta annotation. External types defined in the specification will be automatically namespaced
 * (prefixed with `{NAME}__`) unless they are explicitly imported. While both custom namespace (`as`) and import arguments are optional, due to https://github.com/ExpediaGroup/graphql-kotlin/issues/1830
 * we currently always require those values to be explicitly provided.
 *
 * @param url external schema URL
 * @param as custom namespace, should default to the specification name specified in the url
 * @param import list of imported schema elements
 *
 * @see @link specification
 */
@Repeatable
@GraphQLDirective(
    name = LINK_DIRECTIVE_NAME,
    description = LINK_DIRECTIVE_DESCRIPTION,
    locations = [DirectiveLocation.SCHEMA]
)
annotation class LinkDirective(val url: String, val `as`: String, val import: Array)

internal const val LINK_DIRECTIVE_NAME = "link"
private const val LINK_DIRECTIVE_DESCRIPTION = "Links definitions within the document to external schemas."

internal fun linkDirectiveDefinition(importScalarType: GraphQLScalarType): graphql.schema.GraphQLDirective = graphql.schema.GraphQLDirective.newDirective()
    .name(LINK_DIRECTIVE_NAME)
    .description(LINK_DIRECTIVE_DESCRIPTION)
    .validLocations(DirectiveLocation.SCHEMA)
    .argument(
        GraphQLArgument.newArgument()
            .name("url")
            .type(GraphQLNonNull.nonNull(Scalars.GraphQLString))
    )
    .argument(
        GraphQLArgument.newArgument()
            .name("as")
            .type(Scalars.GraphQLString)
    )
    .argument(
        GraphQLArgument
            .newArgument()
            .name("import")
            .type(GraphQLList.list(importScalarType))
    )
    .repeatable(true)
    .build()

internal fun graphql.schema.GraphQLDirective.toAppliedLinkDirective(url: String, namespace: String? = null, imports: List = emptyList()) = this.toAppliedDirective()
    .transform { appliedDirectiveBuilder ->
        this.getArgument("url")
            .toAppliedArgument()
            .transform { argumentBuilder ->
                argumentBuilder.valueProgrammatic(url)
            }
            .let {
                appliedDirectiveBuilder.argument(it)
            }

        if (!namespace.isNullOrBlank()) {
            this.getArgument("as")
                .toAppliedArgument()
                .transform { argumentBuilder ->
                    argumentBuilder.valueProgrammatic(namespace)
                }
                .let {
                    appliedDirectiveBuilder.argument(it)
                }
        }

        if (imports.isNotEmpty()) {
            this.getArgument("import")
                .toAppliedArgument()
                .transform { argumentBuilder ->
                    argumentBuilder.valueProgrammatic(imports)
                }
                .let {
                    appliedDirectiveBuilder.argument(it)
                }
        }
    }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy