YAML 1.2 parser and serializer for JavaScript environments with complete specification support
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
js-yaml provides comprehensive error handling through the YAMLException class, which extends the standard JavaScript Error with detailed position information and helpful debugging features.
The primary error class for all YAML parsing and serialization errors.
class YAMLException extends Error {
constructor(reason, mark);
toString(compact);
}interface YAMLException extends Error {
name: 'YAMLException';
reason: string;
mark: Mark | null;
message: string;
}
interface Mark {
name: string | null;
line: number;
column: number;
snippet: string | null;
}name - Always 'YAMLException' for type identification
reason - Human-readable description of the error cause
mark - Position information where the error occurred:
name - Source filename (if provided in options)line - Line number (0-based)column - Column number (0-based)snippet - Code snippet showing the error locationmessage - Formatted error message combining reason and position
Invalid YAML Syntax:
try {
yaml.load('invalid: yaml: syntax: error');
} catch (e) {
console.log(e.reason); // "bad indentation of a mapping entry"
console.log(e.mark.line); // 0
console.log(e.mark.column); // 20
}Duplicate Keys:
try {
yaml.load(`
key: value1
key: value2
`);
} catch (e) {
console.log(e.reason); // "duplicated mapping key"
}Invalid Multi-Document:
// load() expects single document
try {
yaml.load(`
---
first: document
---
second: document
`);
} catch (e) {
console.log(e.reason); // "expected a single document in the stream, but found more"
}Unknown Tags:
try {
yaml.load('value: !unknown-tag data');
} catch (e) {
console.log(e.reason); // "unknown tag !<unknown-tag>"
}Invalid Type Data:
try {
yaml.load('timestamp: !!timestamp invalid-date');
} catch (e) {
console.log(e.reason); // Cannot resolve timestamp
}Non-serializable Values:
const objWithFunction = {
name: 'test',
callback: function() { return 'hello'; }
};
try {
yaml.dump(objWithFunction);
} catch (e) {
console.log(e.reason); // "unacceptable kind of an object to dump"
}Circular References:
const circular = { name: 'circular' };
circular.self = circular;
try {
yaml.dump(circular, { noRefs: true });
} catch (e) {
console.log(e.reason); // "circular reference"
}const yaml = require('js-yaml');
function parseYamlSafely(yamlString) {
try {
return yaml.load(yamlString);
} catch (e) {
if (e instanceof yaml.YAMLException) {
console.error('YAML parsing failed:', e.message);
return null;
}
throw e; // Re-throw non-YAML errors
}
}function parseWithDetailedErrors(yamlString, filename = null) {
try {
return yaml.load(yamlString, { filename });
} catch (e) {
if (e instanceof yaml.YAMLException) {
console.error('YAML Error Details:');
console.error(' Reason:', e.reason);
if (e.mark) {
console.error(` Location: line ${e.mark.line + 1}, column ${e.mark.column + 1}`);
if (e.mark.name) {
console.error(' File:', e.mark.name);
}
if (e.mark.snippet) {
console.error(' Snippet:');
console.error(e.mark.snippet);
}
}
return null;
}
throw e;
}
}Handle non-fatal warnings during parsing:
function parseWithWarnings(yamlString) {
const warnings = [];
const doc = yaml.load(yamlString, {
onWarning: (warning) => {
warnings.push({
message: warning.reason,
line: warning.mark ? warning.mark.line + 1 : null,
column: warning.mark ? warning.mark.column + 1 : null
});
}
});
return { document: doc, warnings };
}
// Usage
const result = parseWithWarnings(yamlContent);
if (result.warnings.length > 0) {
console.warn(`Found ${result.warnings.length} warnings:`);
result.warnings.forEach(w => {
console.warn(` Line ${w.line}: ${w.message}`);
});
}Implement fallback strategies for failed parsing:
function parseWithFallback(yamlString, fallbackSchema = null) {
const schemas = [
yaml.DEFAULT_SCHEMA,
yaml.JSON_SCHEMA,
yaml.FAILSAFE_SCHEMA,
fallbackSchema
].filter(Boolean);
let lastError;
for (const schema of schemas) {
try {
return yaml.load(yamlString, {
schema,
skipInvalid: true
});
} catch (e) {
lastError = e;
continue;
}
}
throw lastError;
}function validateYamlStructure(yamlString, requiredFields = []) {
const errors = [];
let doc;
// Parse errors
try {
doc = yaml.load(yamlString);
} catch (e) {
if (e instanceof yaml.YAMLException) {
errors.push({
type: 'parse',
message: e.reason,
line: e.mark ? e.mark.line + 1 : null
});
return { valid: false, errors };
}
throw e;
}
// Structure validation errors
if (typeof doc !== 'object' || doc === null) {
errors.push({
type: 'structure',
message: 'Root document must be an object'
});
} else {
requiredFields.forEach(field => {
if (!(field in doc)) {
errors.push({
type: 'validation',
message: `Missing required field: ${field}`
});
}
});
}
return {
valid: errors.length === 0,
document: doc,
errors
};
}Extend YAMLException for application-specific errors:
class ConfigValidationError extends yaml.YAMLException {
constructor(message, field, mark = null) {
super(message, mark);
this.name = 'ConfigValidationError';
this.field = field;
}
}
function validateConfig(yamlString) {
let config;
try {
config = yaml.load(yamlString);
} catch (e) {
throw e; // Re-throw parsing errors as-is
}
if (!config.database) {
throw new ConfigValidationError(
'Database configuration is required',
'database'
);
}
if (!config.database.host) {
throw new ConfigValidationError(
'Database host is required',
'database.host'
);
}
return config;
}function formatError(error, compact = false) {
if (!(error instanceof yaml.YAMLException)) {
return error.message;
}
if (compact) {
return error.toString(true);
}
let formatted = `YAML Error: ${error.reason}`;
if (error.mark) {
formatted += `\nLocation: line ${error.mark.line + 1}, column ${error.mark.column + 1}`;
if (error.mark.name) {
formatted += ` in ${error.mark.name}`;
}
if (error.mark.snippet) {
formatted += `\n\nSnippet:\n${error.mark.snippet}`;
}
}
return formatted;
}function logYamlError(error, context = {}) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
type: 'yaml_error',
reason: error.reason,
...context
};
if (error.mark) {
logEntry.position = {
line: error.mark.line + 1,
column: error.mark.column + 1,
filename: error.mark.name
};
}
console.error(JSON.stringify(logEntry, null, 2));
}describe('YAML Error Handling', () => {
test('should throw YAMLException for invalid syntax', () => {
expect(() => {
yaml.load('invalid: yaml: syntax');
}).toThrow(yaml.YAMLException);
});
test('should provide position information', () => {
try {
yaml.load('key:\n - invalid\n syntax');
} catch (e) {
expect(e.mark.line).toBe(2);
expect(e.mark.column).toBeGreaterThan(0);
expect(e.mark.snippet).toContain('syntax');
}
});
test('should handle warnings gracefully', () => {
const warnings = [];
const doc = yaml.load(yamlWithWarnings, {
onWarning: (w) => warnings.push(w)
});
expect(warnings).toHaveLength(1);
expect(warnings[0]).toBeInstanceOf(yaml.YAMLException);
});
});