Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement http service #130

Merged
merged 19 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 32 additions & 21 deletions lib/internal/either.dart
Original file line number Diff line number Diff line change
@@ -1,58 +1,69 @@
import 'dart:async';

class Either<V, E> {
abstract class EitherContract {}

class Either<V, E> implements EitherContract {
Either._();

factory Either.success(V value) = Success<V, E>;
factory Either.failure(E error, { StackTrace? stackTrace }) = Failure<V, E>;
static Success<T> success<T>(T value) => Success<T>(value);
static Failure<E> failure<E>(dynamic error, { E? payload, StackTrace? stackTrace }) => Failure<E>(error, payload: payload, stackTrace: stackTrace);

static Future<Either<V, E>> future<V, E> ({ required Future future, Either<V, E>? Function(Failure<V, E>)? onError }) async {
static Future future<V, E> ({ required Future future, EitherContract? Function(Failure<E>)? onError }) async {
try {
final result = await future;
return Success(result);
} catch (e, s) {
final failure = Failure<V, E>(e, stackTrace: s);

if (onError != null) {
final result = onError(failure);
return result ?? failure;
if (result is Success) {
return result;
}

return failure;
if (result is Failure) {
return onError != null
? onError(result as Failure<E>)
: result;
}

return Either.success(result);
} catch (error, stackTrace) {
final result = Either.failure<E>(error, stackTrace: stackTrace);

return onError != null
? onError(result)
: result;
}
}

factory Either.tryCatch(V Function() run, Function(Failure) onError) {
static tryCatch<V>(V Function() run, Function(Failure) onError) {
try {
return Success(run());
return Either.success<V>(run());
} catch (e, s) {
return onError(Failure(e, stackTrace: s));
}
}
}

final class Success<V, E> extends Either<V, E> {
final class Success<V> implements EitherContract {
final V value;
Success(this.value): super._();
Success(this.value);

bool get hasValue => value != null;
}

final class Failure<V, E> extends Either<V, E> {
final class Failure<T> implements EitherContract {
final dynamic error;
final T? payload;
final StackTrace? stackTrace;

Failure(this.error, { this.stackTrace }): super._();
Failure(this.error, { this.payload, this.stackTrace });

Failure<V, E> throwWithStackTrace({ String? message }) {
throw Error.throwWithStackTrace(Exception(message ?? error), stackTrace!);
Failure throwWithStackTrace({ String? message }) {
throw Error.throwWithStackTrace(Exception(message ?? error), stackTrace ?? StackTrace.current);
}

void reThrow() {
Failure reThrow() {
if (stackTrace != null) {
throw error.withStackTrace(stackTrace);
}

throw error;
throw Exception(error);
}
}
45 changes: 45 additions & 0 deletions lib/internal/services/http/builders/discord_delete_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import 'package:http/http.dart';
import 'package:mineral/internal/either.dart';
import 'package:mineral/services/http/builders/delete_builder.dart';
import 'package:mineral/services/http/http_request_dispatcher.dart';
import 'package:mineral/services/http/method_adapter.dart';

/// Builder for [BaseRequest] with [Request]
/// ```dart
/// final DiscordDeleteBuilder client = DiscordDeleteBuilder(baseUrl: '/');
/// await client.delete('/foo').build();
/// ```
class DiscordDeleteBuilder extends DeleteBuilder implements MethodAdapter {
final HttpRequestDispatcher _dispatcher;
final Map<String, String> _headers = {};
final Request _request;

DiscordDeleteBuilder(this._dispatcher, this._request): super(_dispatcher, _request);

/// Add AuditLog to the [BaseRequest] headers
/// [AuditLog] is a reason for the action
/// Related to the official [Discord API](https://discord.com/developers/docs/resources/audit-log) documentation
/// ```dart
/// final DiscordHttpClient client = DiscordHttpClient(baseUrl: '/');
/// final foo = await client.delete('/foo')
/// .auditLog('foo')
/// .build();
/// ```
DiscordDeleteBuilder auditLog (String? value) {
if (value != null) {
_headers.putIfAbsent('X-Audit-Log-Reason', () => value);
}
return this;
}

/// Build the [BaseRequest] and send it to the [HttpClient]
/// ```dart
/// final DiscordHttpClient client = DiscordHttpClient(baseUrl: '/');
/// final foo = await client.delete('/foo/:id').build();
/// ```
@override
Future<EitherContract> build () async {
_request.headers.addAll(_headers);
return _dispatcher.process(_request);
}
}
47 changes: 47 additions & 0 deletions lib/internal/services/http/builders/discord_get_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:http/http.dart';
import 'package:mineral/internal/either.dart';
import 'package:mineral/services/http/builders/get_builder.dart';
import 'package:mineral/services/http/http_client.dart';
import 'package:mineral/services/http/http_request_dispatcher.dart';
import 'package:mineral/services/http/method_adapter.dart';

/// Builder for [BaseRequest] with [Request]
/// ```dart
/// final DiscordGetBuilder client = DiscordGetBuilder(baseUrl: '/');
/// final foo = await client.get('/foo').build();
/// ```
class DiscordGetBuilder extends GetBuilder implements MethodAdapter {
final HttpRequestDispatcher _dispatcher;
final Map<String, String> _headers = {};
final Request _request;

DiscordGetBuilder(this._dispatcher, this._request): super(_dispatcher, _request);

/// Add AuditLog to the [BaseRequest] headers
/// [AuditLog] is a reason for the action
/// Related to the official [Discord API](https://discord.com/developers/docs/resources/audit-log) documentation
/// ```dart
/// final DiscordHttpClient client = DiscordHttpClient(baseUrl: '/');
/// final foo = await client.put('/foo')
/// .payload({'foo': 'bar'})
/// .auditLog('foo')
/// .build();
/// ```
DiscordGetBuilder auditLog (String? value) {
if (value != null) {
_headers.putIfAbsent('X-Audit-Log-Reason', () => value);
}
return this;
}

/// Build the [BaseRequest] and send it to the [HttpClient]
/// ```dart
/// final DiscordHttpClient client = DiscordHttpClient(baseUrl: '/');
/// final foo = await client.get('/foo').build();
/// ```
@override
Future<EitherContract> build () async {
_request.headers.addAll(_headers);
return _dispatcher.process(_request);
}
}
72 changes: 72 additions & 0 deletions lib/internal/services/http/builders/discord_patch_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'dart:convert';

import 'package:http/http.dart';
import 'package:mineral/internal/either.dart';
import 'package:mineral/services/http/builders/patch_builder.dart';
import 'package:mineral/services/http/http_client.dart';
import 'package:mineral/services/http/http_request_dispatcher.dart';
import 'package:mineral/services/http/method_adapter.dart';

/// Builder for [BaseRequest] with [Request] or [MultipartRequest]
/// ```dart
/// final HttpClient client = HttpClient(baseUrl: '/');
/// final foo = await client.patch('/foo')
/// .payload({'foo': 'bar'})
/// .files([MultipartFile.fromBytes('file', [1, 2, 3])])
/// .header('Content-Type', 'application/json')
/// .build();
/// ```
class DiscordPatchBuilder extends PatchBuilder implements MethodAdapter {
final HttpRequestDispatcher _dispatcher;
final Map<String, String> _headers = {};
final Request _request;
final List<MultipartFile> _files = [];
dynamic _payload;

DiscordPatchBuilder(this._dispatcher, this._request): super(_dispatcher, _request);

/// Add AuditLog to the [BaseRequest] headers
/// [AuditLog] is a reason for the action
/// Related to the official [Discord API](https://discord.com/developers/docs/resources/audit-log) documentation
/// ```dart
/// final DiscordHttpClient client = DiscordHttpClient(baseUrl: '/');
/// final foo = await client.put('/foo')
/// .payload({'foo': 'bar'})
/// .auditLog('foo')
/// .build();
/// ```
DiscordPatchBuilder auditLog (String? value) {
if (value != null) {
_headers.putIfAbsent('X-Audit-Log-Reason', () => value);
}
return this;
}

/// Build the [BaseRequest] and send it to the [HttpClient]
/// [BaseRequest] becomes [Request] if there are no files and [MultipartRequest] if there are files
/// ```dart
/// final HttpClient client = HttpClient(baseUrl: '/');
/// final foo = await client.patch('/foo/:id')
/// .payload({'foo': 'bar'})
/// .build();
/// ```
@override
Future<EitherContract> build () async {
final BaseRequest request = _files.isNotEmpty
? MultipartRequest(_request.method, _request.url)
: _request;

if (request is MultipartRequest) {
request.files.addAll(_files);
request.fields.addAll(_payload);
request.headers.addAll(_headers);
}

if (request is Request) {
request.body = jsonEncode(_payload);
request.headers.addAll(_headers);
}

return _dispatcher.process(request);
}
}
72 changes: 72 additions & 0 deletions lib/internal/services/http/builders/discord_post_builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'dart:convert';

import 'package:http/http.dart';
import 'package:mineral/internal/either.dart';
import 'package:mineral/services/http/builders/post_builder.dart';
import 'package:mineral/services/http/http_client.dart';
import 'package:mineral/services/http/http_request_dispatcher.dart';
import 'package:mineral/services/http/method_adapter.dart';

/// Builder for [BaseRequest] with [Request] or [MultipartRequest]
/// ```dart
/// final DiscordHttpClient client = DiscordHttpClient(baseUrl: '/');
/// final foo = await client.post('/foo')
/// .payload({'foo': 'bar'})
/// .files([MultipartFile.fromBytes('file', [1, 2, 3])])
/// .header('Content-Type', 'application/json')
/// .build();
/// ```
class DiscordPostBuilder extends PostBuilder implements MethodAdapter {
final HttpRequestDispatcher _dispatcher;
final Map<String, String> _headers = {};
final Request _request;
final List<MultipartFile> _files = [];
dynamic _payload;

DiscordPostBuilder(this._dispatcher, this._request): super(_dispatcher, _request);

/// Add AuditLog to the [BaseRequest] headers
/// [AuditLog] is a reason for the action
/// Related to the official [Discord API](https://discord.com/developers/docs/resources/audit-log) documentation
/// ```dart
/// final DiscordHttpClient client = DiscordHttpClient(baseUrl: '/');
/// final foo = await client.post('/foo')
/// .payload({'foo': 'bar'})
/// .auditLog('foo')
/// .build();
/// ```
DiscordPostBuilder auditLog (String? value) {
if (value != null) {
_headers.putIfAbsent('X-Audit-Log-Reason', () => value);
}
return this;
}

/// Build the [BaseRequest] and send it to the [HttpClient]
/// [BaseRequest] becomes [Request] if there are no files and [MultipartRequest] if there are files
/// ```dart
/// final HttpClient client = HttpClient(baseUrl: '/');
/// final foo = await client.post('/foo/:id')
/// .payload({'foo': 'bar'})
/// .build();
/// ```
@override
Future<EitherContract> build () async {
final BaseRequest request = _files.isNotEmpty
? MultipartRequest(_request.method, _request.url)
: _request;

if (request is MultipartRequest) {
request.files.addAll(_files);
request.fields.addAll(_payload);
request.headers.addAll(_headers);
}

if (request is Request) {
request.body = jsonEncode(_payload);
request.headers.addAll(_headers);
}

return _dispatcher.process(request);
}
}
Loading