@@ -2,19 +2,26 @@ import 'dart:convert';
2
2
3
3
import 'package:http/http.dart' ;
4
4
5
+ import 'handlers/rpc_handler.dart' ;
6
+ import 'utils/filter_parser.dart' ;
7
+
5
8
class MockSupabaseHttpClient extends BaseClient {
6
9
final Map <String , List <Map <String , dynamic >>> _database = {};
7
10
final Map <
8
11
String ,
9
12
dynamic Function (Map <String , dynamic >? params,
10
13
Map <String , List <Map <String , dynamic >>> tables)> _rpcFunctions = {};
11
14
12
- MockSupabaseHttpClient ();
15
+ late final RpcHandler _rpcHandler;
16
+
17
+ MockSupabaseHttpClient () {
18
+ _rpcHandler = RpcHandler (_rpcFunctions, _database);
19
+ }
13
20
14
21
void reset () {
15
22
// Clear the mock database and RPC functions
16
23
_database.clear ();
17
- _rpcFunctions. clear ();
24
+ _rpcHandler. reset ();
18
25
}
19
26
20
27
/// Registers a RPC function that can be called using the `rpc` method on a `Postgrest` client.
@@ -122,7 +129,7 @@ class MockSupabaseHttpClient extends BaseClient {
122
129
// Handle RPC call
123
130
if (pathSegments.length > restIndex + 2 ) {
124
131
final functionName = pathSegments[restIndex + 2 ];
125
- return _handleRpc (functionName, request, body);
132
+ return _rpcHandler. handleRpc (functionName, request, body);
126
133
} else {
127
134
return _createResponse ({'error' : 'RPC function name not provided' },
128
135
statusCode: 400 , request: request);
@@ -250,23 +257,12 @@ class MockSupabaseHttpClient extends BaseClient {
250
257
}
251
258
}
252
259
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.
263
260
bool _matchesFilters ({
264
261
required Map <String , dynamic > row,
265
262
required Map <String , String > filters,
266
263
}) {
267
- // Check if an item matches the provided filters
268
264
for (var columnName in filters.keys) {
269
- final filter = _parseFilter (
265
+ final filter = FilterParser . parseFilter (
270
266
columnName: columnName,
271
267
postrestFilter: filters[columnName]! ,
272
268
targetRow: row,
@@ -355,7 +351,7 @@ class MockSupabaseHttpClient extends BaseClient {
355
351
final parts = key.split ('.' );
356
352
final referencedTableName = parts[0 ];
357
353
final referencedColumnName = parts[1 ];
358
- final filter = _parseFilter (
354
+ final filter = FilterParser . parseFilter (
359
355
columnName: referencedColumnName,
360
356
postrestFilter: value,
361
357
targetRow: returningRows.first[referencedTableName] is List
@@ -390,7 +386,7 @@ class MockSupabaseHttpClient extends BaseClient {
390
386
// referenced table filtering with !inner
391
387
} else {
392
388
// Regular filtering on the top level table
393
- final filter = _parseFilter (
389
+ final filter = FilterParser . parseFilter (
394
390
columnName: key,
395
391
postrestFilter: value,
396
392
targetRow: returningRows.first,
@@ -580,7 +576,7 @@ class MockSupabaseHttpClient extends BaseClient {
580
576
key != 'order' &&
581
577
key != 'limit' &&
582
578
key != 'range' ) {
583
- final filter = _parseFilter (
579
+ final filter = FilterParser . parseFilter (
584
580
columnName: key,
585
581
postrestFilter: value,
586
582
targetRow: returningRows.isNotEmpty ? returningRows.first : {},
@@ -623,183 +619,6 @@ class MockSupabaseHttpClient extends BaseClient {
623
619
);
624
620
}
625
621
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
-
803
622
StreamedResponse _createResponse (dynamic data,
804
623
{int statusCode = 200 ,
805
624
required BaseRequest request,
@@ -816,22 +635,4 @@ class MockSupabaseHttpClient extends BaseClient {
816
635
request: request,
817
636
);
818
637
}
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
- }
837
638
}
0 commit comments