diff --git a/packages/celest/lib/src/core/context.dart b/packages/celest/lib/src/core/context.dart index a0cfff41..c4bcaf13 100644 --- a/packages/celest/lib/src/core/context.dart +++ b/packages/celest/lib/src/core/context.dart @@ -2,8 +2,6 @@ import 'package:celest/celest.dart'; /// The context of a [CloudFunction] invocation. abstract final class Context { - const Context._(); - /// A context reference to the [User] invoking a [CloudFunction]. /// /// ## Example @@ -13,18 +11,26 @@ abstract final class Context { /// ```dart /// @authenticated /// Future sayHello({ - /// @Context.user() required User user, + /// @Context.user required User user, /// }) async { /// print('Hello, ${user.displayName}!'); /// } /// ``` - const factory Context.user() = _ContextKey.user; + /// + /// If a user is injected to a `@public` or private function, then the + /// user parameter must be nullable: + /// + /// ```dart + /// @public + /// Future sayHello({ + /// @Context.user User? user, + /// }) async { + /// print('Hello, ${user?.displayName ?? 'stranger'}!'); + /// } + /// ``` + static const user = _UserContext(); } -final class _ContextKey extends Context { - const _ContextKey._(this.key) : super._(); - - const _ContextKey.user() : this._('user'); - - final String key; +final class _UserContext implements Context { + const _UserContext(); } diff --git a/packages/celest/lib/src/runtime/serve.dart b/packages/celest/lib/src/runtime/serve.dart index 737bd89d..b13e24b4 100644 --- a/packages/celest/lib/src/runtime/serve.dart +++ b/packages/celest/lib/src/runtime/serve.dart @@ -59,8 +59,14 @@ abstract base class CloudFunctionTarget { Future _handler(Request request) async { final bodyJson = await request.decodeJson(); + const contextHeaderPrefix = 'X-Celest-Context-'; final response = await runZoned( - () => handle(bodyJson), + () => handle({ + for (final MapEntry(:key, :value) in request.headers.entries) + if (key.startsWith(contextHeaderPrefix)) + key.substring(contextHeaderPrefix.length): value, + ...bodyJson, + }), zoneSpecification: ZoneSpecification( print: (self, parent, zone, message) { parent.print(zone, '[$name] $message'); diff --git a/packages/celest_auth/example/celest/functions/greeting.dart b/packages/celest_auth/example/celest/functions/greeting.dart index 817360bc..e8343b5c 100644 --- a/packages/celest_auth/example/celest/functions/greeting.dart +++ b/packages/celest_auth/example/celest/functions/greeting.dart @@ -2,24 +2,20 @@ // folder of your Celest project. import 'package:celest/celest.dart'; -import 'package:celest_backend/exceptions/bad_name_exception.dart'; -import 'package:celest_backend/models/person.dart'; /// Says hello to the authenticated [user]. -// @authenticated @authenticated Future sayHello({ - required Person person, - // @Context.user() required User user, + @Context.user required User user, }) async { - if (person.name.isEmpty) { - // Throw a custom exception defined in the `lib/exceptions/` and catch - // it on the frontend. - throw BadNameException('Name cannot be empty'); + if (!user.emailVerified) { + throw UnauthorizedException('Email not verified'); } - // Logging is handled automatically when you print to the console. - print('Saying hello to ${person.name}'); + print('Saying hello to user: $user'); - return 'Hello, ${person.name}!'; + if (user.displayName case final name?) { + return 'Hello, $name!'; + } + return 'Hello!'; } diff --git a/packages/celest_auth/example/celest/lib/src/client/functions.dart b/packages/celest_auth/example/celest/lib/src/client/functions.dart index a5413ac2..48f1df90 100644 --- a/packages/celest_auth/example/celest/lib/src/client/functions.dart +++ b/packages/celest_auth/example/celest/lib/src/client/functions.dart @@ -7,9 +7,6 @@ library; // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:convert' as _$convert; import 'package:celest/celest.dart'; -import 'package:celest_backend/exceptions/bad_name_exception.dart' - as _$bad_name_exception; -import 'package:celest_backend/models/person.dart' as _$person; import 'package:celest_core/src/exception/cloud_exception.dart'; import 'package:celest_core/src/exception/serialization_exception.dart'; @@ -38,9 +35,6 @@ class CelestFunctionsGreeting { case r'SerializationException': throw Serializers.instance .deserialize($details); - case r'BadNameException': - throw Serializers.instance - .deserialize<_$bad_name_exception.BadNameException>($details); case _: switch ($statusCode) { case 400: @@ -52,12 +46,10 @@ class CelestFunctionsGreeting { } /// Says hello to the authenticated [user]. - Future sayHello({required _$person.Person person}) async { + Future sayHello() async { final $response = await celest.httpClient.post( celest.baseUri.resolve('/greeting/say-hello'), headers: const {'Content-Type': 'application/json; charset=utf-8'}, - body: _$convert.jsonEncode( - {r'person': Serializers.instance.serialize<_$person.Person>(person)}), ); final $body = (_$convert.jsonDecode($response.body) as Map); diff --git a/packages/celest_auth/example/celest/lib/src/client/serializers.dart b/packages/celest_auth/example/celest/lib/src/client/serializers.dart index f75bf32e..fd4cdea5 100644 --- a/packages/celest_auth/example/celest/lib/src/client/serializers.dart +++ b/packages/celest_auth/example/celest/lib/src/client/serializers.dart @@ -1,29 +1,10 @@ // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import -// ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:celest/celest.dart'; -import 'package:celest_backend/exceptions/bad_name_exception.dart' - as _$bad_name_exception; -import 'package:celest_backend/models/person.dart' as _$person; import 'package:celest_core/src/exception/cloud_exception.dart'; import 'package:celest_core/src/exception/serialization_exception.dart'; void initSerializers() { - Serializers.instance.put(Serializer.define< - _$bad_name_exception.BadNameException, Map>( - serialize: ($value) => {r'message': $value.message}, - deserialize: ($serialized) { - return _$bad_name_exception.BadNameException( - ($serialized[r'message'] as String)); - }, - )); - Serializers.instance - .put(Serializer.define<_$person.Person, Map>( - serialize: ($value) => {r'name': $value.name}, - deserialize: ($serialized) { - return _$person.Person(name: ($serialized[r'name'] as String)); - }, - )); Serializers.instance .put(Serializer.define>( serialize: ($value) => {r'message': $value.message}, diff --git a/packages/celest_auth/example/celest/resources.dart b/packages/celest_auth/example/celest/resources.dart index 9e08d4c3..ffc4fd72 100644 --- a/packages/celest_auth/example/celest/resources.dart +++ b/packages/celest_auth/example/celest/resources.dart @@ -3,22 +3,3 @@ // ignore_for_file: type=lint, unused_local_variable, unnecessary_cast, unnecessary_import library; - -import 'package:celest/celest.dart'; - -@Deprecated('Use `Apis` instead.') -typedef apis = Apis; - -abstract final class Apis { - static const greeting = CloudApi(name: r'greeting'); -} - -@Deprecated('Use `Functions` instead.') -typedef functions = Functions; - -abstract final class Functions { - static const greetingSayHello = CloudFunction( - api: r'greeting', - functionName: r'sayHello', - ); -} diff --git a/packages/celest_auth/example/celest/test/functions/greeting_test.dart b/packages/celest_auth/example/celest/test/functions/greeting_test.dart index dc9ec6df..89af06e1 100644 --- a/packages/celest_auth/example/celest/test/functions/greeting_test.dart +++ b/packages/celest_auth/example/celest/test/functions/greeting_test.dart @@ -1,5 +1,4 @@ -import 'package:celest_backend/exceptions/bad_name_exception.dart'; -import 'package:celest_backend/models/person.dart'; +import 'package:celest/celest.dart'; import 'package:test/test.dart'; import '../../functions/greeting.dart'; @@ -7,12 +6,29 @@ import '../../functions/greeting.dart'; void main() { group('greeting', () { test('sayHello', () async { - expect(await sayHello(person: Person(name: 'Celest')), 'Hello, Celest!'); + expect( + await sayHello( + user: User( + userId: '123', + email: 'test@celest.dev', + emailVerified: true, + displayName: 'Celest', + ), + ), + 'Hello, Celest!', + ); }); - test('sayHello (empty name)', () async { + test('sayHello (email not verified)', () async { expect( - sayHello(person: Person(name: '')), - throwsA(isA()), + sayHello( + user: User( + userId: '123', + email: 'test@celest.dev', + emailVerified: false, + displayName: 'Celest', + ), + ), + throwsA(isA()), ); }); }); diff --git a/packages/celest_auth/example/lib/main.dart b/packages/celest_auth/example/lib/main.dart index eb0a6927..e536d496 100644 --- a/packages/celest_auth/example/lib/main.dart +++ b/packages/celest_auth/example/lib/main.dart @@ -1,5 +1,4 @@ import 'package:celest_backend/client.dart'; -import 'package:celest_backend/models/person.dart'; import 'package:flutter/material.dart'; void main() { @@ -53,12 +52,9 @@ class _MainAppState extends State { const SizedBox(height: 16), TextButton( onPressed: () { - _request = celest.functions.greeting.sayHello( - person: const Person(name: 'Dillon'), - ); - if (context.mounted) { - setState(() {}); - } + setState(() { + _request = celest.functions.greeting.sayHello(); + }); }, child: const Text('Make authenticated request'), ),