UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

106 lines (105 loc) 4.79 kB
import { get, mapKeys, merge, set, uniq } from 'lodash-es'; import { sanitizeQuery } from '../../../utils/sanitize-query.js'; import { validateQuery } from '../../../utils/validate-query.js'; import { replaceFuncs } from '../utils/replace-funcs.js'; import { parseArgs } from './parse-args.js'; import { filterReplaceM2A, filterReplaceM2ADeep } from '../utils/filter-replace-m2a.js'; /** * Get a Directus Query object from the parsed arguments (rawQuery) and GraphQL AST selectionSet. Converts SelectionSet into * Directus' `fields` query for use in the resolver. Also applies variables where appropriate. */ export async function getQuery(rawQuery, schema, selections, variableValues, accountability, collection) { const query = await sanitizeQuery(rawQuery, schema, accountability); const parseAliases = (selections) => { const aliases = {}; for (const selection of selections) { if (selection.kind !== 'Field') continue; if (selection.alias?.value) { aliases[selection.alias.value] = selection.name.value; } } return aliases; }; const parseFields = async (selections, parent) => { const fields = []; for (let selection of selections) { if ((selection.kind === 'Field' || selection.kind === 'InlineFragment') !== true) continue; selection = selection; let current; let currentAlias = null; // Union type (Many-to-Any) if (selection.kind === 'InlineFragment') { if (selection.typeCondition.name.value.startsWith('__')) continue; current = `${parent}:${selection.typeCondition.name.value}`; } // Any other field type else { // filter out graphql pointers, like __typename if (selection.name.value.startsWith('__')) continue; current = selection.name.value; if (selection.alias) { currentAlias = selection.alias.value; } if (parent) { current = `${parent}.${current}`; if (currentAlias) { currentAlias = `${parent}.${currentAlias}`; // add nested aliases into deep query if (selection.selectionSet) { if (!query.deep) query.deep = {}; set(query.deep, parent, merge({}, get(query.deep, parent), { _alias: { [selection.alias.value]: selection.name.value } })); } } } } if (selection.selectionSet) { let children; if (current.endsWith('_func')) { children = []; const rootField = current.slice(0, -5); for (const subSelection of selection.selectionSet.selections) { if (subSelection.kind !== 'Field') continue; if (subSelection.name.value.startsWith('__')) continue; children.push(`${subSelection.name.value}(${rootField})`); } } else { children = await parseFields(selection.selectionSet.selections, currentAlias ?? current); } fields.push(...children); } else { fields.push(current); } if (selection.kind === 'Field' && selection.arguments && selection.arguments.length > 0) { if (selection.arguments && selection.arguments.length > 0) { if (!query.deep) query.deep = {}; const args = parseArgs(selection.arguments, variableValues); set(query.deep, currentAlias ?? current, merge({}, get(query.deep, currentAlias ?? current), mapKeys(await sanitizeQuery(args, schema, accountability), (_value, key) => `_${key}`))); } } } return uniq(fields); }; query.alias = parseAliases(selections); query.fields = await parseFields(selections); if (query.filter) query.filter = replaceFuncs(query.filter); query.deep = replaceFuncs(query.deep); if (collection) { if (query.filter) { query.filter = filterReplaceM2A(query.filter, collection, schema); } query.deep = filterReplaceM2ADeep(query.deep, collection, schema); } validateQuery(query); return query; }