refactor: adding support for attribute binding pass 144 fail 2
This commit is contained in:
parent
103b6e2553
commit
40eadbe408
7 changed files with 785 additions and 2 deletions
|
@ -7,9 +7,9 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
library platform_container;
|
||||
|
||||
export 'src/attributes.dart';
|
||||
export 'src/container.dart';
|
||||
export 'src/contextual_binding_builder.dart';
|
||||
export 'src/empty/empty.dart';
|
||||
export 'src/static/static.dart';
|
||||
export 'src/exception.dart';
|
||||
|
|
106
incubation/container/container/lib/src/attribute_binding.dart
Normal file
106
incubation/container/container/lib/src/attribute_binding.dart
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import 'attributes.dart';
|
||||
import 'container.dart';
|
||||
import 'reflector.dart';
|
||||
|
||||
/// Extension methods for attribute-based binding support
|
||||
extension AttributeBindingExtension on Container {
|
||||
/// Register all attribute-based bindings for a type
|
||||
void registerAttributeBindings(Type type) {
|
||||
var annotations = reflector.getAnnotations(type);
|
||||
for (var annotation in annotations) {
|
||||
var value = annotation.reflectee;
|
||||
if (value is Injectable) {
|
||||
// Register the binding
|
||||
if (value.bindTo != null) {
|
||||
bind(value.bindTo!).to(type);
|
||||
}
|
||||
|
||||
// Apply tags
|
||||
if (value.tags.isNotEmpty) {
|
||||
tag([type], value.tags.join(','));
|
||||
}
|
||||
|
||||
// Make it a singleton if requested
|
||||
if (value.singleton) {
|
||||
singleton(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve constructor parameters using attribute-based injection
|
||||
List<dynamic> resolveConstructorParameters(
|
||||
Type type, String constructorName, List<ReflectedParameter> parameters) {
|
||||
var result = <dynamic>[];
|
||||
|
||||
for (var param in parameters) {
|
||||
var annotations =
|
||||
reflector.getParameterAnnotations(type, constructorName, param.name);
|
||||
|
||||
// Find injection annotation
|
||||
ReflectedInstance? injectAnnotation;
|
||||
try {
|
||||
injectAnnotation = annotations.firstWhere(
|
||||
(a) => a.reflectee is Inject || a.reflectee is InjectTagged);
|
||||
} catch (_) {
|
||||
try {
|
||||
injectAnnotation =
|
||||
annotations.firstWhere((a) => a.reflectee is InjectAll);
|
||||
} catch (_) {
|
||||
// No injection annotation found
|
||||
}
|
||||
}
|
||||
|
||||
if (injectAnnotation != null) {
|
||||
var value = injectAnnotation.reflectee;
|
||||
if (value is Inject) {
|
||||
// Inject specific implementation with config
|
||||
result.add(
|
||||
withParameters(value.config, () => make(value.implementation)));
|
||||
} else if (value is InjectTagged) {
|
||||
// Inject tagged implementation
|
||||
var tagged = this.tagged(value.tag);
|
||||
if (tagged.isEmpty) {
|
||||
throw Exception('No implementations found for tag: ${value.tag}');
|
||||
}
|
||||
result.add(tagged.first);
|
||||
} else if (value is InjectAll) {
|
||||
// Inject all implementations
|
||||
if (value.tag != null) {
|
||||
result.add(tagged(value.tag!).toList());
|
||||
} else {
|
||||
result.add(makeAll(param.type.reflectedType));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No injection annotation, use default resolution
|
||||
result.add(make(param.type.reflectedType));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Make all instances of a type
|
||||
List<dynamic> makeAll(Type type) {
|
||||
var reflectedType = reflector.reflectType(type);
|
||||
if (reflectedType == null) {
|
||||
throw Exception('Type not found: $type');
|
||||
}
|
||||
|
||||
return reflector
|
||||
.getAnnotations(type)
|
||||
.where((a) => a.reflectee is Injectable)
|
||||
.map((a) => make(type))
|
||||
.toList();
|
||||
}
|
||||
}
|
58
incubation/container/container/lib/src/attributes.dart
Normal file
58
incubation/container/container/lib/src/attributes.dart
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* This file is part of the Protevus Platform.
|
||||
*
|
||||
* (C) Protevus <developers@protevus.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/// Base class for all container binding attributes
|
||||
abstract class BindingAttribute {
|
||||
const BindingAttribute();
|
||||
}
|
||||
|
||||
/// Marks a class as injectable and optionally specifies how it should be bound
|
||||
class Injectable extends BindingAttribute {
|
||||
/// The type to bind this implementation to (usually an interface)
|
||||
final Type? bindTo;
|
||||
|
||||
/// Whether this should be bound as a singleton
|
||||
final bool singleton;
|
||||
|
||||
/// Tags that can be used to identify this implementation
|
||||
final List<String> tags;
|
||||
|
||||
const Injectable({
|
||||
this.bindTo,
|
||||
this.singleton = false,
|
||||
this.tags = const [],
|
||||
});
|
||||
}
|
||||
|
||||
/// Marks a parameter as requiring a specific implementation
|
||||
class Inject extends BindingAttribute {
|
||||
/// The implementation type to inject
|
||||
final Type implementation;
|
||||
|
||||
/// Configuration parameters for the implementation
|
||||
final Map<String, dynamic> config;
|
||||
|
||||
const Inject(this.implementation, {this.config = const {}});
|
||||
}
|
||||
|
||||
/// Marks a parameter as requiring a tagged implementation
|
||||
class InjectTagged extends BindingAttribute {
|
||||
/// The tag to use when resolving the implementation
|
||||
final String tag;
|
||||
|
||||
const InjectTagged(this.tag);
|
||||
}
|
||||
|
||||
/// Marks a parameter as requiring all implementations of a type
|
||||
class InjectAll extends BindingAttribute {
|
||||
/// Optional tag to filter implementations
|
||||
final String? tag;
|
||||
|
||||
const InjectAll({this.tag});
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'attributes.dart';
|
||||
import 'exception.dart';
|
||||
import 'reflector.dart';
|
||||
import 'contextual_binding_builder.dart';
|
||||
|
@ -420,6 +421,11 @@ class Container {
|
|||
return '' as T;
|
||||
}
|
||||
|
||||
// Handle List<T> specially
|
||||
if (t2.toString().startsWith('List<')) {
|
||||
return [] as T;
|
||||
}
|
||||
|
||||
// Use reflection to create instance
|
||||
var reflectedType = reflector.reflectType(t2);
|
||||
if (reflectedType == null) {
|
||||
|
@ -1132,4 +1138,127 @@ class Container {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind an abstract type to a concrete implementation
|
||||
ContextualBindingBuilder bind(Type abstract) {
|
||||
return ContextualBindingBuilder(this, [abstract]);
|
||||
}
|
||||
|
||||
/// Register a singleton type and initialize it
|
||||
void singleton(Type type) {
|
||||
if (!_singletons.containsKey(type)) {
|
||||
var instance = make(type);
|
||||
_singletons[type] = instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to bind a concrete type to an abstract type
|
||||
void bindTo(Type abstract, Type concrete) {
|
||||
bind(abstract).to(concrete);
|
||||
}
|
||||
|
||||
/// Register all attribute-based bindings for a type
|
||||
void registerAttributeBindings(Type type) {
|
||||
var annotations = reflector.getAnnotations(type);
|
||||
for (var annotation in annotations) {
|
||||
var value = annotation.reflectee;
|
||||
if (value is Injectable) {
|
||||
// Register the binding
|
||||
if (value.bindTo != null) {
|
||||
bind(value.bindTo!).to(type);
|
||||
|
||||
// Apply tags to both the concrete type and the abstract type
|
||||
if (value.tags.isNotEmpty) {
|
||||
for (var tag in value.tags) {
|
||||
_tags[tag] ??= [];
|
||||
_tags[tag]!.add(type);
|
||||
_tags[tag]!.add(value.bindTo!);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Apply tags to just the concrete type
|
||||
if (value.tags.isNotEmpty) {
|
||||
for (var tag in value.tags) {
|
||||
_tags[tag] ??= [];
|
||||
_tags[tag]!.add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make it a singleton if requested
|
||||
if (value.singleton) {
|
||||
singleton(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve constructor parameters using attribute-based injection
|
||||
List<dynamic> resolveConstructorParameters(
|
||||
Type type, String constructorName, List<ReflectedParameter> parameters) {
|
||||
var result = <dynamic>[];
|
||||
|
||||
for (var param in parameters) {
|
||||
var annotations =
|
||||
reflector.getParameterAnnotations(type, constructorName, param.name);
|
||||
|
||||
// Find injection annotation
|
||||
ReflectedInstance? injectAnnotation;
|
||||
try {
|
||||
injectAnnotation = annotations.firstWhere(
|
||||
(a) => a.reflectee is Inject || a.reflectee is InjectTagged);
|
||||
} catch (_) {
|
||||
try {
|
||||
injectAnnotation =
|
||||
annotations.firstWhere((a) => a.reflectee is InjectAll);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (injectAnnotation != null) {
|
||||
var value = injectAnnotation.reflectee;
|
||||
if (value is Inject) {
|
||||
// Inject specific implementation with config
|
||||
result.add(
|
||||
withParameters(value.config, () => make(value.implementation)));
|
||||
} else if (value is InjectTagged) {
|
||||
// Inject tagged implementation
|
||||
var tagged = this.tagged(value.tag);
|
||||
if (tagged.isEmpty) {
|
||||
throw BindingResolutionException(
|
||||
'No implementations found for tag: ${value.tag}');
|
||||
}
|
||||
result.add(tagged.first);
|
||||
} else if (value is InjectAll) {
|
||||
// Inject all implementations
|
||||
if (value.tag != null) {
|
||||
result.add(tagged(value.tag!).toList());
|
||||
} else {
|
||||
result.add(makeAll(param.type.reflectedType));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No injection annotation, use default resolution
|
||||
result.add(make(param.type.reflectedType));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Make all instances of a type
|
||||
List<dynamic> makeAll(Type type) {
|
||||
var result = <dynamic>[];
|
||||
|
||||
// Get all tagged implementations
|
||||
var allTags = _tags.entries
|
||||
.where((entry) => entry.value.any((t) => t == type))
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
|
||||
for (var tag in allTags) {
|
||||
result.addAll(tagged(tag));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,14 @@ class ContextualBindingBuilder {
|
|||
ContextualImplementationBuilder needs<T>() {
|
||||
return ContextualImplementationBuilder(container, concrete, T);
|
||||
}
|
||||
|
||||
/// Bind directly to a concrete implementation
|
||||
void to(Type implementation) {
|
||||
for (var concreteType in concrete) {
|
||||
container.addContextualBinding(
|
||||
concreteType, concreteType, implementation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder class for defining the implementation for a contextual binding.
|
||||
|
@ -62,6 +70,13 @@ class ContextualImplementationBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Bind to a concrete implementation type
|
||||
void to(Type implementation) {
|
||||
for (var concreteType in concrete) {
|
||||
container.addContextualBinding(concreteType, abstract, implementation);
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify a factory function that should be used to create the implementation
|
||||
void giveFactory(dynamic Function(Container container) factory) {
|
||||
for (var concreteType in concrete) {
|
||||
|
|
|
@ -59,6 +59,25 @@ abstract class Reflector {
|
|||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) {
|
||||
throw UnsupportedError('`findInstanceMethod` requires `dart:mirrors`.');
|
||||
}
|
||||
|
||||
/// Get annotations for a type.
|
||||
///
|
||||
/// This method returns a list of reflected instances representing the annotations
|
||||
/// applied to the given type.
|
||||
List<ReflectedInstance> getAnnotations(Type type) {
|
||||
throw UnsupportedError('`getAnnotations` requires `dart:mirrors`.');
|
||||
}
|
||||
|
||||
/// Get annotations for a parameter.
|
||||
///
|
||||
/// This method returns a list of reflected instances representing the annotations
|
||||
/// applied to the parameter with the given name in the specified constructor of
|
||||
/// the given type.
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) {
|
||||
throw UnsupportedError(
|
||||
'`getParameterAnnotations` requires `dart:mirrors`.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a reflected instance of an object.
|
||||
|
|
456
incubation/container/container/test/attribute_binding_test.dart
Normal file
456
incubation/container/container/test/attribute_binding_test.dart
Normal file
|
@ -0,0 +1,456 @@
|
|||
import 'package:platformed_container/container.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
abstract class Logger {
|
||||
void log(String message);
|
||||
}
|
||||
|
||||
@Injectable(bindTo: Logger, tags: ['console'])
|
||||
class ConsoleLogger implements Logger {
|
||||
final String level;
|
||||
|
||||
ConsoleLogger({this.level = 'info'});
|
||||
|
||||
@override
|
||||
void log(String message) => print('Console($level): $message');
|
||||
}
|
||||
|
||||
@Injectable(bindTo: Logger, tags: ['file'])
|
||||
class FileLogger implements Logger {
|
||||
final String filename;
|
||||
|
||||
FileLogger({required this.filename});
|
||||
|
||||
@override
|
||||
void log(String message) => print('File($filename): $message');
|
||||
}
|
||||
|
||||
class Service {
|
||||
final Logger consoleLogger;
|
||||
final Logger fileLogger;
|
||||
final List<Logger> allLoggers;
|
||||
|
||||
Service(
|
||||
@InjectTagged('console') this.consoleLogger,
|
||||
@Inject(FileLogger, config: {'filename': 'app.log'}) this.fileLogger,
|
||||
@InjectAll() this.allLoggers,
|
||||
);
|
||||
|
||||
void logMessage(String message) {
|
||||
for (var logger in allLoggers) {
|
||||
logger.log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable(singleton: true)
|
||||
class SingletonService {
|
||||
static int instanceCount = 0;
|
||||
final int instanceNumber;
|
||||
|
||||
SingletonService() : instanceNumber = ++instanceCount;
|
||||
}
|
||||
|
||||
class MockReflector extends Reflector {
|
||||
@override
|
||||
String? getName(Symbol symbol) => null;
|
||||
|
||||
@override
|
||||
ReflectedClass? reflectClass(Type clazz) {
|
||||
if (clazz == Service) {
|
||||
return MockReflectedClass(
|
||||
'Service',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('consoleLogger', Logger, true, false),
|
||||
MockParameter('fileLogger', Logger, true, false),
|
||||
MockParameter('allLoggers', List<Logger>, true, false),
|
||||
])
|
||||
],
|
||||
Service);
|
||||
}
|
||||
if (clazz == SingletonService) {
|
||||
return MockReflectedClass('SingletonService', [], [],
|
||||
[MockConstructor('', [])], SingletonService);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedType? reflectType(Type type) {
|
||||
if (type == List<Logger>) {
|
||||
return MockReflectedClass('List<Logger>', [], [], [], List<Logger>);
|
||||
}
|
||||
if (type == Service) {
|
||||
return MockReflectedClass(
|
||||
'Service',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('consoleLogger', Logger, true, false),
|
||||
MockParameter('fileLogger', Logger, true, false),
|
||||
MockParameter('allLoggers', List<Logger>, true, false),
|
||||
])
|
||||
],
|
||||
Service);
|
||||
}
|
||||
if (type == ConsoleLogger) {
|
||||
return MockReflectedClass(
|
||||
'ConsoleLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('level', String, false, true),
|
||||
])
|
||||
],
|
||||
ConsoleLogger);
|
||||
}
|
||||
if (type == FileLogger) {
|
||||
return MockReflectedClass(
|
||||
'FileLogger',
|
||||
[],
|
||||
[],
|
||||
[
|
||||
MockConstructor('', [
|
||||
MockParameter('filename', String, true, true),
|
||||
])
|
||||
],
|
||||
FileLogger);
|
||||
}
|
||||
if (type == Logger) {
|
||||
return MockReflectedClass(
|
||||
'Logger', [], [], [MockConstructor('', [])], Logger);
|
||||
}
|
||||
if (type == SingletonService) {
|
||||
return MockReflectedClass('SingletonService', [], [],
|
||||
[MockConstructor('', [])], SingletonService);
|
||||
}
|
||||
if (type == String) {
|
||||
return MockReflectedClass(
|
||||
'String', [], [], [MockConstructor('', [])], String);
|
||||
}
|
||||
return 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) => null;
|
||||
|
||||
@override
|
||||
ReflectedFunction? findInstanceMethod(Object instance, String methodName) =>
|
||||
null;
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getAnnotations(Type type) {
|
||||
if (type == ConsoleLogger) {
|
||||
return [
|
||||
MockReflectedInstance(Injectable(bindTo: Logger, tags: ['console']))
|
||||
];
|
||||
}
|
||||
if (type == FileLogger) {
|
||||
return [
|
||||
MockReflectedInstance(Injectable(bindTo: Logger, tags: ['file']))
|
||||
];
|
||||
}
|
||||
if (type == SingletonService) {
|
||||
return [MockReflectedInstance(Injectable(singleton: true))];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> getParameterAnnotations(
|
||||
Type type, String constructorName, String parameterName) {
|
||||
if (type == Service) {
|
||||
if (parameterName == 'consoleLogger') {
|
||||
return [MockReflectedInstance(InjectTagged('console'))];
|
||||
}
|
||||
if (parameterName == 'fileLogger') {
|
||||
return [
|
||||
MockReflectedInstance(
|
||||
Inject(FileLogger, config: {'filename': 'app.log'}))
|
||||
];
|
||||
}
|
||||
if (parameterName == 'allLoggers') {
|
||||
return [MockReflectedInstance(InjectAll())];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class MockReflectedClass extends ReflectedType implements ReflectedClass {
|
||||
@override
|
||||
final List<ReflectedInstance> annotations;
|
||||
@override
|
||||
final List<ReflectedFunction> constructors;
|
||||
@override
|
||||
final List<ReflectedDeclaration> declarations;
|
||||
|
||||
MockReflectedClass(
|
||||
String name,
|
||||
List<ReflectedTypeParameter> typeParameters,
|
||||
this.annotations,
|
||||
this.constructors,
|
||||
Type reflectedType,
|
||||
) : declarations = [],
|
||||
super(reflectedType.toString(), typeParameters, reflectedType);
|
||||
|
||||
void _validateParameters(List<ReflectedParameter> parameters,
|
||||
List positionalArguments, Map<String, dynamic> namedArguments) {
|
||||
var paramIndex = 0;
|
||||
for (var param in parameters) {
|
||||
if (param.isNamed) {
|
||||
if (param.isRequired && !namedArguments.containsKey(param.name)) {
|
||||
throw BindingResolutionException(
|
||||
'Required parameter ${param.name} is missing');
|
||||
}
|
||||
} else {
|
||||
if (param.isRequired && paramIndex >= positionalArguments.length) {
|
||||
throw BindingResolutionException(
|
||||
'Required parameter ${param.name} is missing');
|
||||
}
|
||||
paramIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) {
|
||||
// Handle List<Logger> specially
|
||||
if (reflectedType == List<Logger>) {
|
||||
var loggers = <Logger>[];
|
||||
for (var arg in positionalArguments) {
|
||||
if (arg is Logger) {
|
||||
loggers.add(arg);
|
||||
}
|
||||
}
|
||||
return MockReflectedInstance(loggers);
|
||||
}
|
||||
|
||||
// Find constructor
|
||||
var constructor = constructors.firstWhere((c) => c.name == constructorName,
|
||||
orElse: () => constructors.first);
|
||||
|
||||
// Validate parameters
|
||||
_validateParameters(
|
||||
constructor.parameters, positionalArguments, namedArguments);
|
||||
|
||||
if (reflectedType == Service) {
|
||||
var loggers = <Logger>[];
|
||||
if (positionalArguments[2] is List) {
|
||||
for (var item in positionalArguments[2] as List) {
|
||||
if (item is Logger) {
|
||||
loggers.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return MockReflectedInstance(Service(
|
||||
positionalArguments[0] as Logger,
|
||||
positionalArguments[1] as Logger,
|
||||
loggers,
|
||||
));
|
||||
}
|
||||
if (reflectedType == ConsoleLogger) {
|
||||
return MockReflectedInstance(
|
||||
ConsoleLogger(level: namedArguments['level'] as String? ?? 'info'));
|
||||
}
|
||||
if (reflectedType == FileLogger) {
|
||||
return MockReflectedInstance(
|
||||
FileLogger(filename: namedArguments['filename'] as String));
|
||||
}
|
||||
if (reflectedType == SingletonService) {
|
||||
return MockReflectedInstance(SingletonService());
|
||||
}
|
||||
if (reflectedType == Logger) {
|
||||
throw BindingResolutionException(
|
||||
'No implementation was provided for Logger');
|
||||
}
|
||||
throw UnsupportedError('Unknown type: $reflectedType');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
if (reflectedType == FileLogger && other?.reflectedType == Logger) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MockConstructor implements ReflectedFunction {
|
||||
final String constructorName;
|
||||
final List<ReflectedParameter> constructorParameters;
|
||||
|
||||
MockConstructor(this.constructorName, this.constructorParameters);
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
bool get isGetter => false;
|
||||
|
||||
@override
|
||||
bool get isSetter => false;
|
||||
|
||||
@override
|
||||
String get name => constructorName;
|
||||
|
||||
@override
|
||||
List<ReflectedParameter> get parameters => constructorParameters;
|
||||
|
||||
@override
|
||||
ReflectedType? get returnType => null;
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError();
|
||||
}
|
||||
|
||||
class MockParameter implements ReflectedParameter {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final bool isRequired;
|
||||
@override
|
||||
final bool isNamed;
|
||||
final Type paramType;
|
||||
final bool isVariadic;
|
||||
|
||||
MockParameter(this.name, this.paramType, this.isRequired, this.isNamed,
|
||||
{this.isVariadic = false});
|
||||
|
||||
@override
|
||||
List<ReflectedInstance> get annotations => [];
|
||||
|
||||
@override
|
||||
ReflectedType get type => MockReflectedType(paramType);
|
||||
}
|
||||
|
||||
class MockReflectedType implements ReflectedType {
|
||||
@override
|
||||
final String name;
|
||||
@override
|
||||
final Type reflectedType;
|
||||
|
||||
MockReflectedType(this.reflectedType) : name = reflectedType.toString();
|
||||
|
||||
@override
|
||||
List<ReflectedTypeParameter> get typeParameters => [];
|
||||
|
||||
@override
|
||||
bool isAssignableTo(ReflectedType? other) {
|
||||
// Handle primitive types
|
||||
if (reflectedType == other?.reflectedType) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
ReflectedInstance newInstance(
|
||||
String constructorName, List positionalArguments,
|
||||
[Map<String, dynamic> namedArguments = const {},
|
||||
List<Type> typeArguments = const []]) =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
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());
|
||||
});
|
||||
|
||||
group('Attribute Binding Tests', () {
|
||||
setUp(() {
|
||||
// Reset instance count
|
||||
SingletonService.instanceCount = 0;
|
||||
|
||||
// Register implementations
|
||||
container.registerAttributeBindings(ConsoleLogger);
|
||||
container.registerAttributeBindings(FileLogger);
|
||||
container.registerAttributeBindings(SingletonService);
|
||||
|
||||
// Set ConsoleLogger as default implementation for Logger
|
||||
container.bind(Logger).to(ConsoleLogger);
|
||||
});
|
||||
|
||||
test('can bind implementation using @Injectable', () {
|
||||
var logger = container.make<Logger>();
|
||||
expect(logger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('can bind implementation using @Injectable with tags', () {
|
||||
var consoleLogger = container.tagged('console').first;
|
||||
expect(consoleLogger, isA<ConsoleLogger>());
|
||||
|
||||
var fileLogger = container.tagged('file').first;
|
||||
expect(fileLogger, isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('can inject tagged implementation using @InjectTagged', () {
|
||||
var service = container.make<Service>();
|
||||
expect(service.consoleLogger, isA<ConsoleLogger>());
|
||||
});
|
||||
|
||||
test('can inject configured implementation using @Inject', () {
|
||||
var service = container.make<Service>();
|
||||
expect(service.fileLogger, isA<FileLogger>());
|
||||
expect((service.fileLogger as FileLogger).filename, equals('app.log'));
|
||||
});
|
||||
|
||||
test('can inject all implementations using @InjectAll', () {
|
||||
var service = container.make<Service>();
|
||||
expect(service.allLoggers, hasLength(2));
|
||||
expect(service.allLoggers[0], isA<ConsoleLogger>());
|
||||
expect(service.allLoggers[1], isA<FileLogger>());
|
||||
});
|
||||
|
||||
test('can bind singleton using @Injectable', () {
|
||||
var first = container.make<SingletonService>();
|
||||
var second = container.make<SingletonService>();
|
||||
expect(first.instanceNumber, equals(1));
|
||||
expect(second.instanceNumber, equals(1));
|
||||
expect(identical(first, second), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue