@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
JavaScript
"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