Skip to content

Commit 998e32b

Browse files
authored
Merge pull request #12 from supabase-community/chore/cleanup
chore: Code cleanup
2 parents 8b0cbe5 + 8f7c948 commit 998e32b

File tree

4 files changed

+283
-215
lines changed

4 files changed

+283
-215
lines changed

lib/mock_supabase_http_client.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,3 @@
44
library;
55

66
export 'src/mock_supabase_http_client.dart';
7-
8-
// TODO: Export any libraries intended for clients of this package.

lib/src/handlers/rpc_handler.dart

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import 'dart:convert';
2+
3+
import 'package:http/http.dart';
4+
5+
/// Handles RPC (Remote Procedure Call) operations for the mock Supabase client
6+
class RpcHandler {
7+
final Map<
8+
String,
9+
dynamic Function(Map<String, dynamic>? params,
10+
Map<String, List<Map<String, dynamic>>> tables)> _rpcFunctions;
11+
final Map<String, List<Map<String, dynamic>>> _database;
12+
13+
RpcHandler(this._rpcFunctions, this._database);
14+
15+
/// Handles an RPC call
16+
///
17+
/// [functionName] The name of the RPC function to call
18+
/// [request] The original HTTP request
19+
/// [body] The parsed request body containing parameters
20+
StreamedResponse handleRpc(
21+
String functionName,
22+
BaseRequest request,
23+
dynamic body,
24+
) {
25+
if (!_rpcFunctions.containsKey(functionName)) {
26+
return _createResponse(
27+
{'error': 'RPC function not found'},
28+
statusCode: 404,
29+
request: request,
30+
);
31+
}
32+
33+
final function = _rpcFunctions[functionName]!;
34+
35+
try {
36+
final result = function(body, _database);
37+
return _createResponse(result, request: request);
38+
} catch (e) {
39+
return _createResponse(
40+
{'error': 'RPC function execution failed: $e'},
41+
statusCode: 500,
42+
request: request,
43+
);
44+
}
45+
}
46+
47+
/// Creates a StreamedResponse with the given data and headers
48+
StreamedResponse _createResponse(
49+
dynamic data, {
50+
int statusCode = 200,
51+
required BaseRequest request,
52+
Map<String, String>? headers,
53+
}) {
54+
final responseHeaders = {
55+
'content-type': 'application/json; charset=utf-8',
56+
...?headers,
57+
};
58+
59+
return StreamedResponse(
60+
Stream.value(utf8.encode(jsonEncode(data))),
61+
statusCode,
62+
headers: responseHeaders,
63+
request: request,
64+
);
65+
}
66+
67+
/// Registers a new RPC function
68+
///
69+
/// [name] The name of the function to register
70+
/// [function] The function implementation
71+
void registerFunction(
72+
String name,
73+
dynamic Function(Map<String, dynamic>? params,
74+
Map<String, List<Map<String, dynamic>>> tables)
75+
function,
76+
) {
77+
_rpcFunctions[name] = function;
78+
}
79+
80+
/// Clears all registered RPC functions
81+
void reset() {
82+
_rpcFunctions.clear();
83+
}
84+
}

lib/src/mock_supabase_http_client.dart

Lines changed: 14 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@ import 'dart:convert';
22

33
import 'package:http/http.dart';
44

