// Copyright (c) 2013, 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. library barback.errors; import 'package:stack_trace/stack_trace.dart'; import 'asset/asset_id.dart'; import 'transformer/wrapping_aggregate_transformer.dart'; import 'utils.dart'; /// Error thrown when an asset with [id] cannot be found. class AssetNotFoundException implements Exception { final AssetId id; AssetNotFoundException(this.id); String toString() => "Could not find asset $id."; } /// Replaces any occurrences of [AggregateException] in [errors] with the list /// of errors it contains. Iterable flattenAggregateExceptions( Iterable errors) { return errors.expand((error) { if (error is! AggregateException) return [error]; return error.errors; }); } /// The interface for exceptions from the barback graph or its transformers. /// /// These exceptions are never produced by programming errors in barback. abstract class BarbackException implements Exception { /// Takes a collection of [BarbackExceptions] and returns a single exception /// that contains them all. /// /// If [errors] is empty, returns `null`. If it only has one error, that /// error is returned. Otherwise, an [AggregateException] is returned. static BarbackException aggregate(Iterable errors) { if (errors.isEmpty) return null; if (errors.length == 1) return errors.single; return new AggregateException(errors); } } /// An error that wraps a collection of other [BarbackException]s. /// /// It implicitly flattens any [AggregateException]s that occur in the list of /// exceptions it wraps. class AggregateException implements BarbackException { final Set errors; AggregateException(Iterable errors) : errors = flattenAggregateExceptions(errors).toSet(); String toString() { var buffer = new StringBuffer(); buffer.writeln("Multiple errors occurred:\n"); for (var error in errors) { buffer.writeln(prefixLines(error.toString(), prefix: " ", firstPrefix: "- ")); } return buffer.toString(); } } /// Error thrown when two or more transformers both output an asset with [id]. class AssetCollisionException implements BarbackException { /// All the transforms that output an asset with [id]. /// /// If this only contains a single transform, that indicates that a /// transformer produced an output that collides with a source asset or an /// asset from a previous phase. final Set transforms; final AssetId id; AssetCollisionException(Iterable transforms, this.id) : transforms = new Set.from(transforms); String toString() => "Transforms $transforms all emitted asset $id."; } /// Error thrown when a transformer requests an input [id] which cannot be /// found. class MissingInputException implements BarbackException { /// The transform that requested [id]. final TransformInfo transform; final AssetId id; MissingInputException(this.transform, this.id); String toString() => "Transform $transform tried to load missing input $id."; } /// Error thrown when a transformer outputs an asset to a different package than /// the primary input's. class InvalidOutputException implements BarbackException { /// The transform that output the asset. final TransformInfo transform; final AssetId id; InvalidOutputException(this.transform, this.id); String toString() => "Transform $transform emitted $id, which wasn't in the " "same package (${transform.primaryId.package})."; } /// Base class for an error that wraps another. abstract class _WrappedException implements BarbackException { /// The wrapped exception. final error; final Chain stackTrace; String get message => "$_message: ${getErrorMessage(error)}"; String get _message; _WrappedException(error, StackTrace stackTrace) : this.error = error, this.stackTrace = _getChain(error, stackTrace); String toString() { var result = message; if (stackTrace != null) result = "$result\n${stackTrace.terse}"; return result; } } /// Returns the stack chain for [error] and [stackTrace]. Chain _getChain(error, StackTrace stackTrace) { if (error is Error && stackTrace == null) stackTrace = error.stackTrace; if (stackTrace != null) return new Chain.forTrace(stackTrace); return null; } /// Error wrapping an exception thrown by a transform. class TransformerException extends _WrappedException { /// The transform that threw the exception. final TransformInfo transform; TransformerException(this.transform, error, StackTrace stackTrace) : super(error, stackTrace); String get _message => "Transform $transform threw error"; } /// Error thrown when a source asset [id] fails to load. /// /// This can be thrown either because the source asset was expected to exist and /// did not or because reading it failed somehow. class AssetLoadException extends _WrappedException { final AssetId id; AssetLoadException(this.id, error, [StackTrace stackTrace]) : super(error, stackTrace); String get _message => "Failed to load source asset $id"; } /// Information about a single transform in the barback graph. /// /// Identifies a single transformation in the barback graph. /// /// A transformation is uniquely identified by the ID of its primary input, and /// the transformer that is applied to it. class TransformInfo { /// The transformer that's run for this transform. /// /// This may be a [Transformer] or a [WrappingAggregateTransformer]. It may /// also return additional types in the future. final transformer; /// The id of this transform's primary asset. final AssetId primaryId; TransformInfo(transformer, this.primaryId) : transformer = transformer is WrappingAggregateTransformer ? transformer.transformer : transformer; bool operator==(other) => other is TransformInfo && other.transformer == transformer && other.primaryId == primaryId; int get hashCode => transformer.hashCode ^ primaryId.hashCode; String toString() => "$transformer on $primaryId"; }