UNPKG

@syntropysoft/praetorian

Version:

Praetorian CLI – A universal multi-environment configuration validator for DevSecOps teams. Validate, compare, and secure YAML/ENV files with ease.

304 lines 12.1 kB
"use strict"; /** * @file src/application/services/SimpleRuleLoaderService.ts * @description Simple rule loader service (SOLID SRP + Functional Programming) */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SimpleRuleLoaderService = void 0; const simple_core_rules_1 = require("../../shared/rules/simple-core-rules"); const SimpleRuleDictionary_1 = require("./rule-loading/SimpleRuleDictionary"); const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const js_yaml_1 = __importDefault(require("js-yaml")); /** * @class SimpleRuleLoaderService * @description Service for loading simple rules (SOLID SRP) */ class SimpleRuleLoaderService { constructor(options = {}) { this.workingDirectory = options.workingDirectory || process.cwd(); this.includeCoreRules = options.includeCoreRules ?? true; this.validateRules = options.validateRules ?? true; } /** * Loads simple rules from a configuration (Main Orchestrator Method) * @param config - Rule configuration * @returns Promise with loaded rules and any errors/warnings */ async loadRules(config) { // Guard clause: no config if (!config) { return this.createEmptyResult(['Configuration is required']); } try { // Initialize rule dictionary let ruleDictionary = (0, SimpleRuleDictionary_1.createEmptySimpleDictionary)(); const allWarnings = []; const allErrors = []; // Load rule sets and add to dictionary const ruleSetResults = await this.loadRuleSets(config.ruleSets); const loadedRules = this.extractRulesFromResults(ruleSetResults); // Add loaded rules to dictionary if (loadedRules.length > 0) { const dictResult = (0, SimpleRuleDictionary_1.addSimpleRulesToDictionary)(ruleDictionary, loadedRules, 'rule-sets'); ruleDictionary = dictResult.dictionary; allWarnings.push(...dictResult.warnings); } // Determine if we should include core rules as base const hasCoreAllRequest = config.ruleSets.includes('@praetorian/core/all'); const shouldIncludeAllCoreRules = this.includeCoreRules && (config.ruleSets.length === 0 || hasCoreAllRequest); // Add core rules to dictionary if needed if (shouldIncludeAllCoreRules) { const coreRules = this.getBaseRules(); const coreDictResult = (0, SimpleRuleDictionary_1.addSimpleRulesToDictionary)(ruleDictionary, coreRules, 'core-rules'); ruleDictionary = coreDictResult.dictionary; allWarnings.push(...coreDictResult.warnings); } // Apply rule overrides using dictionary if (config.overrideRules && config.overrideRules.length > 0) { const overrideResult = (0, SimpleRuleDictionary_1.overrideSimpleRulesInDictionary)(ruleDictionary, config.overrideRules); ruleDictionary = overrideResult.dictionary; allWarnings.push(...overrideResult.warnings); } // Add custom rules to dictionary if (config.customRules && config.customRules.length > 0) { const customDictResult = (0, SimpleRuleDictionary_1.addSimpleRulesToDictionary)(ruleDictionary, config.customRules, 'custom-rules'); ruleDictionary = customDictResult.dictionary; allWarnings.push(...customDictResult.warnings); } // Get final rules from dictionary const finalRules = (0, SimpleRuleDictionary_1.simpleDictionaryToRules)(ruleDictionary); // Validate if enabled const validationWarnings = this.validateRules ? this.validateLoadedRules(finalRules) : []; return { rules: finalRules, errors: [...this.extractErrorsFromResults(ruleSetResults), ...allErrors], warnings: [ ...this.extractWarningsFromResults(ruleSetResults), ...allWarnings, ...validationWarnings ], }; } catch (error) { return this.createEmptyResult([`Failed to load rules: ${error.message}`]); } } /** * Gets base rules to start with (Pure Function) * @returns Base rules */ getBaseRules() { return this.includeCoreRules ? simple_core_rules_1.ALL_CORE_SIMPLE_RULES : []; } /** * Loads multiple rule sets (Pure Function with Guard Clauses) * @param ruleSetPaths - Array of rule set paths * @returns Array of rule load results */ async loadRuleSets(ruleSetPaths) { // Guard clause: no rule sets if (!ruleSetPaths || ruleSetPaths.length === 0) { return []; } const loadPromises = ruleSetPaths.map(path => this.loadRuleSet(path)); return Promise.all(loadPromises); } /** * Loads a single rule set (Pure Function with Guard Clauses) * @param ruleSetPath - Path to rule set * @returns Rule load result */ async loadRuleSet(ruleSetPath) { // Guard clause: empty path if (!ruleSetPath || ruleSetPath.trim().length === 0) { return this.createEmptyResult(['Rule set path cannot be empty']); } try { // Check if it's a core rule set reference if (ruleSetPath.startsWith('@praetorian/core/')) { return this.loadCoreRuleSet(ruleSetPath); } // Check if it's a URL if (this.isUrl(ruleSetPath)) { return await this.loadRuleSetFromUrl(ruleSetPath); } // Load from local file return await this.loadRuleSetFromFile(ruleSetPath); } catch (error) { return this.createEmptyResult([`Failed to load rule set '${ruleSetPath}': ${error.message}`]); } } /** * Loads a core rule set (Pure Function) * @param corePath - Core rule set path * @returns Rule load result */ loadCoreRuleSet(corePath) { // Guard clause: invalid core path if (!corePath || !corePath.startsWith('@praetorian/core/')) { return this.createEmptyResult(['Invalid core rule set path']); } const coreSetName = corePath.replace('@praetorian/core/', ''); // Guard clause: unknown core set if (!(coreSetName in simple_core_rules_1.CORE_SIMPLE_RULE_SETS)) { return this.createEmptyResult([`Unknown core rule set: ${coreSetName}`]); } const rules = simple_core_rules_1.CORE_SIMPLE_RULE_SETS[coreSetName]; return { rules: Array.isArray(rules) ? rules : [], errors: [], warnings: [], }; } /** * Loads rule set from local file (Pure Function with Guard Clauses) * @param filePath - Path to rule file * @returns Rule load result */ async loadRuleSetFromFile(filePath) { // Guard clause: invalid file path if (!filePath || typeof filePath !== 'string') { return this.createEmptyResult(['Invalid file path']); } try { const fullPath = path_1.default.isAbsolute(filePath) ? filePath : path_1.default.join(this.workingDirectory, filePath); const content = await promises_1.default.readFile(fullPath, 'utf-8'); const extension = path_1.default.extname(filePath).toLowerCase(); return this.parseContentByExtension(content, extension); } catch (error) { return this.createEmptyResult([`Failed to read file '${filePath}': ${error.message}`]); } } /** * Loads rule set from URL (Placeholder - Pure Function) * @param url - URL to load from * @returns Rule load result */ async loadRuleSetFromUrl(url) { // Guard clause: invalid URL if (!url || typeof url !== 'string') { return this.createEmptyResult(['Invalid URL']); } // TODO: Implement URL loading return this.createEmptyResult(['URL loading not implemented yet']); } /** * Parses content by file extension (Pure Function) * @param content - File content * @param extension - File extension * @returns Rule load result */ parseContentByExtension(content, extension) { try { let parsed; switch (extension) { case '.yaml': case '.yml': parsed = js_yaml_1.default.load(content); break; case '.json': parsed = JSON.parse(content); break; default: return this.createEmptyResult([`Unsupported file extension: ${extension}`]); } return this.extractRulesFromContent(parsed); } catch (error) { return this.createEmptyResult([`Failed to parse content: ${error.message}`]); } } /** * Extracts rules from parsed content (Pure Function) * @param content - Parsed content * @returns Rule load result */ extractRulesFromContent(content) { // Guard clause: no content if (!content) { return this.createEmptyResult(['No content to parse']); } // Handle different content structures let rules = []; if (Array.isArray(content)) { rules = content; } else if (content.rules && Array.isArray(content.rules)) { rules = content.rules; } else if (typeof content === 'object') { // Treat object as a single rule if it has id and name if (content.id && content.name) { rules = [content]; } } // Filter valid rules and collect errors for invalid ones const validRules = []; const validationErrors = []; for (let i = 0; i < rules.length; i++) { const rule = rules[i]; const validation = (0, SimpleRuleDictionary_1.validateSimpleRule)(rule); if (validation.valid) { validRules.push(rule); } else { validationErrors.push(`Rule at index ${i}: ${validation.errors.join(', ')}`); } } return { rules: validRules, errors: validationErrors, warnings: [], }; } // Helper methods (Pure Functions) createEmptyResult(errors) { return { rules: [], errors, warnings: [], }; } extractRulesFromResults(results) { return results.flatMap(result => result.rules); } extractErrorsFromResults(results) { return results.flatMap(result => result.errors); } extractWarningsFromResults(results) { return results.flatMap(result => result.warnings); } isUrl(path) { try { new URL(path); return true; } catch { return false; } } validateLoadedRules(rules) { const validationErrors = []; for (let i = 0; i < rules.length; i++) { const rule = rules[i]; const validation = (0, SimpleRuleDictionary_1.validateSimpleRule)(rule); if (!validation.valid) { validationErrors.push(`Rule at index ${i}: ${validation.errors.join(', ')}`); } } return validationErrors.map(error => `Validation warning: ${error}`); } } exports.SimpleRuleLoaderService = SimpleRuleLoaderService; //# sourceMappingURL=SimpleRuleLoaderService.js.map