Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#39): add a metadata system to specialize controllers and routes #53

Merged
merged 13 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: unify contexts implementation, allow to send message to ws …
…client from providers, inject parent providers in child
  • Loading branch information
francescovallone committed Jul 15, 2024
commit f7b8e1021a225258f680ca5f24b3a9d5c96c5db1
39 changes: 30 additions & 9 deletions packages/serinus/bin/serinus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ class HomeAController extends Controller {

Future<Response> _handlePostRequest(RequestContext context) async {
print(context.body.formData?.fields);
print(context.canUse<TestProviderThree>());
print(context.canUse<TestWsProvider>());
context.use<TestWsProvider>().send('Hello from controller');
return Response.text('Hello world from a ${context.params}');
}
}
Expand All @@ -172,21 +175,21 @@ class TestWsProvider extends WebSocketGateway
@override
Future<void> onMessage(dynamic message, WebSocketContext context) async {
if (message == 'broadcast') {
context.send('Hello from server', broadcast: true);
context.send('Hello from server');
}
print(context.query);
context.send('Message received: $message');
print('Message received: $message');
}

@override
Future<void> onClientConnect() async {
print('Client connected');
Future<void> onClientConnect(String clientId) async {
print('Client $clientId connected');
}

@override
Future<void> onClientDisconnect() async {
print('Client disconnected');
Future<void> onClientDisconnect(String clientId) async {
print('Client $clientId disconnected');
}
}

Expand All @@ -197,19 +200,19 @@ class TestWs2Provider extends WebSocketGateway
@override
Future<void> onMessage(dynamic message, WebSocketContext context) async {
if (message == 'broadcast') {
context.send('Hello from server', broadcast: true);
context.send('Hello from server');
}
context.send('Message received: $message');
print('Message received: $message');
}

@override
Future<void> onClientConnect() async {
Future<void> onClientConnect(String clientId) async {
print('Client connected');
}

@override
Future<void> onClientDisconnect() async {
Future<void> onClientDisconnect(String clientId) async {
print('Client disconnected');
}
}
Expand Down Expand Up @@ -237,7 +240,7 @@ class AppModule extends Module {

class ReAppModule extends Module {
ReAppModule()
: super(imports: [], controllers: [
: super(imports: [TestInject()], controllers: [
HomeAController()
], providers: [
DeferredProvider(inject: [TestProvider, TestProvider],
Expand All @@ -250,6 +253,24 @@ class ReAppModule extends Module {
]);
}

class TestInject extends Module {
TestInject()
: super(imports: [], controllers: [
], providers: [TestProviderThree()
], middlewares: [], exports: [TestProviderThree
]);
}

class TestProviderThree extends Provider {

TestProviderThree();

String testMethod() {
return 'Hello world from provider three';
}

}

void main(List<String> arguments) async {
SerinusApplication application = await serinus.createApplication(
entrypoint: AppModule(), host: InternetAddress.anyIPv4.address);
Expand Down
8 changes: 6 additions & 2 deletions packages/serinus/lib/src/adapters/ws_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:web_socket_channel/status.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import '../contexts/contexts.dart';
import '../handlers/websocket_handler.dart';
import '../http/internal_request.dart';
import '../services/logger_service.dart';
import 'server_adapter.dart';
Expand Down Expand Up @@ -34,7 +35,7 @@ class WsAdapter extends Adapter<Map<String, WebSocket>> {
@override
Future<void> listen(List<WsRequestHandler> requestCallback,
{InternalRequest? request,
List<void Function()>? onDone,
List<DisconnectHandler>? onDone,
ErrorHandler? errorHandler}) async {
final wsClient = server?[request?.webSocketKey];
wsClient?.listen((data) {
Expand All @@ -44,7 +45,10 @@ class WsAdapter extends Adapter<Map<String, WebSocket>> {
}, onDone: () {
if (onDone != null) {
for (var done in onDone) {
done();
if(done.onDone == null) {
return;
}
done.onDone?.call(done.clientId);
}
}
}, onError: errorHandler);
Expand Down
37 changes: 28 additions & 9 deletions packages/serinus/lib/src/containers/module_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,15 @@ final class ModulesContainer {
moduleInjectables?.concatTo(newInjectables) ?? newInjectables;
}
_providers[token] = [];
for (final provider in initializedModule.providers
.where((element) => element is! DeferredProvider)) {
final split = initializedModule.providers.splitBy<DeferredProvider>();
for (final provider in split.notOfType) {
await initIfUnregistered(provider);
_moduleInjectables[token] = _moduleInjectables[token]!.copyWith(
providers: {..._moduleInjectables[token]!.providers, provider},
);
_providers[token]?.add(provider);
}
_deferredProviders[token] =
initializedModule.providers.whereType<DeferredProvider>();
_deferredProviders[token] = split.ofType;
logger.info(
'${initializedModule.runtimeType}${initializedModule.token.isNotEmpty ? '(${initializedModule.token})' : ''} dependencies initialized');
}
Expand Down Expand Up @@ -132,15 +131,13 @@ final class ModulesContainer {
Future<void> registerModules(Module module, Type entrypoint,
[ModuleInjectables? moduleInjectables]) async {
_isInitialized = true;
final eagerSubModules =
module.imports.where((element) => element is! DeferredModule);
final deferredSubModules = module.imports.whereType<DeferredModule>();
final splittedModules = module.imports.splitBy<DeferredModule>();
final token = moduleToken(module);
final currentModuleInjectables =
_moduleInjectables[token] ??= ModuleInjectables(
middlewares: {...module.middlewares},
);
for (var subModule in eagerSubModules) {
for (var subModule in splittedModules.notOfType) {
await _callForRecursiveRegistration(
subModule, module, entrypoint, currentModuleInjectables);
final subModuleToken = moduleToken(subModule);
Expand All @@ -149,7 +146,7 @@ final class ModulesContainer {
.concatTo(_moduleInjectables[subModuleToken]);
}
}
for (var deferredModule in deferredSubModules) {
for (var deferredModule in splittedModules.ofType) {
final subModule = await deferredModule
.init(_getApplicationContext(deferredModule.inject));
await _callForRecursiveRegistration(
Expand Down Expand Up @@ -197,6 +194,10 @@ final class ModulesContainer {
parentModule.providers.remove(provider);
parentModule.providers.add(initializedProvider);
}
final parentModuleInjectables = getModuleInjectablesByToken(token);
_moduleInjectables[token] = parentModuleInjectables.copyWith(
providers: parentModuleInjectables.providers.whereNot((e) => e is! DeferredProvider).toSet(),
);
if (!parentModule.exports.every((element) =>
_providers[token]?.map((e) => e.runtimeType).contains(element) ??
false)) {
Expand All @@ -215,6 +216,24 @@ final class ModulesContainer {
...entrypointInjectables.providers,
},
);
injectProvidersInSubModule(entrypoint);
}

/// Injects the providers in the submodules
void injectProvidersInSubModule(Module module) {
final token = moduleToken(module);
final moduleInjectables = _moduleInjectables[token]!;
for (final subModule in module.imports) {
final subModuleToken = moduleToken(subModule);
final subModuleInjectables = _moduleInjectables[subModuleToken]!;
_moduleInjectables[subModuleToken] = subModuleInjectables.copyWith(
providers: {
...moduleInjectables.providers,
...subModuleInjectables.providers,
},
);
injectProvidersInSubModule(subModule);
}
}

/// Gets the module scoped providers
Expand Down
16 changes: 3 additions & 13 deletions packages/serinus/lib/src/contexts/application_context.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import '../core/core.dart';
import 'base_context.dart';

/// The [ApplicationContext] class is used to create the application context.
class ApplicationContext {
/// The [providers] property contains the providers of the application context.
final Map<Type, Provider> providers;

class ApplicationContext extends BaseContext{
/// The [applicationId] property contains the ID of the application.
final String applicationId;

/// The constructor of the [ApplicationContext] class.
ApplicationContext(this.providers, this.applicationId);

/// This method is used to retrieve a provider from the context.
T use<T>() {
if (!providers.containsKey(T)) {
throw StateError('Provider not found in request context');
}
return providers[T] as T;
}
ApplicationContext(super.providers, this.applicationId);

/// This method is used to add a provider to the context.
void add(Provider provider) {
Expand Down
27 changes: 27 additions & 0 deletions packages/serinus/lib/src/contexts/base_context.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import '../core/provider.dart';

/// The [BaseContext] class must be used as the base class for all contexts.
///
/// It contains the common properties and methods that are used in all contexts.
abstract class BaseContext {
/// The [providers] property contains the providers of the context.
final Map<Type, Provider> providers;

/// The constructor of the [BaseContext] class.
const BaseContext(this.providers);

/// The [canUse] method is used to check if a provider exists in the context.
bool canUse<T>() {
return providers.containsKey(T);
}

/// This method is used to retrieve a provider from the context.
T use<T>() {
if (!canUse<T>()) {
throw StateError('Provider not found in request context');
}
return providers[T] as T;
}


}
21 changes: 3 additions & 18 deletions packages/serinus/lib/src/contexts/request_context.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import '../core/core.dart';
import '../http/http.dart';
import 'base_context.dart';

/// The [RequestContext] class is used to create the request context.
final class RequestContext {
/// The [providers] property contains the providers of the request context.
final Map<Type, Provider> providers;

final class RequestContext extends BaseContext{
/// The [request] property contains the request of the context.
final Request request;

Expand Down Expand Up @@ -34,7 +32,7 @@ final class RequestContext {

/// The constructor of the [RequestContext] class.
RequestContext(
this.providers,
super.providers,
this.request,
);

Expand All @@ -55,17 +53,4 @@ final class RequestContext {
bool canStat(String name) {
return metadata.containsKey(name);
}

/// This method is used to retrieve a provider from the context.
T use<T>() {
if (!canUse<T>()) {
throw StateError('Provider not found in request context');
}
return providers[T] as T;
}

/// The [canUse] method is used to check if a provider exists in the context.
bool canUse<T>() {
return providers.containsKey(T);
}
}
24 changes: 13 additions & 11 deletions packages/serinus/lib/src/contexts/ws_context.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import '../../../serinus.dart';
import 'base_context.dart';

/// The [WebSocketContext] class is used to create a WebSocket context.
///
/// It contains the request, the WebSocket adapter, the ID of the context, the providers, and the serializer.
final class WebSocketContext {
final class WebSocketContext extends BaseContext{
/// The [request] property contains the request of the context.
final Request request;

Expand All @@ -12,8 +13,6 @@ final class WebSocketContext {
/// The [id] property contains the id of the client.
final String id;

final Map<Type, Provider> _providers;

final MessageSerializer? _serializer;

/// The [queryParamters] property contains the query parameters of the request.
Expand All @@ -23,26 +22,29 @@ final class WebSocketContext {
Map<String, dynamic> get headers => request.headers;

/// The constructor of the [WebSocketContext] class.
const WebSocketContext(this._wsAdapter, this.id, this._providers,
const WebSocketContext(this._wsAdapter, this.id, super.providers,
this.request, this._serializer);

/// This method is used to send data to the client.
///
/// The [data] parameter is the data to be sent.
///
/// The [broadcast] parameter is used to broadcast the data to all clients.
void send(dynamic data, {bool broadcast = false}) {
void send(dynamic data) {
if (_serializer != null) {
data = _serializer!.serialize(data);
}
_wsAdapter.send(data, broadcast: broadcast, key: id);
_wsAdapter.send(data, key: id);
}

/// This method is used to retrieve a provider from the context.
T use<T>() {
if (!_providers.containsKey(T)) {
throw StateError('Provider not found in request context');
/// This method is used to broadcast data to all clients.
///
/// The [data] parameter is the data to be sent.
void broadcast(dynamic data) {
if (_serializer != null) {
data = _serializer!.serialize(data);
}
return _providers[T] as T;
_wsAdapter.send(data, broadcast: true);
}

}
21 changes: 20 additions & 1 deletion packages/serinus/lib/src/core/websockets/ws_gateway.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'package:meta/meta.dart';

import '../../adapters/ws_adapter.dart';
import '../../contexts/contexts.dart';
import '../core.dart';

Expand Down Expand Up @@ -30,13 +33,29 @@ abstract class WebSocketGateway extends Provider {
/// It is used to deserialize the data received from the client.
final MessageDeserializer? deserializer;

/// The [server] property contains the server of the WebSocketGateway.
WsAdapter? server;

/// The [WebSocketGateway] constructor is used to create a new instance of the [WebSocketGateway] class.
const WebSocketGateway({this.path, this.serializer, this.deserializer});
WebSocketGateway({this.path, this.serializer, this.deserializer});

/// The [onMessage] method will be called when a message from the client is received.
///
/// It takes a [dynamic] data and a [WebSocketContext] context and returns a [Future] of [void].
///
/// The [WebSocketContext] contains the context of the WebSocket and the methods to send messages to the client.
Future<void> onMessage(dynamic data, WebSocketContext context);

/// This method is used to send data to the client.
///
/// The [data] parameter is the data to be sent.
///
/// The [broadcast] parameter is used to broadcast the data to all clients.
@nonVirtual
void send(dynamic data, [String? clientId]) {
if (serializer != null) {
data = serializer!.serialize(data);
}
server?.send(data, broadcast: clientId == null, key: clientId);
}
}
Loading
Loading