Skip to content

Commit

Permalink
Merge pull request #31 from RatakondalaArun/RatakondalaArun/issue29
Browse files Browse the repository at this point in the history
Ratakondala-arun/issue29
  • Loading branch information
RatakondalaArun authored Dec 29, 2020
2 parents ecfcbcd + 5a48254 commit 1893631
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 99 deletions.
5 changes: 5 additions & 0 deletions .todo
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Improve Message class
- [] Add "threadMessageCount"
- [] Add isParent getter
- [] Add isChild getter
- [] Add isEdited getter
131 changes: 124 additions & 7 deletions lib/gitter_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@ library gitterapi;
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:dio/dio.dart';
import 'package:meta/meta.dart' show visibleForTesting;
import 'package:meta/meta.dart' show visibleForTesting, required;

part 'src/abstract/version.dart';
part 'src/abstract/resource.dart';
part 'src/abstract/version.dart';
part 'src/api_keys.dart';
part 'src/errors/gitter_api_error.dart';
part 'src/utils/auth.dart';
part 'src/v1.dart';
part 'src/v1/user_resource.dart';
part 'src/v1/rooms_resource.dart';
part 'src/v1/messages_resource.dart';
part 'src/v1/groups_resource.dart';
part 'src/v1/messages_resource.dart';
part 'src/v1/rooms_resource.dart';
part 'src/v1/stream_api.dart';
part 'src/utils/auth.dart';
part 'src/errors/gitter_api_error.dart';
part 'src/v1/user_resource.dart';

class GitterApi {
final host = 'api.gitter.im';
Expand All @@ -31,3 +32,119 @@ class GitterApi {
_v1 = V1(this);
}
}

