refactor: adding support for class@method syntax
This commit is contained in:
parent
f4e567c046
commit
a07bd666c2
3 changed files with 205 additions and 0 deletions
|
@ -1033,6 +1033,41 @@ class Container {
|
|||
}
|
||||
}
|
||||
|
||||
/// Call a method on a resolved instance using Class@method syntax.
|
||||
///
|
||||
/// This allows you to resolve and call a method in one step:
|
||||
/// ```dart
|
||||
/// container.call('Logger@log', ['Hello world']);
|
||||
/// ```
|
||||
dynamic call(String target, [List<dynamic> parameters = const []]) {
|
||||
var parts = target.split('@');
|
||||
if (parts.length != 2) {
|
||||
throw ArgumentError('Invalid Class@method syntax: $target');
|
||||
}
|
||||
|
||||
var className = parts[0];
|
||||
var methodName = parts[1];
|
||||
|
||||
// Find the type by name
|
||||
var type = reflector.findTypeByName(className);
|
||||
if (type == null) {
|
||||
throw ArgumentError('Class not found: $className');
|
||||
}
|
||||
|
||||
// Resolve the instance
|
||||
var instance = make(type);
|
||||
|
||||
// Find and call the method
|
||||
var method = reflector.findInstanceMethod(instance, methodName);
|
||||
if (method == null) {
|
||||
throw ArgumentError('Method not found: $methodName on $className');
|
||||
}
|
||||
|
||||
return method
|
||||
.invoke(Invocation.method(Symbol(methodName), parameters))
|
||||
.reflectee;
|
||||
}
|
||||
|
||||
/// Check if we're in danger of a circular dependency.
|
||||
void _checkCircularDependency(Type type) {
|
||||
if (_buildStack.contains(type)) {
|
||||
|
|
|
@ -43,6 +43,22 @@ abstract class Reflector {
|
|||
ReflectedType reflectFutureOf(Type type) {
|
||||
throw UnsupportedError('`reflectFutureOf` requires `dart:mirrors`.');
|
||||
}
|
||||
|
||||
/// Find a type by its name.
|
||||
///
|
||||
/// This method is used to support Class@method syntax by finding a type
|
||||
/// from its string name.
|
||||
Type? findTypeByName(String name) {
|
||||
throw UnsupportedError('`findTypeByName` requires `dart:mirrors`.');
|
||||
}
|
||||
|
||||
/// Find an instance method by its name.
|
||||
///
|
||||
/// This method is used to support Class@method syntax by finding a method
|
||||
/// on an instance from its string name.
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
|
||||
throw UnsupportedError('`findInstanceMethod` requires `dart:mirrors`.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a reflected instance of an object.
|
||||
|
@ -241,6 +257,9 @@ abstract class ReflectedFunction {
|
|||
other.isGetter == isGetter &&
|
||||
other.isSetter == isSetter;
|
||||
|
||||
/// Invoke this function with the given invocation.
|
||||
///
|
||||
/// This method is used to support dynamic method invocation.
|
||||
ReflectedInstance invoke(Invocation invocation);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
import 'package:platformed_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class Logger {
|
||||
String log(String message) => message;
|
||||
void debug(String message, {int? level}) {}
|
||||
int count(List<String> items) => items.length;
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) => null;
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) => null;
|
||||
|
||||
@override
|
||||
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? reflectFunction(Function function) => null;
|
||||
|
||||
@override
|
||||
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Type? findTypeByName(String name) {
|
||||
if (name == 'Logger') return Logger;
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
|
||||
if (instance is Logger) {
|
||||
switch (methodName) {
|
||||
case 'log':
|
||||
return MockMethod('log', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
return MockReflectedInstance(instance.log(args[0] as String));
|
||||
});
|
||||
case 'debug':
|
||||
return MockMethod('debug', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
instance.debug(args[0] as String);
|
||||
return MockReflectedInstance(null);
|
||||
});
|
||||
case 'count':
|
||||
return MockMethod('count', (invocation) {
|
||||
var args = invocation.positionalArguments;
|
||||
return MockReflectedInstance(
|
||||
instance.count(args[0] as List<String>));
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockMethod implements ReflectedFunction {
|
||||
final String methodName;
|
||||
final ReflectedInstance Function(Invocation) handler;
|
||||
|
||||
MockMethod(this.methodName, this.handler);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => methodName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => [];
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => handler(invocation);
|
||||
}
|
||||
|
||||
class MockReflectedInstance implements ReflectedInstance {
|
||||
final dynamic value;
|
||||
|
||||
MockReflectedInstance(this.value);
|
||||
|
||||
@override
|
||||
ReflectedClass get clazz => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ReflectedInstance getField(String name) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
dynamic get reflectee => value;
|
||||
|
||||
@override
|
||||
ReflectedType get type => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void main() {
|
||||
late Container container;
|
||||
|
||||
setUp(() {
|
||||
container = Container(MockReflector());
|
||||
container.registerSingleton(Logger());
|
||||
});
|
||||
|
||||
group('Class@method syntax', () {
|
||||
test('can call method with return value', () {
|
||||
var result = container.call('Logger@log', ['Hello world']);
|
||||
expect(result, equals('Hello world'));
|
||||
});
|
||||
|
||||
test('can call void method', () {
|
||||
expect(() => container.call('Logger@debug', ['Debug message']),
|
||||
returnsNormally);
|
||||
});
|
||||
|
||||
test('can call method with list parameter', () {
|
||||
var result = container.call('Logger@count', [
|
||||
['one', 'two', 'three']
|
||||
]);
|
||||
expect(result, equals(3));
|
||||
});
|
||||
|
||||
test('throws on invalid syntax', () {
|
||||
expect(() => container.call('Logger'), throwsArgumentError);
|
||||
expect(() => container.call('Logger@'), throwsArgumentError);
|
||||
expect(() => container.call('@log'), throwsArgumentError);
|
||||
});
|
||||
|
||||
test('throws on unknown class', () {
|
||||
expect(() => container.call('Unknown@method'), throwsArgumentError);
|
||||
});
|
||||
|
||||
test('throws on unknown method', () {
|
||||
expect(() => container.call('Logger@unknown'), throwsArgumentError);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue