Skip to content
This repository was archived by the owner on May 13, 2023. It is now read-only.

feat: add generic types to .select() #94

Merged
merged 17 commits into from
Nov 1, 2022
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
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,20 @@ import 'package:postgrest/postgrest.dart';

final url = 'https://example.com/postgrest/endpoint';
final client = PostgrestClient(url);
final response = await client.from('users').select();
final response = await client.from<PostgrestList>('users').select();
```

#### Reading your data and converting it to an object

```dart
import 'package:postgrest/postgrest.dart';

final url = 'https://example.com/postgrest/endpoint';
final client = PostgrestClient(url);
final response = await client
.from('users')
.select<PostgrestList>()
.withConverter((data) => data.map(User.fromJson).toList());
```

#### Insert records
Expand All @@ -33,7 +46,7 @@ import 'package:postgrest/postgrest.dart';
final url = 'https://example.com/postgrest/endpoint';
final client = PostgrestClient(url);
try {
final data = await client.from('users')
await client.from('users')
.insert([
{'username': 'supabot', 'status': 'ONLINE'}
]);
Expand All @@ -53,7 +66,7 @@ import 'package:postgrest/postgrest.dart';

final url = 'https://example.com/postgrest/endpoint';
final client = PostgrestClient(url);
final data = await client.from('users')
await client.from('users')
.update({'status': 'OFFLINE'})
.eq('username', 'dragarcia');
```
Expand All @@ -65,7 +78,7 @@ import 'package:postgrest/postgrest.dart';

final url = 'https://example.com/postgrest/endpoint';
final client = PostgrestClient(url);
final data = await client.from('users')
await client.from('users')
.delete()
.eq('username', 'supabot');
```
Expand All @@ -78,7 +91,7 @@ import 'package:postgrest/postgrest.dart';
final url = 'https://example.com/postgrest/endpoint';
final client = PostgrestClient(url);
final response = await client.from('countries')
.select('*', FetchOptions(count: CountOption.exact));
.select<PostgrestResponse>('*', FetchOptions(count: CountOption.exact));
final data = response.data;
final count = response.count;
```
Expand Down
4 changes: 2 additions & 2 deletions lib/src/postgrest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ class PostgrestClient {
}

