| // 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:meta/meta.dart'; |
| |
| ErrorOr<R> cancelled<R>() => |
| error(ErrorCodes.RequestCancelled, 'Request was cancelled'); |
| |
| 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> { |
| ErrorOr.error(super.error) : super.t1(); |
| |
| ErrorOr.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 [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); |
| } |
| } |