/// This object wraps all the required result params from the
/// request headers.
class Result<T> {
/// Status code retured by server.
final int statusCode;

/// Status message returned by server.
final String statusMessage;

/// `X-RateLimit-Limit` header.
final int maxRateLimit;

/// `X-RateLimit-Remaining` header.
final int remainingRateLimit;

/// `X-RateLimit-Reset` header.
///
/// Rate limit will get reset after this.
final DateTime rateLimitReset;

/// This is the data recieved from server.
final T data;

/// Error object.
/// This will be null if there is no error.
final Object error;

/// You can use this to debug where excatly
/// the error happend if any error occurs.
final StackTrace stackTrace;

/// Return if error==null.
bool get isError => error == null;

/// Creates a instance if [this].
const Result({
this.statusCode,
this.statusMessage,
this.maxRateLimit,
this.remainingRateLimit,
this.rateLimitReset,
this.data,
this.error,
this.stackTrace,
});

/// Creates a instance of [this].
/// When the request is success.
factory Result.success({
@required Headers headers,
@required int statusCode,
@required String statusMessage,
@required T data,
}) {
List<String> maxRateLimit;
List<String> remainingRateLimit;
List<String> rateLimitReset;

maxRateLimit = (headers['X-RateLimit-Limit'] ?? ['0']);
remainingRateLimit = (headers['X-RateLimit-Remaining'] ?? ['0']);
rateLimitReset = (headers['X-RateLimit-Reset'] ?? ['0']);

return Result<T>(
statusCode: statusCode,
maxRateLimit: int.tryParse(maxRateLimit[0] ?? '0'),
remainingRateLimit: int.tryParse(remainingRateLimit[0] ?? '0'),
rateLimitReset: DateTime.fromMillisecondsSinceEpoch(
int.tryParse(rateLimitReset[0] ?? '0'),
),
data: data,
);
}

/// Creates a instance of this with error object.
factory Result.fromError({
@required int statusCode,
@required String statusMessage,
@required Object error,
@required StackTrace stackTrace,
T data,
}) {
return Result<T>(
statusCode: statusCode,
statusMessage: statusMessage,
error: error,
stackTrace: stackTrace,
data: data,
);
}

Result<T> copyWith({
int statusCode,
String statusMessage,
int maxRateLimit,
int remainingRateLimit,
DateTime rateLimitReset,
T data,
Object error,
}) {
return Result<T>(
statusCode: statusCode ?? this.statusCode,
statusMessage: statusMessage ?? this.statusMessage,
maxRateLimit: maxRateLimit ?? this.maxRateLimit,
remainingRateLimit: remainingRateLimit ?? this.remainingRateLimit,
rateLimitReset: rateLimitReset ?? this.rateLimitReset,
data: data ?? this.data,
error: error ?? this.error,
);
}

@override
String toString() {
return 'Result(statusCode: $statusCode, statusMessage: $statusMessage, maxRateLimit: $maxRateLimit, remainingRateLimit: $remainingRateLimit, rateLimitReset: $rateLimitReset, data: $data, error: $error)';
}
}
10 changes: 5 additions & 5 deletions lib/models.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export 'models/user.dart';
export 'models/room.dart';
export 'models/provider.dart';
export 'models/message.dart';
export 'models/group.dart';
export 'models/issue.dart';
export 'models/mention.dart';
export 'models/group.dart';
export 'models/message.dart';
export 'models/permission.dart';
export 'models/provider.dart';
export 'models/room.dart';
export 'models/user.dart';
21 changes: 14 additions & 7 deletions lib/src/errors/gitter_api_error.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@ class GitterApiException implements Exception {
/// Original exception may contains
final Exception originalException;

/// Contains `data` and `statusCode` keys
final Map<String, dynamic> response;
final int statusCode;
final String statusMessage;

/// Helper getter returns true if response is empty or null,
bool get isResponseEmpty => response == null || response.isEmpty;

GitterApiException(
const GitterApiException(
this.message, {
this.originalException,
this.response,
this.statusCode,
this.statusMessage,
});

factory GitterApiException.fromDio(DioError error) {
return GitterApiException(
error.message,
originalException: error,
statusCode: error?.response?.statusCode,
statusMessage: error?.response?.statusMessage,
);
}
}
51 changes: 29 additions & 22 deletions lib/src/v1.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class V1 extends Version {
_streamApi = StreamApi(this);
}

Future<T> _jsonRequest<T>(
Future<Result<T>> _jsonRequest<T>(
String path, {
String method = 'GET',
Map<String, dynamic> queryParameters = const <String, dynamic>{},
Expand All @@ -42,25 +42,32 @@ class V1 extends Version {
// instance of duo with BaseOptions
final duo = new Dio(options);

// send request
final response = await duo.request<T>(
'/$_version/$path',
queryParameters: _sanatized(queryParameters),
data: _sanatized(postData),
);
Response<T> response;
try {
// send request
response = await duo.request<T>(
'/$_version/$path',
queryParameters: _sanatized(queryParameters),
data: _sanatized(postData),
);

// if statusCode is not 200 throw a error
if (response.statusCode != 200) {
throw GitterApiException(
'statusCode != 200 statusMessage: ${response.statusMessage} statusCode:${response.statusCode}',
response: response.data != null
? {'data': response.data, 'statusCode': response.statusCode}
: null,
// Converts response to Result object.
return Result<T>.success(
headers: response.headers,
data: response.data,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
);
} catch (e, st) {
// Convert exceptions to Result object
return Result<T>.fromError(
statusCode: response.statusCode,
statusMessage: response.statusMessage,
error: e,
stackTrace: st,
data: response.data,
);
}

// cast the response.data to type T
return response.data;
}

Future<Stream<StreamEvent>> _streamRequest(
Expand All @@ -85,11 +92,11 @@ class V1 extends Version {

try {
response = await duo.request<ResponseBody>('/$_version/$path');
} on DioError catch (e, st) {
print('$e, $st');
rethrow;
} catch (e, st) {
print('$e, $st');
} on DioError catch (e) {
// catch all DioErrors.
throw GitterApiException.fromDio(e);
} catch (_) {
// rethrow remaining exceptions.
rethrow;
}

Expand Down
8 changes: 4 additions & 4 deletions lib/src/v1/groups_resource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ class GroupsResource extends Resource<V1> {
GroupsResource(V1 v) : super(v, 'groups');

/// List groups the current user is in.
Future<List> getGropus() {
Future<Result<List>> getGropus() {
return _v._jsonRequest<List>('$_path');
}

/// List of rooms nested under the specified group.
Future<List> getRooms(String groupId) {
Future<Result<List>> getRooms(String groupId) {
return _v._jsonRequest<List>('$_path/$groupId/rooms');
}

/// Create room nested under the specified group.
Future<void> createRoom(
Future<Result<Map>> createRoom(
String groupId, {
String name,
String topic,
Expand All @@ -25,7 +25,7 @@ class GroupsResource extends Resource<V1> {
}) {
// TODO(@RatakondalaArun): Not sure how this works
// checkout this https://developer.gitter.im/docs/groups-resource
return _v._jsonRequest<void>(
return _v._jsonRequest<Map>(
'$_path/$groupId/rooms',
method: 'POST',
postData: {
Expand Down
14 changes: 8 additions & 6 deletions lib/src/v1/messages_resource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MessagesResource extends Resource<V1> {
/// - `aroundId`: Get messages around aroundId including this message.
/// - `limit`: Maximum number of messages to return (constrained to 100 or less).
/// - `q`: Search query.
Future<List<dynamic>> getMessages(
Future<Result<List<dynamic>>> getMessages(
String roomId, {
int skip,
String beforeId,
Expand All @@ -37,7 +37,7 @@ class MessagesResource extends Resource<V1> {
}

/// There is also a way to retrieve a single message using its id.
Future<Map> getSingleMessage(String roomId, String messageId) {
Future<Result<Map>> getSingleMessage(String roomId, String messageId) {
return _v._jsonRequest<Map>('rooms/$roomId/$_path/$messageId');
}

Expand All @@ -46,7 +46,8 @@ class MessagesResource extends Resource<V1> {
/// - `text`: Required Body of the message.
/// - `status`: Boolean, set to true to indicate that the message is a status update (what /me uses)
///
Future<void> sendMessage(String roomId, String messageText, {bool status}) {
Future<Result<void>> sendMessage(String roomId, String messageText,
{bool status}) {
return _v._jsonRequest<void>(
'rooms/$roomId/$_path',
method: 'POST',
Expand Down Expand Up @@ -90,7 +91,8 @@ class MessagesResource extends Resource<V1> {
/// "v": 2
/// }
/// ```
Future<Map> updateMessage(String roomId, String messageId, String text) {
Future<Result<Map>> updateMessage(
String roomId, String messageId, String text) {
return _v._jsonRequest<Map>(
'rooms/$roomId/$_path/$messageId',
method: 'PUT',
Expand All @@ -102,13 +104,13 @@ class MessagesResource extends Resource<V1> {

/// You can retrieve unread items and
/// mentions using the following endpoint.
Future<Map> getUnreadItems(String userId, String roomId) {
Future<Result<Map>> getUnreadItems(String userId, String roomId) {
return _v._jsonRequest<Map>('user/$userId/rooms/$roomId/unreadItems');
}

/// There is an additional endpoint nested under
/// rooms that you can use to mark chat messages as read.
Future<void> markMessagesAsRead(
Future<Result<void>> markMessagesAsRead(
String userId,
String roomId, {
List<String> chatIds = const [],
Expand Down
Loading

0 comments on commit 1893631

Please # to comment.