import path from 'path'; import { MissingRequiredParamsError } from '../errors'; import { CBFileSystem } from '../node/file-system'; import { IparamInputs, IparamDefns, SectionObject } from '../types/common'; import { IparamInputsValidator } from '../validator/iparams-inputs-validator'; import { IparamDefnsValidator } from '../validator/iparam-defns-validator'; import { ensureFileExists } from '../validator/validator-utils'; /** * Service for managing parameters * Handles loading, validation, and management of iparams and inputs */ export class IparamsService { private fileSystem: CBFileSystem; private iparamDefnsValidator: IparamDefnsValidator; private inputsValidator: IparamInputsValidator; constructor(fileSystem: CBFileSystem) { this.fileSystem = fileSystem; this.iparamDefnsValidator = new IparamDefnsValidator(fileSystem); this.inputsValidator = new IparamInputsValidator(fileSystem); } private getIparamsFilePath(appDir: string): string { return path.join(appDir, 'iparams.json'); } private getInputsFilePath(appDir: string): string { return path.join(appDir, 'iparams.local.json'); } /** * Checks if iparams.json file exists * @param {string} appDir - The application directory * @returns {boolean} True if the file exists */ iparamsFileExists(appDir: string): boolean { return this.fileSystem.existsSync(this.getIparamsFilePath(appDir)); } /** * Loads and validates the iparamDefns * @param {string} appDir - The application directory * @returns {IparamDefns} The validated iparamDefns * @throws {Error} If the iparamDefns is invalid or cannot be read */ loadIparamDefns(appDir: string): IparamDefns { return this.iparamDefnsValidator.validateAndGetIparamDefnsFile(this.getIparamsFilePath(appDir)); } /** * Loads iparam inputs (without validation against defns). Validates file shape only. * @param {string} appDir - The application directory * @returns {IparamInputs} The loaded inputs * @throws {Error} If `iparams.local.json` is missing, invalid JSON, or not a plain object */ loadInputs(appDir: string): IparamInputs { return this.inputsValidator.validateAndGetIparamInputsFile(this.getInputsFilePath(appDir)); } /** * Merges inputs with defaults per section. * @param {IparamInputs} inputs - The provided input values * @param {IparamDefns} iparamDefns - The iparam defns * @returns {IparamInputs} The merged inputs with defaults applied * @throws {MissingRequiredParamsError} When required parameters are missing */ mergeInputsWithDefaults(inputs: IparamInputs, iparamDefns: IparamDefns): IparamInputs { const mergedInputs: IparamInputs = {}; const missingRequired: string[] = []; for (const section of iparamDefns.installation_parameters?.sections ?? []) { const sectionInputs = inputs[section.name] ?? {}; const mergedSection: SectionObject = {}; for (const param of section.parameters) { const value = sectionInputs[param.name]; const hasValue = IparamInputsValidator.isProvidedIparamInputValue(value); if (hasValue) { mergedSection[param.name] = value; continue; } if (param.default !== undefined && param.default !== null) { mergedSection[param.name] = param.default; continue; } if (param.required) { missingRequired.push(`${section.name}.${param.name}`); } } if (Object.keys(mergedSection).length > 0) { mergedInputs[section.name] = mergedSection; } } if (missingRequired.length > 0) { throw new MissingRequiredParamsError(missingRequired); } return mergedInputs; } /** * Loads inputs and defns, validates inputs against defns, merges with defaults, returns resolved iparams. * @param {string} appDir - The application directory * @returns {IparamInputs} The merged inputs with defaults applied * @throws {Error} If validation fails, or required parameters are missing and have no defaults */ validateAndGetInputsForExecution(appDir: string): IparamInputs { const inputs = this.loadInputs(appDir); const iparamDefns = this.loadIparamDefns(appDir); this.inputsValidator.validateInputs(inputs, iparamDefns); return this.mergeInputsWithDefaults(inputs, iparamDefns); } /** * Saves the iparamDefns to file * @param {string} appDir - The application directory * @param {IparamDefns} iparamDefns - The iparamDefns to save * @throws {Error} If the iparamDefns is invalid or cannot be written */ saveIparamDefns(appDir: string, iparamDefns: IparamDefns): void { ensureFileExists(this.fileSystem, this.getIparamsFilePath(appDir)); this.iparamDefnsValidator.validate(iparamDefns); this.fileSystem.writeFileSync( this.getIparamsFilePath(appDir), JSON.stringify(iparamDefns, null, '\t') ); } /** * Loads defns and merges inputs, ensures iparams.local.json exists, then writes. * @param {string} appDir - The application directory * @param {IparamInputs} inputs - The raw input values from the request * @throws {Error} If iparams.json or iparams.local.json is missing, or validation fails */ saveInputs(appDir: string, inputs: IparamInputs): void { const iparamDefns = this.loadIparamDefns(appDir); this.inputsValidator.validateInputs(inputs, iparamDefns); const mergedInputs = this.mergeInputsWithDefaults(inputs, iparamDefns); ensureFileExists(this.fileSystem, this.getInputsFilePath(appDir)); this.fileSystem.writeFileSync( this.getInputsFilePath(appDir), JSON.stringify(mergedInputs, null, '\t') ); } /** * Validates iparams file without loading it * @param {string} appDir - The application directory * @throws {Error} If the iparamDefns is invalid */ validateIparamDefns(appDir: string): void { this.iparamDefnsValidator.validateAndGetIparamDefnsFile(this.getIparamsFilePath(appDir)); } }