/// Perform a table operation.
PostgrestQueryBuilder from(String table) {
PostgrestQueryBuilder<void> from(String table) {
final url = '${this.url}/$table';
return PostgrestQueryBuilder(
return PostgrestQueryBuilder<void>(
url,
headers: headers,
schema: schema,
Expand Down
95 changes: 81 additions & 14 deletions lib/src/postgrest_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ const METHOD_PUT = 'PUT';
const METHOD_PATCH = 'PATCH';
const METHOD_DELETE = 'DELETE';

typedef _Nullable<T> = T?;

/// The base builder class.
class PostgrestBuilder<T> implements Future<T?> {
class PostgrestBuilder<T, S> implements Future<T> {
dynamic _body;
late final Headers _headers;
bool _maybeEmpty = false;
String? _method;
late final String? _schema;
late Uri _url;
PostgrestConverter? _converter;
PostgrestConverter<T, S>? _converter;
late final Client? _httpClient;
// ignore: prefer_final_fields
FetchOptions? _options;
Expand Down Expand Up @@ -62,9 +64,8 @@ class PostgrestBuilder<T> implements Future<T?> {
/// .select()
/// .withConverter<User>((data) => User.fromJson(data));
/// ```
PostgrestBuilder<S> withConverter<S>(PostgrestConverter<S> converter) {
_converter = converter;
return PostgrestBuilder<S>(
PostgrestBuilder<R, T> withConverter<R>(PostgrestConverter<R, T> converter) {
return PostgrestBuilder<R, T>(
url: _url,
headers: _headers,
schema: _schema,
Expand All @@ -75,6 +76,22 @@ class PostgrestBuilder<T> implements Future<T?> {
.._converter = converter;
}

void _assertCorrectGeneric(Type R) {
assert(
R == PostgrestList ||
R == PostgrestMap ||
R == (_Nullable<PostgrestMap>) ||
R == PostgrestListResponse ||
R == PostgrestMapResponse ||
R == (PostgrestResponse<PostgrestMap?>) ||
R == PostgrestResponse ||
R == List ||
R == (List<Map>) ||
R == Map ||
R == dynamic,
"$R is not allowed as generic for `select<R>()`. Allowed types are: `PostgrestList`, `PostgrestMap`, `PostgrestMap?`, `PostgrestListResponse`, `PostgrestMapResponse`, `PostgrestResponse`, `dynamic`.");
}

/// Sends the request and returns a [PostgrestResponse]
///
/// [head] to trigger a HEAD request
Expand All @@ -92,7 +109,7 @@ class PostgrestBuilder<T> implements Future<T?> {
/// }
/// ```
@Deprecated('Use async/await or .then instead. Deprecated in 0.2.0')
Future<PostgrestResponse<T>> execute({
Future<PostgrestResponse> execute({
bool head = false,
CountOption? count,
}) async {
Expand All @@ -103,7 +120,7 @@ class PostgrestBuilder<T> implements Future<T?> {
return _execute();
}

Future<PostgrestResponse<T>> _execute() async {
Future<PostgrestResponse> _execute() async {
if (_options?.head ?? false) {
_method = METHOD_HEAD;
}
Expand Down Expand Up @@ -183,7 +200,7 @@ class PostgrestBuilder<T> implements Future<T?> {
}

/// Parse request response to json object if possible
Future<PostgrestResponse<T>> _parseResponse(http.Response response) async {
Future<PostgrestResponse> _parseResponse(http.Response response) async {
if (response.statusCode >= 200 && response.statusCode <= 299) {
dynamic body;
int? count;
Expand All @@ -207,12 +224,62 @@ class PostgrestBuilder<T> implements Future<T?> {
: int.parse(contentRange.split('/').last);
}

// When using converter [S] is the type of the converter functions's argument. Otherwise [T] should be equal to [S]
if (S == PostgrestList) {
body = PostgrestList.from(body as Iterable) as S;
} else if (S == List<Map>) {
body = List<Map>.from(body as Iterable) as S;
} else if (S == PostgrestMap) {
body = PostgrestMap.from(body as Map) as S;

//You can't write `S == PostgrestMap?`
} else if (S == _Nullable<PostgrestMap>) {
if (body == null) {
body = null as S;
} else {
body = PostgrestMap.from(body as Map) as S;
}
} else if (S == PostgrestListResponse) {
body = PostgrestList.from(body as Iterable);
if (_converter != null) {
body = _converter!(body as S);
}
return PostgrestResponse<PostgrestList>(
data: body,
status: response.statusCode,
count: count,
);
} else if (S == PostgrestMapResponse) {
body = PostgrestMap.from(body as Map);
if (_converter != null) {
body = _converter!(body as S);
}
return PostgrestResponse<PostgrestMap>(
data: body,
status: response.statusCode,
count: count,
);
} else if (S == PostgrestResponse<PostgrestMap?>) {
if (body == null) {
body = null;
} else {
body = PostgrestMap.from(body as Map);
}
if (_converter != null) {
body = _converter!(body as S);
}
return PostgrestResponse<PostgrestMap?>(
data: body,
status: response.statusCode,
count: count,
);
}
if (_converter != null) {
body = _converter!(body);
}

return PostgrestResponse<T>(
data: body as T,
return PostgrestResponse(
data: body,
status: response.statusCode,
count: count,
);
Expand Down Expand Up @@ -289,8 +356,8 @@ class PostgrestBuilder<T> implements Future<T?> {
}

@override
Stream<T?> asStream() {
final controller = StreamController<T?>.broadcast();
Stream<T> asStream() {
final controller = StreamController<T>.broadcast();

then((value) {
controller.add(value);
Expand All @@ -311,7 +378,7 @@ class PostgrestBuilder<T> implements Future<T?> {
/// Register callbacks to be called when this future completes.
@override
Future<R> then<R>(
FutureOr<R> Function(T? value) onValue, {
FutureOr<R> Function(T value) onValue, {
Function? onError,
}) async {
if (onError != null &&
Expand Down Expand Up @@ -378,7 +445,7 @@ class PostgrestBuilder<T> implements Future<T?> {
}

@override
Future<T?> whenComplete(FutureOr<void> Function() action) {
Future<T> whenComplete(FutureOr<void> Function() action) {
return then(
(v) {
final f2 = action();
Expand Down
Loading