blob: b32128d7968d85dc068d1141be924f2daf61a1b9 [file] [log] [blame]
// Copyright 2021 The Flutter Authors. 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:github/github.dart';
import 'package:meta/meta.dart';
import 'config.dart';
import 'github_service.dart';
/// Provides the ability to query a PR for the files changed.
///
/// This abstraction exists to simplify testing, and to swap out the
/// implementation details; for example between using [GithubApiGetFilesChanged]
/// and a hypothetical future `ActionsArtifactGetFilesChanged`.
abstract interface class GetFilesChanged {
/// Returns a future that comples with [FilesChanged].
///
/// There are two possible outcomes:
/// - [InconclusiveFilesChanged] an error state that is non-fatal;
/// - [SuccessfulFilesChanged] a success state, see [SuccessfulFilesChanged.filesChanged].
Future<FilesChanged> get(RepositorySlug slug, int pullRequestNumber);
}
/// Uses []"List pull requests files"](https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests-files).
///
/// This implementation has a limitation where if too many files are returned we
/// consider the result [InconclusiveFilesChanged], as we aren't sure we can get
/// every page of results.
final class GithubApiGetFilesChanged implements GetFilesChanged {
/// Creates from [GithubService].
const GithubApiGetFilesChanged(this._config);
final Config _config;
@override
Future<FilesChanged> get(RepositorySlug slug, int pullRequestNumber) async {
final List<String> files;
try {
final githubService = await _config.createGithubService(slug);
files = await githubService.listFiles(slug, pullRequestNumber);
} on GitHubError catch (e) {
return InconclusiveFilesChanged(
pullRequestNumber: pullRequestNumber,
reason: 'An error occurred: $e',
);
}
if (files.length >= _config.maxFilesChangedForSkippingEnginePhase) {
return InconclusiveFilesChanged(
pullRequestNumber: pullRequestNumber,
reason:
'>= ${_config.maxFilesChangedForSkippingEnginePhase} '
'files were changed, not confident about result size.',
);
}
return SuccessfulFilesChanged(
pullRequestNumber: pullRequestNumber,
filesChanged: files,
);
}
}
/// Represents the result of requesting the files changed at a PR number.
@immutable
sealed class FilesChanged {
const FilesChanged({required this.pullRequestNumber});
/// Which pull request was queried.
final int pullRequestNumber;
}
/// An inconclusive request: assume that all files were changed.
///
/// For debugging, [reason] contains details on why the reuslt was inconclusive.
final class InconclusiveFilesChanged extends FilesChanged {
const InconclusiveFilesChanged({
required super.pullRequestNumber,
required this.reason,
});
/// Why files changed could not be retrieved or cannot be safely used.
///
/// Common reasons could include:
/// - An HTTP exception or network connectivity issues;
/// -
final String reason;
}
/// A successful request for files changed.
final class SuccessfulFilesChanged extends FilesChanged {
SuccessfulFilesChanged({
required super.pullRequestNumber,
required Iterable<String> filesChanged,
}) : filesChanged = Set.unmodifiable(filesChanged);
/// A set of files changed, in no particular order.
///
/// This list is unmodifiable.
final Set<String> filesChanged;
}