| // Copyright (c) 2024, 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:analysis_server/lsp_protocol/protocol.dart'; | |
| import 'package:analyzer/src/utilities/cancellation.dart'; | |
| import 'package:meta/meta.dart'; | |
| ErrorOr<R> cancelled<R>([CancellationToken? token]) { | |
| var code = ErrorCodes.RequestCancelled; | |
| var reason = 'Request was cancelled'; | |
| if (token is CancelableToken) { | |
| if (token.cancellationCode case var cancellationCode?) { | |
| code = ErrorCodes(cancellationCode); | |
| } | |
| if (token.cancellationReason case var cancellationReason?) { | |
| reason = cancellationReason; | |
| } | |
| } | |
| return error(code, reason); | |
| } | |
| ErrorOr<R> error<R>(ErrorCodes code, String message, [String? data]) => | |
| ErrorOr<R>.error(ResponseError(code: code, message: message, data: data)); | |
| ErrorOr<R> failure<R>(ErrorOr<Object?> error) => ErrorOr<R>.error(error.error); | |
| ErrorOr<R> success<R>(R t) => ErrorOr<R>.success(t); | |
| /// A specialised version of [Either2] for working with results or errors in | |
| /// LSP handlers. | |
| /// | |
| /// Contains a helpers to assist in chaining operations while propagating errors. | |
| class ErrorOr<T> extends Either2<ResponseError, T> { | |
| new error(super.error) : super.t1(); | |
| new success(super.result) : super.t2(); | |
| /// Returns the error or throws if object is not an error. Check [isError] | |
| /// before accessing [error]. | |
| @visibleForTesting | |
| ResponseError get error { | |
| return map( | |
| (error) => error, | |
| (_) => throw StateError('Value is not an error'), | |
| ); | |
| } | |
| /// Returns the error or `null` if this object is not an error. | |
| ResponseError? get errorOrNull { | |
| return isError ? error : null; | |
| } | |
| /// Returns `true` if this object is an error, `false` if it is a result. | |
| /// | |
| /// Prefer [mapResult] instead of checking this flag if the errors will simply | |
| /// be propagated as-is. | |
| bool get isError => map((_) => true, (_) => false); | |
| /// Returns true if this object is aa result, false if it is an error. Prefer | |
| /// [ifResult] or [mapResult] instead to read/operate on errors. | |
| bool get isResult => map((_) => false, (_) => true); | |
| /// Returns the result or throws if this object is an error. Check [isError] | |
| /// before accessing [result]. It is valid for this to return null is the | |
| /// object does not represent an error but the resulting value was null. | |
| @visibleForTesting | |
| T get result { | |
| return map( | |
| (_) => throw StateError('Value is not a result'), | |
| (result) => result, | |
| ); | |
| } | |
| /// Returns the result or `null` if this object is an error. | |
| T? get resultOrNull { | |
| return isError ? null : result; | |
| } | |
| /// If this object is an error, calls [f] with the error. | |
| void ifError(void Function(ResponseError) f) { | |
| if (isError) { | |
| f(error); | |
| } | |
| } | |
| /// If this object is a result, calls [f] with the value. | |
| void ifResult(void Function(T) f) { | |
| if (!isError) { | |
| f(result); | |
| } | |
| } | |
| /// If this object is a result, maps [result] through [f], otherwise returns | |
| /// a new error object representing [error]. | |
| Future<ErrorOr<N>> mapResult<N>(Future<ErrorOr<N>> Function(T) f) async { | |
| return isError | |
| // Re-wrap the error using our new type arg | |
| ? ErrorOr<N>.error(error) | |
| // Otherwise call the map function | |
| : await f(result); | |
| } | |
| /// Sync version of [mapResult]. | |
| ErrorOr<N> mapResultSync<N>(ErrorOr<N> Function(T) f) { | |
| return isError | |
| // Re-wrap the error using our new type arg | |
| ? ErrorOr<N>.error(error) | |
| // Otherwise call the map function | |
| : f(result); | |
| } | |
| } | |
| extension ErrorOrRecord2Extension<T1, T2> on (ErrorOr<T1>, ErrorOr<T2>) { | |
| void ifResults(void Function(T1, T2) f) { | |
| if ($1.isError || $2.isError) { | |
| return; | |
| } | |
| f($1.result, $2.result); | |
| } | |
| /// If all parts of the record are results, maps them through [f], otherwise | |
| /// returns a new error object representing the first error. | |
| Future<ErrorOr<R>> mapResults<R>( | |
| Future<ErrorOr<R>> Function(T1, T2) f, | |
| ) async { | |
| if ($1.isError) { | |
| return failure($1); | |
| } | |
| if ($2.isError) { | |
| return failure($2); | |
| } | |
| return await f($1.result, $2.result); | |
| } | |
| /// Sync version of [mapResults]. | |
| ErrorOr<R> mapResultsSync<R>(ErrorOr<R> Function(T1, T2) f) { | |
| if ($1.isError) { | |
| return failure($1); | |
| } | |
| if ($2.isError) { | |
| return failure($2); | |
| } | |
| return f($1.result, $2.result); | |
| } | |
| } | |
| extension ErrorOrRecord3Extension<T1, T2, T3> | |
| on (ErrorOr<T1>, ErrorOr<T2>, ErrorOr<T3>) { | |
| void ifResults(void Function(T1, T2, T3) f) { | |
| if ($1.isError || $2.isError || $3.isError) { | |
| return; | |
| } | |
| f($1.result, $2.result, $3.result); | |
| } | |
| /// If all parts of the record are results, maps them through [f], otherwise | |
| /// returns a new error object representing the first error. | |
| Future<ErrorOr<R>> mapResults<R>( | |
| Future<ErrorOr<R>> Function(T1, T2, T3) f, | |
| ) async { | |
| if ($1.isError) { | |
| return failure($1); | |
| } | |
| if ($2.isError) { | |
| return failure($2); | |
| } | |
| if ($3.isError) { | |
| return failure($3); | |
| } | |
| return await f($1.result, $2.result, $3.result); | |
| } | |
| /// Sync version of [mapResults]. | |
| ErrorOr<R> mapResultsSync<R>(ErrorOr<R> Function(T1, T2, T3) f) { | |
| if ($1.isError) { | |
| return failure($1); | |
| } | |
| if ($2.isError) { | |
| return failure($2); | |
| } | |
| if ($3.isError) { | |
| return failure($3); | |
| } | |
| return f($1.result, $2.result, $3.result); | |
| } | |
| } | |
| extension ErrorOrRecord4Extension<T1, T2, T3, T4> | |
| on (ErrorOr<T1>, ErrorOr<T2>, ErrorOr<T3>, ErrorOr<T4>) { | |
| void ifResults(void Function(T1, T2, T3, T4) f) { | |
| if ($1.isError || $2.isError || $3.isError || $4.isError) { | |
| return; | |
| } | |
| f($1.result, $2.result, $3.result, $4.result); | |
| } | |
| /// If all parts of the record are results, maps them through [f], otherwise | |
| /// returns a new error object representing the first error. | |
| Future<ErrorOr<R>> mapResults<R>( | |
| Future<ErrorOr<R>> Function(T1, T2, T3, T4) f, | |
| ) async { | |
| if ($1.isError) { | |
| return failure($1); | |
| } | |
| if ($2.isError) { | |
| return failure($2); | |
| } | |
| if ($3.isError) { | |
| return failure($3); | |
| } | |
| if ($4.isError) { | |
| return failure($4); | |
| } | |
| return await f($1.result, $2.result, $3.result, $4.result); | |
| } | |
| /// Sync version of [mapResults]. | |
| ErrorOr<R> mapResultsSync<R>(ErrorOr<R> Function(T1, T2, T3, T4) f) { | |
| if ($1.isError) { | |
| return failure($1); | |
| } | |
| if ($2.isError) { | |
| return failure($2); | |
| } | |
| if ($3.isError) { | |
| return failure($3); | |
| } | |
| if ($4.isError) { | |
| return failure($4); | |
| } | |
| return f($1.result, $2.result, $3.result, $4.result); | |
| } | |
| } | |
| extension IterableErrorOrExtension<T> on Iterable<ErrorOr<T>> { | |
| /// Converts a [List<ErrorOr<T>>] into an [ErrorOr<List<T>>]. If any of the | |
| /// items represents an error, that error will be returned. Otherwise, the | |
| /// list of results will be returned in a success response. | |
| ErrorOr<List<T>> get errorOrResults { | |
| var results = <T>[]; | |
| for (var item in this) { | |
| if (item.isError) { | |
| return failure(item); | |
| } | |
| results.add(item.result); | |
| } | |
| return success(results); | |
| } | |
| } |