@@ -2,19 +2,26 @@ import 'dart:convert';
22
33import 'package:http/http.dart' ;
44
5+ import 'handlers/rpc_handler.dart' ;
6+ import 'utils/filter_parser.dart' ;
7+
58class 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