/* eslint-disable @typescript-eslint/no-use-before-define */ import { BuildCache, GiraphQLInputFieldConfig, GiraphQLInputFieldType, GiraphQLTypeConfig, SchemaTypes, } from '..'; export interface InputTypeFieldsMapping { configs: Record>; map: InputFieldsMapping | null; } export type InputFieldMapping = | { kind: 'Enum'; isList: boolean; config: GiraphQLInputFieldConfig; value: T; } | { kind: 'InputObject'; config: GiraphQLInputFieldConfig; isList: boolean; value: T | null; fields: InputTypeFieldsMapping; } | { kind: 'Scalar'; isList: boolean; config: GiraphQLInputFieldConfig; value: T; }; export type InputFieldsMapping = Map< string, InputFieldMapping >; export function resolveInputTypeConfig( type: GiraphQLInputFieldType, buildCache: BuildCache, ): Extract { if (type.kind === 'List') { return resolveInputTypeConfig(type.type, buildCache); } const config = buildCache.getTypeConfig(type.ref); if (config.kind === 'Enum' || config.kind === 'Scalar' || config.kind === 'InputObject') { return config; } throw new TypeError(`Unexpected config type ${config.kind} for input ref ${String(type.ref)}`); } export function mapInputFields( inputs: Record>, buildCache: BuildCache, mapper: (config: GiraphQLInputFieldConfig) => T | null, ): InputFieldsMapping | null { const filterMappings = new Map, InputFieldsMapping>(); return filterMapped( internalMapInputFields( inputs, buildCache, mapper, new Map>(), ), ); function filterMapped(map: InputFieldsMapping) { if (filterMappings.has(map)) { return filterMappings.get(map)!; } const filtered = new Map>(); filterMappings.set(map, filtered); map.forEach((mapping, fieldName) => { if (mapping.kind === 'Enum' || mapping.kind === 'Scalar') { filtered.set(fieldName, mapping); return; } const hasNestedMappings = checkForMappings(mapping.fields.map!); if (mapping.value !== null || hasNestedMappings) { const filteredTypeFields = filterMapped(mapping.fields.map!); const mappingForType = { ...mapping, typeFields: { configs: mapping.fields.configs, map: filteredTypeFields, }, }; filtered.set(fieldName, mappingForType); } }); return filtered.size > 0 ? filtered : null; } function checkForMappings( map: InputFieldsMapping, hasMappings = new Map, boolean>(), ): boolean { if (hasMappings.has(map)) { return hasMappings.get(map)!; } hasMappings.set(map, false); let result = false; map.forEach((mapping) => { if (mapping.value !== null || mapping.kind !== 'InputObject') { result = true; } else if (mapping.fields.map && checkForMappings(mapping.fields.map, hasMappings)) { result = true; } }); hasMappings.set(map, result); return result; } } function internalMapInputFields( inputs: Record>, buildCache: BuildCache, mapper: (config: GiraphQLInputFieldConfig) => T | null, seenTypes: Map>, ) { const map = new Map>(); Object.keys(inputs).forEach((fieldName) => { const inputField = inputs[fieldName]; const typeConfig = resolveInputTypeConfig(inputField.type, buildCache); const fieldMapping = mapper(inputField); if (typeConfig.kind === 'Enum' || typeConfig.kind === 'Scalar') { if (fieldMapping !== null) { map.set(fieldName, { kind: typeConfig.kind, isList: inputField.type.kind === 'List', config: inputField, value: fieldMapping, }); } return; } const inputFieldConfigs = buildCache.getInputTypeFieldConfigs( inputField.type.kind === 'List' ? inputField.type.type.ref : inputField.type.ref, ); if (!seenTypes.has(typeConfig.name)) { const typeEntry = { configs: inputFieldConfigs, map: new Map>(), }; seenTypes.set(typeConfig.name, typeEntry); typeEntry.map = internalMapInputFields(inputFieldConfigs, buildCache, mapper, seenTypes); } const typeFields = seenTypes.get(typeConfig.name)!; map.set(fieldName, { kind: typeConfig.kind, isList: inputField.type.kind === 'List', config: inputField, value: fieldMapping, fields: typeFields, }); }); return map; } export function createInputValueMapper( argMap: InputFieldsMapping, mapValue: (val: unknown, mapping: InputFieldMapping) => unknown, ) { return function mapObject(obj: object, map: InputFieldsMapping = argMap) { const mapped: Record = { ...obj }; map.forEach((field, fieldName) => { let fieldVal = (obj as Record)[fieldName]; if (fieldVal === null || fieldVal === undefined) { return; } if (field.kind === 'InputObject' && field.fields.map) { fieldVal = mapObject(fieldVal as Record, field.fields.map); mapped[fieldName] = fieldVal; } if (field.kind !== 'InputObject' || field.value !== null) { mapped[fieldName] = field.isList ? (fieldVal as unknown[]).map((val) => mapValue(val, field)) : mapValue(fieldVal, field); } }); return mapped; }; }