@apollo/federation-internals
Advanced tools
Comparing version 2.1.2-alpha.1 to 2.1.2-alpha.2
@@ -57,3 +57,2 @@ import { ASTNode, GraphQLError, GraphQLErrorOptions, GraphQLFormattedError } from "graphql"; | ||
PROVIDES_FIELDS_HAS_ARGS: ErrorCodeDefinition; | ||
REQUIRES_FIELDS_HAS_ARGS: ErrorCodeDefinition; | ||
PROVIDES_MISSING_EXTERNAL: ErrorCodeDefinition; | ||
@@ -60,0 +59,0 @@ REQUIRES_MISSING_EXTERNAL: ErrorCodeDefinition; |
@@ -129,3 +129,2 @@ "use strict"; | ||
const PROVIDES_FIELDS_HAS_ARGS = FIELDS_HAS_ARGS.createCode('provides'); | ||
const REQUIRES_FIELDS_HAS_ARGS = FIELDS_HAS_ARGS.createCode('requires'); | ||
const DIRECTIVE_FIELDS_MISSING_EXTERNAL = makeFederationDirectiveErrorCodeCategory('FIELDS_MISSING_EXTERNAL', (directive) => `The \`fields\` argument of a \`@${directive}\` directive includes a field that is not marked as \`@external\`.`, { addedIn: FED1_CODE }); | ||
@@ -220,3 +219,2 @@ const PROVIDES_MISSING_EXTERNAL = DIRECTIVE_FIELDS_MISSING_EXTERNAL.createCode('provides'); | ||
PROVIDES_FIELDS_HAS_ARGS, | ||
REQUIRES_FIELDS_HAS_ARGS, | ||
PROVIDES_MISSING_EXTERNAL, | ||
@@ -306,3 +304,4 @@ REQUIRES_MISSING_EXTERNAL, | ||
['NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH', 'Since federation 2.1.0, the case this error used to cover is now a warning (with code `INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS`) instead of an error'], | ||
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported'], | ||
]; | ||
//# sourceMappingURL=error.js.map |
@@ -1,2 +0,2 @@ | ||
import { CompositeType, CoreFeature, Directive, DirectiveDefinition, FieldDefinition, InputFieldDefinition, InterfaceType, NamedType, ObjectType, ScalarType, Schema, SchemaBlueprint, SchemaConfig, UnionType } from "./definitions"; | ||
import { CompositeType, CoreFeature, Directive, DirectiveDefinition, FieldDefinition, InputFieldDefinition, InterfaceType, NamedType, ObjectType, ScalarType, Schema, SchemaBlueprint, SchemaConfig, SchemaElement, UnionType } from "./definitions"; | ||
import { SDLValidationRule } from "graphql/validation/ValidationContext"; | ||
@@ -91,5 +91,5 @@ import { ASTNode, DocumentNode, GraphQLError } from "graphql"; | ||
export declare function newEmptyFederation2Schema(config?: SchemaConfig): Schema; | ||
export declare function parseFieldSetArgument({ parentType, directive, fieldAccessor, validate, }: { | ||
export declare function parseFieldSetArgument({ parentType, directive, fieldAccessor, validate, decorateValidationErrors, }: { | ||
parentType: CompositeType; | ||
directive: Directive<NamedType | FieldDefinition<CompositeType>, { | ||
directive: Directive<SchemaElement<any, any>, { | ||
fields: any; | ||
@@ -99,2 +99,3 @@ }>; | ||
validate?: boolean; | ||
decorateValidationErrors?: boolean; | ||
}): SelectionSet; | ||
@@ -101,0 +102,0 @@ export declare function collectTargetFields({ parentType, directive, includeInterfaceFieldsImplementations, validate, }: { |
@@ -34,3 +34,3 @@ "use strict"; | ||
const FEDERATION_VALIDATION_RULES = specifiedRules_1.specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES); | ||
function validateFieldSetSelections(directiveName, selectionSet, hasExternalInParents, federationMetadata, onError, allowOnNonExternalLeafFields) { | ||
function validateFieldSetSelections({ directiveName, selectionSet, hasExternalInParents, metadata, onError, allowOnNonExternalLeafFields, allowFieldsWithArguments, }) { | ||
for (const selection of selectionSet.selections()) { | ||
@@ -43,4 +43,4 @@ const appliedDirectives = selection.element().appliedDirectives; | ||
const field = selection.element().definition; | ||
const isExternal = federationMetadata.isFieldExternal(field); | ||
if (field.hasArguments()) { | ||
const isExternal = metadata.isFieldExternal(field); | ||
if (!allowFieldsWithArguments && field.hasArguments()) { | ||
onError(error_1.ERROR_CATEGORIES.FIELDS_HAS_ARGS.get(directiveName).err(`field ${field.coordinate} cannot be included because it has arguments (fields with argument are not allowed in @${directiveName})`, { nodes: field.sourceAST })); | ||
@@ -51,3 +51,3 @@ } | ||
const errorCode = error_1.ERROR_CATEGORIES.DIRECTIVE_FIELDS_MISSING_EXTERNAL.get(directiveName); | ||
if (federationMetadata.isFieldFakeExternal(field)) { | ||
if (metadata.isFieldFakeExternal(field)) { | ||
onError(errorCode.err(`field "${field.coordinate}" should not be part of a @${directiveName} since it is already "effectively" provided by this subgraph ` | ||
@@ -66,3 +66,3 @@ + `(while it is marked @${federationSpec_1.externalDirectiveSpec.name}, it is a @${federationSpec_1.keyDirectiveSpec.name} field of an extension type, which are not internally considered external for historical/backward compatibility reasons)`, { nodes: field.sourceAST })); | ||
const fieldInImplem = implem.field(field.name); | ||
if (fieldInImplem && federationMetadata.isFieldExternal(fieldInImplem)) { | ||
if (fieldInImplem && metadata.isFieldExternal(fieldInImplem)) { | ||
newHasExternalInParents = true; | ||
@@ -73,11 +73,27 @@ break; | ||
} | ||
validateFieldSetSelections(directiveName, selection.selectionSet, newHasExternalInParents, federationMetadata, onError, allowOnNonExternalLeafFields); | ||
validateFieldSetSelections({ | ||
directiveName, | ||
selectionSet: selection.selectionSet, | ||
hasExternalInParents: newHasExternalInParents, | ||
metadata, | ||
onError, | ||
allowOnNonExternalLeafFields, | ||
allowFieldsWithArguments, | ||
}); | ||
} | ||
} | ||
else { | ||
validateFieldSetSelections(directiveName, selection.selectionSet, hasExternalInParents, federationMetadata, onError, allowOnNonExternalLeafFields); | ||
validateFieldSetSelections({ | ||
directiveName, | ||
selectionSet: selection.selectionSet, | ||
hasExternalInParents, | ||
metadata, | ||
onError, | ||
allowOnNonExternalLeafFields, | ||
allowFieldsWithArguments, | ||
}); | ||
} | ||
} | ||
} | ||
function validateFieldSet(type, directive, federationMetadata, errorCollector, allowOnNonExternalLeafFields, onFields) { | ||
function validateFieldSet({ type, directive, metadata, errorCollector, allowOnNonExternalLeafFields, allowFieldsWithArguments, onFields, }) { | ||
try { | ||
@@ -94,3 +110,11 @@ const fieldAccessor = onFields | ||
const selectionSet = parseFieldSetArgument({ parentType: type, directive, fieldAccessor }); | ||
validateFieldSetSelections(directive.name, selectionSet, false, federationMetadata, (error) => errorCollector.push(handleFieldSetValidationError(directive, error)), allowOnNonExternalLeafFields); | ||
validateFieldSetSelections({ | ||
directiveName: directive.name, | ||
selectionSet, | ||
hasExternalInParents: false, | ||
metadata, | ||
onError: (error) => errorCollector.push(handleFieldSetValidationError(directive, error)), | ||
allowOnNonExternalLeafFields, | ||
allowFieldsWithArguments, | ||
}); | ||
} | ||
@@ -135,3 +159,3 @@ catch (e) { | ||
} | ||
function validateAllFieldSet(definition, targetTypeExtractor, errorCollector, federationMetadata, isOnParentType, allowOnNonExternalLeafFields, onFields) { | ||
function validateAllFieldSet({ definition, targetTypeExtractor, errorCollector, metadata, isOnParentType = false, allowOnNonExternalLeafFields = false, allowFieldsWithArguments = false, onFields, }) { | ||
for (const application of definition.applications()) { | ||
@@ -147,3 +171,11 @@ const elt = application.parent; | ||
} | ||
validateFieldSet(type, application, federationMetadata, errorCollector, allowOnNonExternalLeafFields, onFields); | ||
validateFieldSet({ | ||
type, | ||
directive: application, | ||
metadata, | ||
errorCollector, | ||
allowOnNonExternalLeafFields, | ||
allowFieldsWithArguments, | ||
onFields, | ||
}); | ||
} | ||
@@ -453,3 +485,3 @@ } | ||
var _a; | ||
const errors = super.onValidation(schema); | ||
const errorCollector = super.onValidation(schema); | ||
if (this.withRootTypeRenaming) { | ||
@@ -462,3 +494,3 @@ for (const k of definitions_1.allSchemaRootKinds) { | ||
if (existing) { | ||
errors.push(error_1.ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err(`The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): ` | ||
errorCollector.push(error_1.ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err(`The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): ` | ||
+ 'this is not supported by federation. ' | ||
@@ -474,26 +506,45 @@ + 'If a root type does not use its default name, there should be no other type with that default name.', { nodes: (0, definitions_1.sourceASTs)(type, existing) })); | ||
if (!metadata.isFed2Schema()) { | ||
return errors; | ||
return errorCollector; | ||
} | ||
const keyDirective = metadata.keyDirective(); | ||
validateAllFieldSet(keyDirective, type => type, errors, metadata, true, true, field => { | ||
const type = (0, definitions_1.baseType)(field.type); | ||
if ((0, definitions_1.isUnionType)(type) || (0, definitions_1.isInterfaceType)(type)) { | ||
let kind = type.kind; | ||
kind = kind.slice(0, kind.length - 'Type'.length); | ||
throw error_1.ERRORS.KEY_FIELDS_SELECT_INVALID_TYPE.err(`field "${field.coordinate}" is a ${kind} type which is not allowed in @key`); | ||
validateAllFieldSet({ | ||
definition: keyDirective, | ||
targetTypeExtractor: type => type, | ||
errorCollector, | ||
metadata, | ||
isOnParentType: true, | ||
allowOnNonExternalLeafFields: true, | ||
onFields: field => { | ||
const type = (0, definitions_1.baseType)(field.type); | ||
if ((0, definitions_1.isUnionType)(type) || (0, definitions_1.isInterfaceType)(type)) { | ||
let kind = type.kind; | ||
kind = kind.slice(0, kind.length - 'Type'.length); | ||
throw error_1.ERRORS.KEY_FIELDS_SELECT_INVALID_TYPE.err(`field "${field.coordinate}" is a ${kind} type which is not allowed in @key`); | ||
} | ||
} | ||
}); | ||
validateAllFieldSet(metadata.requiresDirective(), field => field.parent, errors, metadata, false, false); | ||
validateAllFieldSet(metadata.providesDirective(), field => { | ||
if (metadata.isFieldExternal(field)) { | ||
throw error_1.ERRORS.EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE.err(`Cannot have both @provides and @external on field "${field.coordinate}"`, { nodes: field.sourceAST }); | ||
} | ||
const type = (0, definitions_1.baseType)(field.type); | ||
if (!(0, definitions_1.isCompositeType)(type)) { | ||
throw error_1.ERRORS.PROVIDES_ON_NON_OBJECT_FIELD.err(`Invalid @provides directive on field "${field.coordinate}": field has type "${field.type}" which is not a Composite Type`, { nodes: field.sourceAST }); | ||
} | ||
return type; | ||
}, errors, metadata, false, false); | ||
validateNoExternalOnInterfaceFields(metadata, errors); | ||
validateAllExternalFieldsUsed(metadata, errors); | ||
validateAllFieldSet({ | ||
definition: metadata.requiresDirective(), | ||
targetTypeExtractor: field => field.parent, | ||
errorCollector, | ||
metadata, | ||
allowFieldsWithArguments: true, | ||
}); | ||
validateAllFieldSet({ | ||
definition: metadata.providesDirective(), | ||
targetTypeExtractor: field => { | ||
if (metadata.isFieldExternal(field)) { | ||
throw error_1.ERRORS.EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE.err(`Cannot have both @provides and @external on field "${field.coordinate}"`, { nodes: field.sourceAST }); | ||
} | ||
const type = (0, definitions_1.baseType)(field.type); | ||
if (!(0, definitions_1.isCompositeType)(type)) { | ||
throw error_1.ERRORS.PROVIDES_ON_NON_OBJECT_FIELD.err(`Invalid @provides directive on field "${field.coordinate}": field has type "${field.type}" which is not a Composite Type`, { nodes: field.sourceAST }); | ||
} | ||
return type; | ||
}, | ||
errorCollector, | ||
metadata, | ||
}); | ||
validateNoExternalOnInterfaceFields(metadata, errorCollector); | ||
validateAllExternalFieldsUsed(metadata, errorCollector); | ||
const tagDirective = metadata.tagDirective(); | ||
@@ -503,9 +554,9 @@ if (tagDirective) { | ||
if (error) { | ||
errors.push(error); | ||
errorCollector.push(error); | ||
} | ||
} | ||
for (const itf of schema.interfaceTypes()) { | ||
validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errors); | ||
validateInterfaceRuntimeImplementationFieldsTypes(itf, metadata, errorCollector); | ||
} | ||
return errors; | ||
return errorCollector; | ||
} | ||
@@ -740,5 +791,5 @@ validationRules() { | ||
} | ||
function parseFieldSetArgument({ parentType, directive, fieldAccessor, validate, }) { | ||
function parseFieldSetArgument({ parentType, directive, fieldAccessor, validate, decorateValidationErrors = true, }) { | ||
try { | ||
return (0, operations_1.parseSelectionSet)({ | ||
const selectionSet = (0, operations_1.parseSelectionSet)({ | ||
parentType, | ||
@@ -749,5 +800,13 @@ source: validateFieldSetValue(directive), | ||
}); | ||
if (validate !== null && validate !== void 0 ? validate : true) { | ||
selectionSet.forEachElement((elt) => { | ||
if (elt.kind === 'Field' && elt.alias) { | ||
throw new graphql_1.GraphQLError(`Cannot use alias "${elt.alias}" in "${elt}": aliases are not currently supported in @${directive.name}`); | ||
} | ||
}); | ||
} | ||
return selectionSet; | ||
} | ||
catch (e) { | ||
if (!(e instanceof graphql_1.GraphQLError)) { | ||
if (!(e instanceof graphql_1.GraphQLError) || !decorateValidationErrors) { | ||
throw e; | ||
@@ -1014,2 +1073,5 @@ } | ||
function addSubgraphToASTNode(node, subgraph) { | ||
if ('subgraph' in node) { | ||
return node; | ||
} | ||
return { | ||
@@ -1016,0 +1078,0 @@ ...node, |
@@ -167,2 +167,3 @@ import { DocumentNode, FieldNode, FragmentDefinitionNode, SelectionNode, SelectionSetNode } from "graphql"; | ||
private toOperationPathsInternal; | ||
forEachElement(callback: (elt: OperationElement) => void): void; | ||
clone(): SelectionSet; | ||
@@ -169,0 +170,0 @@ toOperationString(rootKind: SchemaRootKind, variableDefinitions: VariableDefinitions, operationName?: string, expandFragments?: boolean, prettyPrint?: boolean): string; |
@@ -545,3 +545,3 @@ "use strict"; | ||
if (!expectedType) { | ||
throw error_1.ERRORS.INVALID_GRAPHQL.err(`Unknown argument "${name}" found in value: ${context} has no argument named "${name}"`); | ||
throw error_1.ERRORS.INVALID_GRAPHQL.err(`Unknown argument "${name}" found in value: "${context}" has no argument named "${name}"`); | ||
} | ||
@@ -553,3 +553,3 @@ try { | ||
if (e instanceof graphql_1.GraphQLError) { | ||
throw error_1.ERRORS.INVALID_GRAPHQL.err(`Invalid value for argument ${name}: ${e.message}`); | ||
throw error_1.ERRORS.INVALID_GRAPHQL.err(`Invalid value for argument "${name}": ${e.message}`); | ||
} | ||
@@ -556,0 +556,0 @@ throw e; |
{ | ||
"name": "@apollo/federation-internals", | ||
"version": "2.1.2-alpha.1", | ||
"version": "2.1.2-alpha.2", | ||
"description": "Apollo Federation internal utilities", | ||
@@ -35,3 +35,3 @@ "main": "dist/index.js", | ||
}, | ||
"gitHead": "d4da310a8aa3ef93350a3de0c5a32692bb2d8e0b" | ||
"gitHead": "c7bb87702c5913f5a0ecc5c2a28d2f7228e2988c" | ||
} |
@@ -63,18 +63,2 @@ import { DocumentNode } from 'graphql'; | ||
it('rejects field defined with arguments in @requires', () => { | ||
const subgraph = gql` | ||
type Query { | ||
t: T | ||
} | ||
type T { | ||
f(x: Int): Int @external | ||
g: Int @requires(fields: "f") | ||
} | ||
` | ||
expect(buildForErrors(subgraph)).toStrictEqual([ | ||
['REQUIRES_FIELDS_HAS_ARGS', '[S] On field "T.g", for @requires(fields: "f"): field T.f cannot be included because it has arguments (fields with argument are not allowed in @requires)'] | ||
]); | ||
}); | ||
it('rejects @provides on non-external fields', () => { | ||
@@ -463,2 +447,57 @@ const subgraph = gql` | ||
}); | ||
it('rejects aliases in @key', () => { | ||
const subgraph = gql` | ||
type Query { | ||
t: T | ||
} | ||
type T @key(fields: "foo: id") { | ||
id: ID! | ||
} | ||
` | ||
expect(buildForErrors(subgraph)).toStrictEqual([ | ||
[ 'KEY_INVALID_FIELDS', '[S] On type "T", for @key(fields: "foo: id"): Cannot use alias "foo" in "foo: id": aliases are not currently supported in @key' ], | ||
]); | ||
}); | ||
it('rejects aliases in @provides', () => { | ||
const subgraph = gql` | ||
type Query { | ||
t: T @provides(fields: "bar: x") | ||
} | ||
type T @key(fields: "id") { | ||
id: ID! | ||
x: Int @external | ||
} | ||
` | ||
expect(buildForErrors(subgraph)).toStrictEqual([ | ||
[ 'PROVIDES_INVALID_FIELDS', '[S] On field "Query.t", for @provides(fields: "bar: x"): Cannot use alias "bar" in "bar: x": aliases are not currently supported in @provides' ], | ||
]); | ||
}); | ||
it('rejects aliases in @requires', () => { | ||
const subgraph = gql` | ||
type Query { | ||
t: T | ||
} | ||
type T { | ||
x: X @external | ||
y: Int @external | ||
g: Int @requires(fields: "foo: y") | ||
h: Int @requires(fields: "x { m: a n: b }") | ||
} | ||
type X { | ||
a: Int | ||
b: Int | ||
} | ||
` | ||
expect(buildForErrors(subgraph)).toStrictEqual([ | ||
[ 'REQUIRES_INVALID_FIELDS', '[S] On field "T.g", for @requires(fields: "foo: y"): Cannot use alias "foo" in "foo: y": aliases are not currently supported in @requires' ], | ||
[ 'REQUIRES_INVALID_FIELDS', '[S] On field "T.h", for @requires(fields: "x { m: a n: b }"): Cannot use alias "m" in "m: a": aliases are not currently supported in @requires' ], | ||
]); | ||
}); | ||
}); | ||
@@ -465,0 +504,0 @@ |
@@ -226,3 +226,2 @@ import { ASTNode, GraphQLError, GraphQLErrorOptions, GraphQLFormattedError } from "graphql"; | ||
const PROVIDES_FIELDS_HAS_ARGS = FIELDS_HAS_ARGS.createCode('provides'); | ||
const REQUIRES_FIELDS_HAS_ARGS = FIELDS_HAS_ARGS.createCode('requires'); | ||
@@ -549,3 +548,2 @@ const DIRECTIVE_FIELDS_MISSING_EXTERNAL = makeFederationDirectiveErrorCodeCategory( | ||
PROVIDES_FIELDS_HAS_ARGS, | ||
REQUIRES_FIELDS_HAS_ARGS, | ||
PROVIDES_MISSING_EXTERNAL, | ||
@@ -649,2 +647,3 @@ REQUIRES_MISSING_EXTERNAL, | ||
['NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH', 'Since federation 2.1.0, the case this error used to cover is now a warning (with code `INCONSISTENT_NON_REPEATABLE_DIRECTIVE_ARGUMENTS`) instead of an error'], | ||
['REQUIRES_FIELDS_HAS_ARGS', 'Since federation 2.1.1, using fields with arguments in a @requires is fully supported'], | ||
]; |
@@ -704,3 +704,3 @@ import { | ||
throw ERRORS.INVALID_GRAPHQL.err( | ||
`Unknown argument "${name}" found in value: ${context} has no argument named "${name}"` | ||
`Unknown argument "${name}" found in value: "${context}" has no argument named "${name}"` | ||
); | ||
@@ -712,3 +712,3 @@ } | ||
if (e instanceof GraphQLError) { | ||
throw ERRORS.INVALID_GRAPHQL.err(`Invalid value for argument ${name}: ${e.message}`); | ||
throw ERRORS.INVALID_GRAPHQL.err(`Invalid value for argument "${name}": ${e.message}`); | ||
} | ||
@@ -715,0 +715,0 @@ throw e; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1953945
32915