Spaces:
Running
Running
import { Kind } from '../language/kinds.mjs'; | |
import { visit } from '../language/visitor.mjs'; | |
import { TypeInfo, visitWithTypeInfo } from '../utilities/TypeInfo.mjs'; | |
/** | |
* An instance of this class is passed as the "this" context to all validators, | |
* allowing access to commonly useful contextual information from within a | |
* validation rule. | |
*/ | |
export class ASTValidationContext { | |
constructor(ast, onError) { | |
this._ast = ast; | |
this._fragments = undefined; | |
this._fragmentSpreads = new Map(); | |
this._recursivelyReferencedFragments = new Map(); | |
this._onError = onError; | |
} | |
get [Symbol.toStringTag]() { | |
return 'ASTValidationContext'; | |
} | |
reportError(error) { | |
this._onError(error); | |
} | |
getDocument() { | |
return this._ast; | |
} | |
getFragment(name) { | |
let fragments; | |
if (this._fragments) { | |
fragments = this._fragments; | |
} else { | |
fragments = Object.create(null); | |
for (const defNode of this.getDocument().definitions) { | |
if (defNode.kind === Kind.FRAGMENT_DEFINITION) { | |
fragments[defNode.name.value] = defNode; | |
} | |
} | |
this._fragments = fragments; | |
} | |
return fragments[name]; | |
} | |
getFragmentSpreads(node) { | |
let spreads = this._fragmentSpreads.get(node); | |
if (!spreads) { | |
spreads = []; | |
const setsToVisit = [node]; | |
let set; | |
while ((set = setsToVisit.pop())) { | |
for (const selection of set.selections) { | |
if (selection.kind === Kind.FRAGMENT_SPREAD) { | |
spreads.push(selection); | |
} else if (selection.selectionSet) { | |
setsToVisit.push(selection.selectionSet); | |
} | |
} | |
} | |
this._fragmentSpreads.set(node, spreads); | |
} | |
return spreads; | |
} | |
getRecursivelyReferencedFragments(operation) { | |
let fragments = this._recursivelyReferencedFragments.get(operation); | |
if (!fragments) { | |
fragments = []; | |
const collectedNames = Object.create(null); | |
const nodesToVisit = [operation.selectionSet]; | |
let node; | |
while ((node = nodesToVisit.pop())) { | |
for (const spread of this.getFragmentSpreads(node)) { | |
const fragName = spread.name.value; | |
if (collectedNames[fragName] !== true) { | |
collectedNames[fragName] = true; | |
const fragment = this.getFragment(fragName); | |
if (fragment) { | |
fragments.push(fragment); | |
nodesToVisit.push(fragment.selectionSet); | |
} | |
} | |
} | |
} | |
this._recursivelyReferencedFragments.set(operation, fragments); | |
} | |
return fragments; | |
} | |
} | |
export class SDLValidationContext extends ASTValidationContext { | |
constructor(ast, schema, onError) { | |
super(ast, onError); | |
this._schema = schema; | |
} | |
get [Symbol.toStringTag]() { | |
return 'SDLValidationContext'; | |
} | |
getSchema() { | |
return this._schema; | |
} | |
} | |
export class ValidationContext extends ASTValidationContext { | |
constructor(schema, ast, typeInfo, onError) { | |
super(ast, onError); | |
this._schema = schema; | |
this._typeInfo = typeInfo; | |
this._variableUsages = new Map(); | |
this._recursiveVariableUsages = new Map(); | |
} | |
get [Symbol.toStringTag]() { | |
return 'ValidationContext'; | |
} | |
getSchema() { | |
return this._schema; | |
} | |
getVariableUsages(node) { | |
let usages = this._variableUsages.get(node); | |
if (!usages) { | |
const newUsages = []; | |
const typeInfo = new TypeInfo(this._schema); | |
visit( | |
node, | |
visitWithTypeInfo(typeInfo, { | |
VariableDefinition: () => false, | |
Variable(variable) { | |
newUsages.push({ | |
node: variable, | |
type: typeInfo.getInputType(), | |
defaultValue: typeInfo.getDefaultValue(), | |
}); | |
}, | |
}), | |
); | |
usages = newUsages; | |
this._variableUsages.set(node, usages); | |
} | |
return usages; | |
} | |
getRecursiveVariableUsages(operation) { | |
let usages = this._recursiveVariableUsages.get(operation); | |
if (!usages) { | |
usages = this.getVariableUsages(operation); | |
for (const frag of this.getRecursivelyReferencedFragments(operation)) { | |
usages = usages.concat(this.getVariableUsages(frag)); | |
} | |
this._recursiveVariableUsages.set(operation, usages); | |
} | |
return usages; | |
} | |
getType() { | |
return this._typeInfo.getType(); | |
} | |
getParentType() { | |
return this._typeInfo.getParentType(); | |
} | |
getInputType() { | |
return this._typeInfo.getInputType(); | |
} | |
getParentInputType() { | |
return this._typeInfo.getParentInputType(); | |
} | |
getFieldDef() { | |
return this._typeInfo.getFieldDef(); | |
} | |
getDirective() { | |
return this._typeInfo.getDirective(); | |
} | |
getArgument() { | |
return this._typeInfo.getArgument(); | |
} | |
getEnumValue() { | |
return this._typeInfo.getEnumValue(); | |
} | |
} | |