Skip to content

Commit 0f0f135

Browse files
committed
✨ enhance get and stream methods to support enum types with validation
1 parent 7e66468 commit 0f0f135

File tree

5 files changed

+82
-26
lines changed

5 files changed

+82
-26
lines changed

packages/hyper_storage/lib/src/api/backend.dart

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -184,18 +184,23 @@ mixin GenericStorageOperationsMixin implements StorageOperationsApi {
184184

185185
@override
186186
Future<E?> get<E extends Object>(String key, {List<Enum>? enumValues}) async {
187-
if (E == String) return await getString(key) as E?;
188-
if (E == int) return await getInt(key) as E?;
189-
if (E == double) return await getDouble(key) as E?;
190-
if (E == bool) return await getBool(key) as E?;
191-
if (E == DateTime) return await getDateTime(key) as E?;
192-
if (E == Duration) return await getDuration(key) as E?;
193-
if (E == List<String>) return await getStringList(key) as E?;
194-
if (E == Map<String, dynamic>) return await getJson(key) as E?;
195-
if (E == List<Map<String, dynamic>>) return await getJsonList(key) as E?;
196-
if (enumValues != null) return await getEnum(key, enumValues) as E?;
197-
198-
throw UnsupportedError('Type $E is not supported');
187+
return switch (E) {
188+
const (String) => await getString(key) as E?,
189+
const (int) => await getInt(key) as E?,
190+
const (double) => await getDouble(key) as E?,
191+
const (num) => await getDouble(key) as E?,
192+
const (bool) => await getBool(key) as E?,
193+
const (DateTime) => await getDateTime(key) as E?,
194+
const (Duration) => await getDuration(key) as E?,
195+
const (List<String>) => await getStringList(key) as E?,
196+
const (Map<String, dynamic>) => await getJson(key) as E?,
197+
const (List<Map<String, dynamic>>) => await getJsonList(key) as E?,
198+
_ when enumValues != null => () {
199+
checkEnumType<E>(enumValues);
200+
return getEnum(key, enumValues.cast<Enum>()) as E?;
201+
}(),
202+
_ => throw UnsupportedError('Type $E is not supported. If this is an enum, provide the enumValues parameter.'),
203+
};
199204
}
200205

201206
@override

packages/hyper_storage/lib/src/hyper_storage.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:meta/meta.dart';
1010
import '../hyper_storage.dart';
1111
import 'api/backend.dart';
1212
import 'api/storage_container.dart';
13+
import 'utils.dart';
1314

1415
part 'storage_base.dart';
1516

@@ -403,8 +404,10 @@ class HyperStorage extends _HyperStorageImpl {
403404
/// - List of JSON Map
404405
/// - DateTime
405406
/// - Duration
406-
Stream<E?> stream<E extends Object>(String key) async* {
407-
final E? itemValue = await get<E>(key);
407+
/// - Enum (requires providing [enumValues])
408+
Stream<E?> stream<E extends Object>(String key, {List<Enum>? enumValues}) async* {
409+
if(enumValues != null) checkEnumType<E>(enumValues);
410+
final E? itemValue = await get<E>(key, enumValues: enumValues);
408411
yield itemValue;
409412

410413
late final void Function() retrieveAndAdd;
@@ -414,7 +417,7 @@ class HyperStorage extends _HyperStorageImpl {
414417

415418
retrieveAndAdd = () async {
416419
if (controller.isClosed) return;
417-
final E? value = await get<E>(key);
420+
final E? value = await get<E>(key, enumValues: enumValues);
418421
if (!controller.isClosed) {
419422
controller.add(value);
420423
}
@@ -423,6 +426,8 @@ class HyperStorage extends _HyperStorageImpl {
423426
addKeyListener(key, retrieveAndAdd);
424427

425428
yield* controller.stream;
429+
430+
// Defensive close call. Almost never reached because the stream is closed on cancel.
426431
await controller.close(); // coverage:ignore-line
427432
}
428433
}

packages/hyper_storage/lib/src/hyper_storage_container.dart

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:meta/meta.dart' show protected;
1010
import '../hyper_storage.dart';
1111
import 'api/backend.dart';
1212
import 'api/storage_container.dart';
13+
import 'utils.dart';
1314

1415
/// A concrete implementation of [StorageContainer] for storing key-value pairs.
1516
///
@@ -281,8 +282,10 @@ final class HyperStorageContainer extends StorageContainer with ItemHolderMixin,
281282
/// - List of JSON Map
282283
/// - DateTime
283284
/// - Duration
284-
Stream<E?> stream<E extends Object>(String key) async* {
285-
final E? itemValue = await get<E>(key);
285+
/// - Enum (requires providing [enumValues])
286+
Stream<E?> stream<E extends Object>(String key, {List<Enum>? enumValues}) async* {
287+
if(enumValues != null) checkEnumType<E>(enumValues);
288+
final E? itemValue = await get<E>(key, enumValues: enumValues);
286289
yield itemValue;
287290

288291
late final void Function() retrieveAndAdd;
@@ -292,15 +295,14 @@ final class HyperStorageContainer extends StorageContainer with ItemHolderMixin,
292295

293296
retrieveAndAdd = () async {
294297
if (controller.isClosed) return;
295-
final E? value = await get<E>(key);
296-
if (!controller.isClosed) {
297-
controller.add(value);
298-
}
298+
final E? value = await get<E>(key, enumValues: enumValues);
299+
if (!controller.isClosed) controller.add(value);
299300
};
300301

301302
addKeyListener(key, retrieveAndAdd);
302303

303304
yield* controller.stream;
305+
// Defensive close call. Almost never reached because the stream is closed on cancel.
304306
await controller.close(); // coverage:ignore-line
305307
}
306308
}

packages/hyper_storage/lib/src/item_holder.dart

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'dart:convert';
1111
import 'package:meta/meta.dart';
1212

1313
import '../hyper_storage.dart';
14+
import 'utils.dart';
1415

1516
/// Function type definition for getting an item from the storage backend.
1617
typedef ItemGetter<E extends Object> = Future<E?> Function(StorageBackend backend, String key);
@@ -31,6 +32,8 @@ class ItemHolder<E extends Object> with Stream<E?> implements BaseListenable, It
3132

3233
bool _isClosed = false;
3334

35+
final List<Enum>? _enumValues;
36+
3437
/// The key associated with this item holder.
3538
@internal
3639
bool get isClosed => _isClosed;
@@ -56,7 +59,13 @@ class ItemHolder<E extends Object> with Stream<E?> implements BaseListenable, It
5659
final StreamController<E?> _streamController = StreamController<E?>.broadcast();
5760

5861
/// Creates a new [ItemHolder] instance.
59-
ItemHolder(BaseStorage this._parent, this._key, {this.getter, this.setter});
62+
ItemHolder(
63+
BaseStorage this._parent,
64+
this._key, {
65+
this.getter,
66+
this.setter,
67+
List<Enum>? enumValues,
68+
}) : _enumValues = enumValues;
6069

6170
@override
6271
Future<bool> get exists => _parent?.backend.containsKey(_key) ?? Future.value(false);
@@ -65,7 +74,7 @@ class ItemHolder<E extends Object> with Stream<E?> implements BaseListenable, It
6574
Future<E?> get() async {
6675
if (_parent case BaseStorage(:final backend)) {
6776
if (getter case var getter?) return getter(backend, _key);
68-
final E? value = await backend.get(_key);
77+
final E? value = await backend.get<E>(_key, enumValues: _enumValues);
6978
return value;
7079
}
7180
return Future.value(null);
@@ -399,6 +408,7 @@ mixin ItemHolderMixin on BaseStorage {
399408
/// - List of String
400409
/// - JSON Map
401410
/// - List of JSON Maps
411+
/// - Enum (requires providing [enumValues] unless custom getter/setter supplied)
402412
///
403413
/// Parameters:
404414
/// - [key]: The key under which to store the item. Must be non-empty and not only whitespace.
@@ -407,6 +417,8 @@ mixin ItemHolderMixin on BaseStorage {
407417
/// to retrieve the item instead of the default backend method.
408418
/// - [set]: Optional custom setter function. If provided, this function will be used
409419
/// to store the item instead of the default backend method.
420+
/// - [enumValues]: Optional list of all enum values when using an enum type with the
421+
/// default getter/setter. Required if [E] is an enum and you are not supplying custom accessors.
410422
///
411423
/// Returns:
412424
/// A [ItemHolder] configured to manage the item at the specified key.
@@ -417,13 +429,25 @@ mixin ItemHolderMixin on BaseStorage {
417429
/// See also:
418430
/// - [jsonItemHolder] for JSON-specific serialization.
419431
/// - [serializableItemHolder] for generic/other serialization.
420-
ItemHolder<E> itemHolder<E extends Object>(String key, {ItemGetter<E>? get, ItemSetter<E>? set}) {
432+
ItemHolder<E> itemHolder<E extends Object>(
433+
String key, {
434+
ItemGetter<E>? get,
435+
ItemSetter<E>? set,
436+
List<Enum>? enumValues,
437+
}) {
421438
validateKey(key);
422439
if ((get == null && set != null) || (get != null && set == null)) {
423440
throw ArgumentError('Both getter and setter must be provided together, or neither.');
424441
}
425442
// Only run generic type validation if custom getter/setter are not provided.
426-
if (set == null || get == null) _validateGenericType<E>();
443+
final hasCustomAccessors = get != null && set != null;
444+
if (!hasCustomAccessors) {
445+
if (enumValues == null) {
446+
_validateGenericType<E>();
447+
} else {
448+
checkEnumType<E>(enumValues);
449+
}
450+
}
427451
var existing = _holders[key];
428452
if (existing != null && existing.isClosed) {
429453
_holders.remove(key);
@@ -439,7 +463,13 @@ mixin ItemHolderMixin on BaseStorage {
439463
}
440464
}
441465

442-
final holder = ItemHolder<E>(this, encodeKey(key), setter: set, getter: get);
466+
final holder = ItemHolder<E>(
467+
this,
468+
encodeKey(key),
469+
setter: set,
470+
getter: get,
471+
enumValues: enumValues,
472+
);
443473
_holders[key] = holder;
444474
return holder;
445475
}

packages/hyper_storage/lib/src/utils.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,17 @@ Object? tryJsonDecode(String value) {
1818
return null;
1919
}
2020
}
21+
22+
@internal
23+
void checkEnumType<E extends Object>(List<Enum> enumValues) {
24+
if (enumValues is List<E>) {
25+
if (enumValues.isEmpty) {
26+
throw ArgumentError('The enumValues parameter cannot be empty.');
27+
}
28+
if (enumValues.first.runtimeType != E) {
29+
throw ArgumentError('The enumValues parameter must be of type $E. Found: ${enumValues.first.runtimeType}.');
30+
}
31+
} else {
32+
throw ArgumentError('The enumValues parameter must be of type List<$E>. Found: ${enumValues.runtimeType}.');
33+
}
34+
}

0 commit comments

Comments
 (0)