5+
import 'handlers/rpc_handler.dart';
6+
import 'utils/filter_parser.dart';
7+
58
class MockSupabaseHttpClient extends BaseClient {
69
final Map<String, List<Map<String, dynamic>>> _database = {};
710
final Map<
811
String,
912
dynamic Function(Map<String, dynamic>? params,
1013
Map<String, List<Map<String, dynamic>>> tables)> _rpcFunctions = {};
1114

12-
MockSupabaseHttpClient();
15+
late final RpcHandler _rpcHandler;
16+
17+
MockSupabaseHttpClient() {
18+
_rpcHandler = RpcHandler(_rpcFunctions, _database);
19+
}
1320

1421
void reset() {
1522
// Clear the mock database and RPC functions
1623
_database.clear();
17-
_rpcFunctions.clear();
24+
_rpcHandler.reset();
1825
}
1926

2027
/// Registers a RPC function that can be called using the `rpc` method on a `Postgrest` client.
@@ -122,7 +129,7 @@ class MockSupabaseHttpClient extends BaseClient {
122129
// Handle RPC call
123130
if (pathSegments.length > restIndex + 2) {
124131
final functionName = pathSegments[restIndex + 2];
125-
return _handleRpc(functionName, request, body);
132+
return _rpcHandler.handleRpc(functionName, request, body);
126133
} else {
127134
return _createResponse({'error': 'RPC function name not provided'},
128135
statusCode: 400, request: request);
@@ -250,23 +257,12 @@ class MockSupabaseHttpClient extends BaseClient {
250257
}
251258
}
252259

253-
/// Checks if a given item matches the provided filters.
254-
///
255-
/// This method iterates through each filter in the `filters` map,
256-
/// parses the filter using `_parseFilter`, and applies it to the `item`.
257-
/// If any filter doesn't match, the method returns false.
258-
/// If all filters match, it returns true.
259-
///
260-
/// [row] The item to check against the filters.
261-
/// [filters] A map of filter keys and their corresponding values.
262-
/// Returns true if the item matches all filters, false otherwise.
263260
bool _matchesFilters({
264261
required Map<String, dynamic> row,
265262
required Map<String, String> filters,
266263
}) {
267-
// Check if an item matches the provided filters
268264
for (var columnName in filters.keys) {
269-
final filter = _parseFilter(
265+
final filter = FilterParser.parseFilter(
270266
columnName: columnName,
271267
postrestFilter: filters[columnName]!,
272268
targetRow: row,
@@ -355,7 +351,7 @@ class MockSupabaseHttpClient extends BaseClient {
355351
final parts = key.split('.');
356352
final referencedTableName = parts[0];
357353
final referencedColumnName = parts[1];
358-
final filter = _parseFilter(
354+
final filter = FilterParser.parseFilter(
359355
columnName: referencedColumnName,
360356
postrestFilter: value,
361357
targetRow: returningRows.first[referencedTableName] is List
@@ -390,7 +386,7 @@ class MockSupabaseHttpClient extends BaseClient {
390386
// referenced table filtering with !inner
391387
} else {
392388
// Regular filtering on the top level table
393-
final filter = _parseFilter(
389+
final filter = FilterParser.parseFilter(
394390
columnName: key,
395391
postrestFilter: value,
396392
targetRow: returningRows.first,
@@ -580,7 +576,7 @@ class MockSupabaseHttpClient extends BaseClient {
580576
key != 'order' &&
581577
key != 'limit' &&
582578
key != 'range') {
583-
final filter = _parseFilter(
579+
final filter = FilterParser.parseFilter(
584580
columnName: key,
585581
postrestFilter: value,
586582
targetRow: returningRows.isNotEmpty ? returningRows.first : {},
@@ -623,183 +619,6 @@ class MockSupabaseHttpClient extends BaseClient {
623619
);
624620
}
625621

626-
bool Function(Map<String, dynamic> row) _parseFilter({
627-
required String columnName,
628-
required String postrestFilter,
629-
required Map<String, dynamic> targetRow,
630-
}) {
631-
// Parse filters from query parameters
632-
if (columnName == 'or') {
633-
final orFilters =
634-
postrestFilter.substring(1, postrestFilter.length - 1).split(',');
635-
return (row) {
636-
return orFilters.any((filter) {
637-
final parts = filter.split('.');
638-
final subColumnName = parts[0];
639-
final operator = parts[1];
640-
final value = parts.sublist(2).join('.');
641-
final subFilter = _parseFilter(
642-
columnName: subColumnName,
643-
postrestFilter: '$operator.$value',
644-
targetRow: row);
645-
return subFilter(row);
646-
});
647-
};
648-
} else if (postrestFilter.startsWith('eq.')) {
649-
final value = postrestFilter.substring(3);
650-
return (row) => row[columnName].toString() == value;
651-
} else if (postrestFilter.startsWith('neq.')) {
652-
final value = postrestFilter.substring(4);
653-
return (row) => row[columnName].toString() != value;
654-
} else if (postrestFilter.startsWith('gt.')) {
655-
return _handleComparison(
656-
operator: 'gt',
657-
value: postrestFilter.substring(3),
658-
columnName: columnName,
659-
);
660-
} else if (postrestFilter.startsWith('lt.')) {
661-
return _handleComparison(
662-
operator: 'lt',
663-
value: postrestFilter.substring(3),
664-
columnName: columnName,
665-
);
666-
} else if (postrestFilter.startsWith('gte.')) {
667-
return _handleComparison(
668-
operator: 'gte',
669-
value: postrestFilter.substring(4),
670-
columnName: columnName,
671-
);
672-
} else if (postrestFilter.startsWith('lte.')) {
673-
return _handleComparison(
674-
operator: 'lte',
675-
value: postrestFilter.substring(4),
676-
columnName: columnName,
677-
);
678-
} else if (postrestFilter.startsWith('like.')) {
679-
final value = postrestFilter.substring(5);
680-
final regex = RegExp(value.replaceAll('%', '.*'));
681-
return (row) => regex.hasMatch(row[columnName]);
682-
} else if (postrestFilter == 'is.null') {
683-
return (row) => row[columnName] == null;
684-
} else if (postrestFilter.startsWith('in.')) {
685-
final value = postrestFilter.substring(3);
686-
final values = value.substring(1, value.length - 1).split(',');
687-
return (row) => values.contains(row[columnName].toString());
688-
} else if (postrestFilter.startsWith('cs.')) {
689-
final value = postrestFilter.substring(3);
690-
if (value.startsWith('{') && value.endsWith('}')) {
691-
// Array case
692-
final values = value.substring(1, value.length - 1).split(',');
693-
return (row) => values.every((v) {
694-
final decodedValue = v.startsWith('"') && v.endsWith('"')
695-
? jsonDecode(v)
696-
: v.toString();
697-
return (row[columnName] as List).contains(decodedValue);
698-
});
699-
} else {
700-
throw UnimplementedError(
701-
'JSON and range operators in contains is not yet supported');
702-
}
703-
} else if (postrestFilter.startsWith('containedBy.')) {
704-
final value = postrestFilter.substring(12);
705-
final values = jsonDecode(value);
706-
return (row) =>
707-
values.every((v) => (row[columnName] as List).contains(v));
708-
} else if (postrestFilter.startsWith('overlaps.')) {
709-
final value = postrestFilter.substring(9);
710-
final values = jsonDecode(value);
711-
return (row) =>
712-
(row[columnName] as List).any((element) => values.contains(element));
713-
} else if (postrestFilter.startsWith('fts.')) {
714-
final value = postrestFilter.substring(4);
715-
return (row) => (row[columnName] as String).contains(value);
716-
} else if (postrestFilter.startsWith('match.')) {
717-
final value = jsonDecode(postrestFilter.substring(6));
718-
return (row) {
719-
if (row[columnName] is! Map) return false;
720-
final rowMap = row[columnName] as Map<String, dynamic>;
721-
return value.entries.every((entry) => rowMap[entry.key] == entry.value);
722-
};
723-
} else if (postrestFilter.startsWith('not.')) {
724-
final parts = postrestFilter.split('.');
725-
final operator = parts[1];
726-
final value = parts.sublist(2).join('.');
727-
final filter = _parseFilter(
728-
columnName: columnName,
729-
postrestFilter: '$operator.$value',
730-
targetRow: targetRow,
731-
);
732-
return (row) => !filter(row);
733-
}
734-
return (row) => true;
735-
}
736-
737-
/// Handles comparison operations for date and numeric values.
738-
///
739-
/// This function creates a filter based on the given comparison [operator],
740-
/// [value], and [columnName]. It supports both date and numeric comparisons.
741-
///
742-
/// [operator] can be 'gt', 'lt', 'gte', or 'lte'.
743-
/// [value] is the string representation of the value to compare against.
744-
/// [columnName] is the name of the column to compare in each row.
745-
///
746-
/// Returns a function that takes a row and returns a boolean indicating
747-
/// whether the row matches the comparison criteria.
748-
bool Function(Map<String, dynamic> row) _handleComparison({
749-
required String operator,
750-
required String value,
751-
required String columnName,
752-
}) {
753-
// Check if the value is a valid date
754-
if (DateTime.tryParse(value) != null) {
755-
final dateTime = DateTime.parse(value);
756-
return (row) {
757-
final rowDate = DateTime.tryParse(row[columnName].toString());
758-
if (rowDate == null) return false;
759-
// Perform date comparison based on the operator
760-
switch (operator) {
761-
case 'gt':
762-
return rowDate.isAfter(dateTime);
763-
case 'lt':
764-
return rowDate.isBefore(dateTime);
765-
case 'gte':
766-
return rowDate.isAtSameMomentAs(dateTime) ||
767-
rowDate.isAfter(dateTime);
768-
case 'lte':
769-
return rowDate.isAtSameMomentAs(dateTime) ||
770-
rowDate.isBefore(dateTime);
771-
default:
772-
throw UnimplementedError('Unsupported operator: $operator');
773-
}
774-
};
775-
}
776-
// Check if the value is a valid number
777-
else if (num.tryParse(value) != null) {
778-
final numValue = num.parse(value);
779-
return (row) {
780-
final rowValue = num.tryParse(row[columnName].toString());
781-
if (rowValue == null) return false;
782-
// Perform numeric comparison based on the operator
783-
switch (operator) {
784-
case 'gt':
785-
return rowValue > numValue;
786-
case 'lt':
787-
return rowValue < numValue;
788-
case 'gte':
789-
return rowValue >= numValue;
790-
case 'lte':
791-
return rowValue <= numValue;
792-
default:
793-
throw UnimplementedError('Unsupported operator: $operator');
794-
}
795-
};
796-
}
797-
// Throw an error if the value is neither a date nor a number
798-
else {
799-
throw UnimplementedError('Unsupported value type');
800-
}
801-
}
802-
803622
StreamedResponse _createResponse(dynamic data,
804623
{int statusCode = 200,
805624
required BaseRequest request,
@@ -816,22 +635,4 @@ class MockSupabaseHttpClient extends BaseClient {
816635
request: request,
817636
);
818637
}
819-
820-
StreamedResponse _handleRpc(
821-
String functionName, BaseRequest request, dynamic body) {
822-
if (!_rpcFunctions.containsKey(functionName)) {
823-
return _createResponse({'error': 'RPC function not found'},
824-
statusCode: 404, request: request);
825-
}
826-
827-
final function = _rpcFunctions[functionName]!;
828-
829-
try {
830-
final result = function(body, _database);
831-
return _createResponse(result, request: request);
832-
} catch (e) {
833-
return _createResponse({'error': 'RPC function execution failed: $e'},
834-
statusCode: 500, request: request);
835-
}
836-
}
837638
}

0 commit comments

Comments
 (0)