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.
|
/// Check if we're in danger of a circular dependency.
|
||||||
void _checkCircularDependency(Type type) {
|
void _checkCircularDependency(Type type) {
|
||||||
if (_buildStack.contains(type)) {
|
if (_buildStack.contains(type)) {
|
||||||
|
|
|
@ -43,6 +43,22 @@ abstract class Reflector {
|
||||||
ReflectedType reflectFutureOf(Type type) {
|
ReflectedType reflectFutureOf(Type type) {
|
||||||
throw UnsupportedError('`reflectFutureOf` requires `dart:mirrors`.');
|
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.
|
/// Represents a reflected instance of an object.
|
||||||
|
@ -241,6 +257,9 @@ abstract class ReflectedFunction {
|
||||||
other.isGetter == isGetter &&
|
other.isGetter == isGetter &&
|
||||||
other.isSetter == isSetter;
|
other.isSetter == isSetter;
|
||||||
|
|
||||||
|
/// Invoke this function with the given invocation.
|
||||||
|
///
|
||||||
|
/// This method is used to support dynamic method invocation.
|
||||||
ReflectedInstance invoke(Invocation 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