Skip to content

Commit

Permalink
♻️ refactor: GitHub例外をNetwork例外に変更してドメイン層で定義するように変更
Browse files Browse the repository at this point in the history
Issue #145
  • Loading branch information
susatthi committed May 28, 2022
1 parent a502d6e commit 95ddc61
Show file tree
Hide file tree
Showing 15 changed files with 302 additions and 301 deletions.
Expand Up @@ -2,67 +2,100 @@
// Use of this source code is governed by a MIT license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
/// 基底ドメイン例外
///
/// ドメイン層が定義する例外の基底クラス
abstract class DomainException implements Exception {
const DomainException(this.message);

/// GitHub API の例外
class GitHubException implements Exception {
const GitHubException([
this.message,
final Object message;
}

/// ドメイン層で発生する入力値検査例外
class ValidatorException extends DomainException {
const ValidatorException([
super.message = 'validator exception',
this.errorMessages = const [],
]);

/// ユーザーに表示するエラーメッセージのリスト
final List<String> errorMessages;

@override
String toString() {
return '''
ValidatorException: $message
Error messages:
$errorMessages
''';
}

/// エラーメッセージを返す
String get errorMessage => errorMessages.join('\n');
}

/// インフラストラクチャ層で発生するネットワーク関連の例外
///
/// リポジトリ実装がこの例外を投げたら、プレゼンテーション層
/// で受け取って適切に表示すること。
class NetworkException extends DomainException {
const NetworkException._([
super.message = 'network exception',
String? code,
// ignore: unnecessary_this
]) : this.code = code ?? codeUnknown;
]) : code = code ?? codeUnknown;

/// 1. 無効なJSONを送信すると、`400 Bad Request` レスポンスが返されます。
/// 2. 間違ったタイプの JSON 値を送信すると、`400 Bad Request` レスポンスが返されます。
factory GitHubException.badRequest() => const GitHubException(
factory NetworkException.badRequest() => const NetworkException._(
'Illegal request sent. (400)',
codeBadRequest,
);

/// 無効な認証情報で認証すると、`401 Unauthorized` が返されます。
factory GitHubException.badCredentials() => const GitHubException(
factory NetworkException.badCredentials() => const NetworkException._(
'Illegal request sent. (401)',
codeBadCredentials,
);

/// API は、無効な認証情報を含むリクエストを短期間に複数回検出すると、`403 Forbidden` で、
/// そのユーザに対するすべての認証試行(有効な認証情報を含む)を一時的に拒否します。
factory GitHubException.maximumNumberOfLoginAttemptsExceeded() =>
const GitHubException(
factory NetworkException.maximumNumberOfLoginAttemptsExceeded() =>
const NetworkException._(
'Please wait a while and try again. (403)',
codeMaximumNumberOfLoginAttemptsExceeded,
);

/// `404 Not Found`
factory GitHubException.notFound() => const GitHubException(
factory NetworkException.notFound() => const NetworkException._(
'No data found. (404)',
codeNotFound,
);

/// 無効なフィールドを送信すると、`422 Unprocessable Entity` レスポンスが返されます。
factory GitHubException.validationFailed() => const GitHubException(
factory NetworkException.validationFailed() => const NetworkException._(
'Illegal request sent. (422)',
codeValidationFailed,
);

/// `503 Service Unavailable` サービス停止中
factory GitHubException.serviceUnavailable() => const GitHubException(
factory NetworkException.serviceUnavailable() => const NetworkException._(
'Please wait a while and try again. (503)',
codeServiceUnavailable,
);

/// 不明なエラー
factory GitHubException.unknown() => const GitHubException(
'An unknown error has occurred. (-1)',
codeUnknown,
);

/// インターネット接続不可
factory GitHubException.noInternetConnection() => const GitHubException(
factory NetworkException.noInternetConnection() => const NetworkException._(
'Please try again in a good communication environment. (-2)',
codeNoInternetConnection,
);

/// 不明なエラー
factory NetworkException.unknown() => const NetworkException._(
'An unknown error has occurred. (-1)',
codeUnknown,
);

// エラーコードの定義
static const codeBadRequest = 'bad-request';
static const codeBadCredentials = 'bad-credentials';
Expand All @@ -71,35 +104,12 @@ class GitHubException implements Exception {
static const codeNotFound = 'not-found';
static const codeValidationFailed = 'validation-failed';
static const codeServiceUnavailable = 'service-unavailable';
static const codeUnknown = 'unknown';
static const codeNoInternetConnection = 'no-internet-connection';

/// メッセージ
final dynamic message;
static const codeUnknown = 'unknown';

/// エラーコード
final String code;

@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other is! GitHubException) {
return false;
}
return other.hashCode == hashCode;
}

@override
int get hashCode => hashValues(code, message);

@override
String toString() {
final Object? message = this.message;
if (message == null) {
return 'GitHubException[$code]';
}
return 'GitHubException[$code]: $message';
}
String toString() => 'NetworkException[$code]: $message';
}
28 changes: 14 additions & 14 deletions lib/infrastructure/github/http_client.dart
Expand Up @@ -10,8 +10,8 @@ import 'package:http/http.dart' as http;

import '../../config/env.dart';
import '../../config/env_define.dart';
import '../../domain/exceptions.dart';
import '../../utils/logger.dart';
import 'exception.dart';

/// GitHubアクセストークンプロバイダー
final githubAccessTokenProvider = Provider<String>(
Expand Down Expand Up @@ -75,23 +75,23 @@ class GitHubHttpClient {
final data = json.decode(response.body) as Map<String, dynamic>;
return responseBuilder(data);
case 400:
throw GitHubException.badRequest();
throw NetworkException.badRequest();
case 401:
throw GitHubException.badCredentials();
throw NetworkException.badCredentials();
case 403:
throw GitHubException.maximumNumberOfLoginAttemptsExceeded();
throw NetworkException.maximumNumberOfLoginAttemptsExceeded();
case 404:
throw GitHubException.notFound();
throw NetworkException.notFound();
case 422:
throw GitHubException.validationFailed();
throw NetworkException.validationFailed();
case 503:
throw GitHubException.serviceUnavailable();
throw NetworkException.serviceUnavailable();
default:
throw GitHubException.unknown();
throw NetworkException.unknown();
}
} on SocketException catch (e) {
logger.w(e);
throw GitHubException.noInternetConnection();
throw NetworkException.noInternetConnection();
}
}

Expand All @@ -110,17 +110,17 @@ class GitHubHttpClient {
case 200:
return response.body;
case 400:
throw GitHubException.badRequest();
throw NetworkException.badRequest();
case 404:
throw GitHubException.notFound();
throw NetworkException.notFound();
case 503:
throw GitHubException.serviceUnavailable();
throw NetworkException.serviceUnavailable();
default:
throw GitHubException.unknown();
throw NetworkException.unknown();
}
} on SocketException catch (e) {
logger.w(e);
throw GitHubException.noInternetConnection();
throw NetworkException.noInternetConnection();
}
}
}
8 changes: 4 additions & 4 deletions lib/infrastructure/github/repo_repository.dart
Expand Up @@ -10,9 +10,9 @@ import '../../domain/entities/search_repos_result.dart';
import '../../domain/entities/search_repos_sort.dart';
import '../../domain/entities/values/repo_count.dart';
import '../../domain/entities/values/repo_language.dart';
import '../../domain/exceptions.dart';
import '../../domain/repositories/repo_repository.dart';
import 'api.dart';
import 'exception.dart';
import 'http_client.dart';
import 'json_object/repo/repo.dart';
import 'json_object/search_repos_result/search_repos_result.dart';
Expand Down Expand Up @@ -120,9 +120,9 @@ class GitHubRepoRepository implements RepoRepository {
);
try {
return await _client.getRaw(uri: uri);
} on GitHubException catch (e) {
} on NetworkException catch (e) {
// 404 の場合はファイル名を変えてリトライする
if (e.code == GitHubException.codeNotFound) {
if (e.code == NetworkException.codeNotFound) {
continue;
}

Expand All @@ -132,6 +132,6 @@ class GitHubRepoRepository implements RepoRepository {
}

// 最終的に取得できなかったら 404 を返す
throw GitHubException.notFound();
throw NetworkException.notFound();
}
}
16 changes: 8 additions & 8 deletions lib/localizations/strings.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions lib/localizations/strings.i18n.json
Expand Up @@ -7,7 +7,9 @@
"error": "Error",
"errorOccurred": "An error has occurred",
"forksCount": "Forks count",
"gitHubExceptionMessage": {
"helpWantedIssuesCount": "Help wanted @:issuesCount",
"issuesCount": "Issues count",
"networkExceptionMessage": {
"badRequest": "Illegal request sent (400)",
"badCredentials": "Illegal request sent (401)",
"maximumNumberOfLoginAttemptsExceeded": "Please wait a while and try again (403)",
Expand All @@ -17,8 +19,6 @@
"unknown": "An unknown error has occurred (-1)",
"noInternetConnection": "Please try again in a good communication environment (-2)"
},
"helpWantedIssuesCount": "Help wanted @:issuesCount",
"issuesCount": "Issues count",
"notFoundRepos": "We couldn't find any repositories",
"ownerName": "Owner name",
"projectLanguage": "Project language",
Expand Down
6 changes: 3 additions & 3 deletions lib/localizations/strings_ja.i18n.json
Expand Up @@ -7,7 +7,9 @@
"error": "エラー",
"errorOccurred": "エラーが発生しました",
"forksCount": "Fork数",
"gitHubExceptionMessage": {
"helpWantedIssuesCount": "助けを求めている@:issuesCount",
"issuesCount": "Issue数",
"networkExceptionMessage": {
"badRequest": "不正なリクエストが送信されました (400)",
"badCredentials": "不正なリクエストが送信されました (401)",
"maximumNumberOfLoginAttemptsExceeded": "しばらく時間をおいてから再度お試しください (403)",
Expand All @@ -17,8 +19,6 @@
"unknown": "不明なエラーが発生しました (-1)",
"noInternetConnection": "通信環境の良いところで再度お試しください (-2)"
},
"helpWantedIssuesCount": "助けを求めている@:issuesCount",
"issuesCount": "Issue数",
"notFoundRepos": "リポジトリが見つかりませんでした",
"ownerName": "オーナー名",
"projectLanguage": "プロジェクト言語",
Expand Down
34 changes: 32 additions & 2 deletions lib/presentation/common/components/error_view.dart
Expand Up @@ -5,8 +5,9 @@
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';

import '../../../domain/exceptions.dart';
import '../../../localizations/strings.g.dart';
import '../../../utils/assets/assets.gen.dart';
import '../../../utils/extensions.dart';
import '../../../utils/logger.dart';

/// エラーView
Expand Down Expand Up @@ -36,7 +37,7 @@ class ErrorView extends StatelessWidget {
),
const SizedBox(height: 8),
Text(
error.toErrorMessage(),
error.errorMessage,
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 40),
Expand All @@ -45,3 +46,32 @@ class ErrorView extends StatelessWidget {
);
}
}

extension ObjectHelper on Object {
/// エラーメッセージを返す
String get errorMessage {
if (this is NetworkException) {
final error = this as NetworkException;
switch (error.code) {
case NetworkException.codeBadRequest:
return i18n.networkExceptionMessage.badRequest;
case NetworkException.codeBadCredentials:
return i18n.networkExceptionMessage.badCredentials;
case NetworkException.codeMaximumNumberOfLoginAttemptsExceeded:
return i18n
.networkExceptionMessage.maximumNumberOfLoginAttemptsExceeded;
case NetworkException.codeNotFound:
return i18n.networkExceptionMessage.notFound;
case NetworkException.codeValidationFailed:
return i18n.networkExceptionMessage.validationFailed;
case NetworkException.codeServiceUnavailable:
return i18n.networkExceptionMessage.serviceUnavailable;
case NetworkException.codeUnknown:
return i18n.networkExceptionMessage.unknown;
case NetworkException.codeNoInternetConnection:
return i18n.networkExceptionMessage.noInternetConnection;
}
}
return toString();
}
}

0 comments on commit 95ddc61

Please sign in to comment.