// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:json_annotation/json_annotation.dart'; import 'package:yaml/yaml.dart'; /// Decodes [yamlContent] as YAML and calls [constructor] with the resulting /// [Map]. /// /// If there are errors thrown while decoding [yamlContent], if it is not a /// [Map] or if [CheckedFromJsonException] is thrown when calling [constructor], /// a [ParsedYamlException] will be thrown. /// /// If [sourceUrl] is passed, it's used as the URL from which the YAML /// originated for error reporting. It can be a [String], a [Uri], or `null`. /// /// If [allowNull] is `true`, a `null` value from [yamlContent] will be allowed /// and passed to [constructor]. [constructor], therefore, will need to handle /// `null` values. T checkedYamlDecode( String yamlContent, T Function(Map?) constructor, { Uri? sourceUrl, bool allowNull = false, }) { YamlNode yaml; try { yaml = loadYamlNode(yamlContent, sourceUrl: sourceUrl); } on YamlException catch (e) { throw ParsedYamlException.fromYamlException(e); } Map? map; if (yaml is YamlMap) { map = yaml; } else if (allowNull && yaml is YamlScalar && yaml.value == null) { map = null; } else { throw ParsedYamlException('Not a map', yaml); } try { return constructor(map); } on CheckedFromJsonException catch (e) { throw toParsedYamlException(e); } } /// Returns a [ParsedYamlException] for the provided [exception]. /// /// This function assumes `exception.map` is of type `YamlMap` from /// `package:yaml`. If not, you may provide an alternative via [exceptionMap]. ParsedYamlException toParsedYamlException( CheckedFromJsonException exception, { YamlMap? exceptionMap, }) { final yamlMap = exceptionMap ?? exception.map as YamlMap; final innerError = exception.innerError; if (exception.badKey) { final key = (innerError is UnrecognizedKeysException) ? innerError.unrecognizedKeys.first : exception.key; final node = yamlMap.nodes.keys.singleWhere( (k) => (k as YamlScalar).value == key, orElse: () => yamlMap, ) as YamlNode; return ParsedYamlException(exception.message!, node, innerError: exception); } if (exception.key == null) { return ParsedYamlException( exception.message ?? 'There was an error parsing the map.', yamlMap, innerError: exception, ); } if (!yamlMap.containsKey(exception.key)) { return ParsedYamlException( ['Missing key "${exception.key}".', ?exception.message].join(' '), yamlMap, innerError: exception, ); } return ParsedYamlException( ['Unsupported value for "${exception.key}".', ?exception.message].join(' '), yamlMap.nodes[exception.key] ?? yamlMap, innerError: exception, ); } /// An exception thrown when parsing YAML that contains information about the /// location in the source where the exception occurred. class ParsedYamlException implements Exception { /// Describes the nature of the parse failure. final String message; /// The node associated with this exception. /// /// May be `null` if there was an error decoding. final YamlNode? yamlNode; /// If this exception was thrown as a result of another error, /// contains the source error object. final Object? innerError; ParsedYamlException(this.message, YamlNode this.yamlNode, {this.innerError}); factory ParsedYamlException.fromYamlException(YamlException exception) = _WrappedYamlException; /// Returns [message] formatted with source information provided by /// [yamlNode]. String? get formattedMessage => yamlNode?.span.message(message); @override String toString() => 'ParsedYamlException: $formattedMessage'; } class _WrappedYamlException implements ParsedYamlException { _WrappedYamlException(this.innerError); @override String? get formattedMessage => innerError.span?.message(innerError.message); @override final YamlException innerError; @override String get message => innerError.message; @override YamlNode? get yamlNode => null; }