|
| 1 | +// ignore_for_file: avoid_print |
| 2 | + |
| 3 | +import 'dart:async'; |
| 4 | +import 'package:relic/src/router/router.dart'; |
| 5 | + |
| 6 | +// === Core Stubs (simplified) === |
| 7 | +class Request { |
| 8 | + final Uri uri; |
| 9 | + final Method method; |
| 10 | + final Map<String, String> headers; |
| 11 | + Request({required this.uri, required this.method, this.headers = const {}}); |
| 12 | +} |
| 13 | + |
| 14 | +class Response { |
| 15 | + final int statusCode; |
| 16 | + final String body; |
| 17 | + Response(this.statusCode, this.body); |
| 18 | + |
| 19 | + static Response ok(final String body) => Response(200, body); |
| 20 | + static Response notFound(final String body) => Response(404, body); |
| 21 | + static Response unauthorized(final String body) => Response(401, body); |
| 22 | +} |
| 23 | + |
| 24 | +class RequestContext { |
| 25 | + final Request request; |
| 26 | + final Object token; // Stable unique token |
| 27 | + RequestContext(this.request, this.token); |
| 28 | +} |
| 29 | + |
| 30 | +class NewContext extends RequestContext { |
| 31 | + NewContext(super.request, super.token); |
| 32 | +} |
| 33 | + |
| 34 | +// === ContextProperty and Views Stubs === |
| 35 | +class ContextProperty<T extends Object> { |
| 36 | + final Expando<T> _expando; |
| 37 | + final String? _debugName; |
| 38 | + |
| 39 | + ContextProperty([this._debugName]) : _expando = Expando<T>(_debugName); |
| 40 | + T get(final RequestContext ctx) { |
| 41 | + final val = _expando[ctx.token]; |
| 42 | + if (val == null) { |
| 43 | + throw StateError('Property ${_debugName ?? T.toString()} not found'); |
| 44 | + } |
| 45 | + return val; |
| 46 | + } |
| 47 | + |
| 48 | + void set(final RequestContext ctx, final T val) => _expando[ctx.token] = val; |
| 49 | +} |
| 50 | + |
| 51 | +extension type BaseContextView(RequestContext _relicContext) { |
| 52 | + Request get request => _relicContext.request; |
| 53 | +} |
| 54 | + |
| 55 | +// User data and view |
| 56 | +class User { |
| 57 | + final String id; |
| 58 | + final String name; |
| 59 | + User(this.id, this.name); |
| 60 | +} |
| 61 | + |
| 62 | +final _userProperty = ContextProperty<User>('user'); |
| 63 | +extension type UserContextView(RequestContext _relicContext) |
| 64 | + implements BaseContextView { |
| 65 | + User get user => _userProperty.get(_relicContext); |
| 66 | + void attachUser(final User user) => _userProperty.set(_relicContext, user); |
| 67 | +} |
| 68 | + |
| 69 | +// Admin data and view |
| 70 | +class AdminRole { |
| 71 | + final String roleName; |
| 72 | + AdminRole(this.roleName); |
| 73 | +} |
| 74 | + |
| 75 | +final _adminRoleProperty = ContextProperty<AdminRole>('admin_role'); |
| 76 | +extension type AdminContextView(RequestContext _relicContext) |
| 77 | + implements UserContextView { |
| 78 | + // Admin also has User |
| 79 | + AdminRole get adminRole => _adminRoleProperty.get(_relicContext); |
| 80 | + void attachAdminRole(final AdminRole role) => |
| 81 | + _adminRoleProperty.set(_relicContext, role); |
| 82 | +} |
| 83 | + |
| 84 | +// === PipelineBuilder Stub === |
| 85 | +class PipelineBuilder<TInView extends BaseContextView, TOutView> { |
| 86 | + final TOutView Function(TInView) _chain; |
| 87 | + PipelineBuilder._(this._chain); |
| 88 | + |
| 89 | + static PipelineBuilder<BaseContextView, BaseContextView> start() { |
| 90 | + return PipelineBuilder._((final BaseContextView view) => view); |
| 91 | + } |
| 92 | + |
| 93 | + PipelineBuilder<TInView, TNextOutView> add<TNextOutView>( |
| 94 | + final TNextOutView Function(TOutView currentView) middleware, |
| 95 | + ) { |
| 96 | + return PipelineBuilder<TInView, TNextOutView>._( |
| 97 | + (final TInView initialView) { |
| 98 | + final previousOutput = _chain(initialView); |
| 99 | + return middleware(previousOutput); |
| 100 | + }); |
| 101 | + } |
| 102 | + |
| 103 | + FutureOr<Response> Function(NewContext initialContext) build( |
| 104 | + final FutureOr<Response> Function(TOutView finalView) handler, |
| 105 | + ) { |
| 106 | + final TOutView Function(BaseContextView) builtChain = |
| 107 | + _chain as TOutView Function(BaseContextView); |
| 108 | + return (final NewContext initialContext) { |
| 109 | + final initialView = BaseContextView(initialContext) |
| 110 | + as TInView; // Cast for the chain start |
| 111 | + final finalView = builtChain(initialView); |
| 112 | + return handler(finalView); |
| 113 | + }; |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +// === Placeholder Middleware === |
| 118 | +// API Auth: Adds User, returns UserContextView |
| 119 | +UserContextView apiAuthMiddleware(final BaseContextView inputView) { |
| 120 | + print('API Auth Middleware Running for ${inputView.request.uri.path}'); |
| 121 | + if (inputView.request.headers['X-API-Key'] == 'secret-api-key') { |
| 122 | + final userView = UserContextView(inputView._relicContext); |
| 123 | + userView.attachUser(User('api_user_123', 'API User')); |
| 124 | + return userView; |
| 125 | + } |
| 126 | + throw Response(401, 'API Key Required'); // Short-circuiting via exception |
| 127 | +} |
| 128 | + |
| 129 | +// Admin Auth: Adds User and AdminRole, returns AdminContextView |
| 130 | +AdminContextView adminAuthMiddleware(final BaseContextView inputView) { |
| 131 | + print('Admin Auth Middleware Running for ${inputView.request.uri.path}'); |
| 132 | + if (inputView.request.headers['X-Admin-Token'] == |
| 133 | + 'super-secret-admin-token') { |
| 134 | + final userView = UserContextView(inputView._relicContext); |
| 135 | + userView.attachUser(User('admin_user_007', 'Admin User')); |
| 136 | + |
| 137 | + final adminView = AdminContextView(inputView._relicContext); |
| 138 | + adminView.attachAdminRole(AdminRole('super_admin')); |
| 139 | + return adminView; |
| 140 | + } |
| 141 | + throw Response(401, 'Admin Token Required'); |
| 142 | +} |
| 143 | + |
| 144 | +T generalLoggingMiddleware<T extends BaseContextView>(final T inputView) { |
| 145 | + // Weird analyzer bug inputView cannot be null here. |
| 146 | + // Compiler and interpreter don't complain. Trying: |
| 147 | + // final req = inputView!.request; |
| 148 | + // won't work ¯\_(ツ)_/¯ |
| 149 | + // ignore: unchecked_use_of_nullable_value |
| 150 | + final req = inputView.request; |
| 151 | + print('Logging: ${req.method} ${req.uri.path}'); |
| 152 | + return inputView; |
| 153 | +} |
| 154 | + |
| 155 | +// === Endpoint Handlers === |
| 156 | +FutureOr<Response> handleApiUserDetails(final UserContextView context) { |
| 157 | + print('Handling API User Details for ${context.user.name}'); |
| 158 | + return Response.ok('API User: ${context.user.name} (id: ${context.user.id})'); |
| 159 | +} |
| 160 | + |
| 161 | +FutureOr<Response> handleAdminDashboard(final AdminContextView context) { |
| 162 | + print( |
| 163 | + 'Handling Admin Dashboard for ${context.user.name} (${context.adminRole.roleName})'); |
| 164 | + return Response.ok( |
| 165 | + 'Admin: ${context.user.name}, Role: ${context.adminRole.roleName}'); |
| 166 | +} |
| 167 | + |
| 168 | +FutureOr<Response> handlePublicInfo(final BaseContextView context) { |
| 169 | + print('Handling Public Info for ${context.request.uri.path}'); |
| 170 | + return Response.ok('This is public information.'); |
| 171 | +} |
| 172 | + |
| 173 | +typedef Handler = FutureOr<Response> Function(NewContext); |
| 174 | + |
| 175 | +void main() async { |
| 176 | + // === 1. Build Specialized Pipeline Handlers === |
| 177 | + final apiHandler = PipelineBuilder.start() |
| 178 | + .add(generalLoggingMiddleware) |
| 179 | + .add(apiAuthMiddleware) |
| 180 | + .build(handleApiUserDetails); |
| 181 | + |
| 182 | + final adminHandler = PipelineBuilder.start() |
| 183 | + .add(generalLoggingMiddleware) |
| 184 | + .add(adminAuthMiddleware) |
| 185 | + .build(handleAdminDashboard); |
| 186 | + |
| 187 | + final publicHandler = PipelineBuilder.start() |
| 188 | + .add(generalLoggingMiddleware) |
| 189 | + .build(handlePublicInfo); |
| 190 | + |
| 191 | + // === 2. Configure Top-Level Router === |
| 192 | + final topLevelRouter = Router<Handler>() |
| 193 | + ..any('/api/users/**', apiHandler) |
| 194 | + ..any('/admin/dashboard/**', adminHandler) |
| 195 | + ..any('/public/**', publicHandler); |
| 196 | + |
| 197 | + // === 3. Main Server Request Handler === |
| 198 | + FutureOr<Response> mainServerRequestHandler(final Request request) { |
| 199 | + final initialContext = NewContext(request, Object()); |
| 200 | + print('\nProcessing ${request.method} ${request.uri.path}'); |
| 201 | + |
| 202 | + try { |
| 203 | + final targetPipelineHandler = |
| 204 | + topLevelRouter.lookup(request.method, request.uri.path)?.value; |
| 205 | + |
| 206 | + if (targetPipelineHandler != null) { |
| 207 | + return targetPipelineHandler(initialContext); |
| 208 | + } else { |
| 209 | + print('No top-level route matched.'); |
| 210 | + return Response.notFound('Service endpoint not found.'); |
| 211 | + } |
| 212 | + } on Response catch (e) { |
| 213 | + print('Request short-circuited with response: ${e.statusCode}'); |
| 214 | + return e; |
| 215 | + } catch (e) { |
| 216 | + print('Unhandled error: $e'); |
| 217 | + return Response(500, 'Internal Server Error'); |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + // === Simulate some requests === |
| 222 | + final requests = [ |
| 223 | + Request( |
| 224 | + uri: Uri.parse('/api/users/123'), |
| 225 | + method: Method.get, |
| 226 | + headers: {'X-API-Key': 'secret-api-key'}, |
| 227 | + ), |
| 228 | + Request( |
| 229 | + uri: Uri.parse('/api/users/456'), |
| 230 | + method: Method.get, |
| 231 | + headers: {'X-API-Key': 'wrong-key'}, |
| 232 | + ), |
| 233 | + Request( |
| 234 | + uri: Uri.parse('/admin/dashboard'), |
| 235 | + method: Method.get, |
| 236 | + headers: {'X-Admin-Token': 'super-secret-admin-token'}, |
| 237 | + ), |
| 238 | + Request(uri: Uri.parse('/public/info'), method: Method.get), |
| 239 | + Request(uri: Uri.parse('/unknown/path'), method: Method.get), |
| 240 | + ]; |
| 241 | + |
| 242 | + for (final req in requests) { |
| 243 | + final res = await mainServerRequestHandler(req); |
| 244 | + print('Response for ${req.uri.path}: ${res.statusCode} - ${res.body}'); |
| 245 | + } |
| 246 | +} |
0 commit comments