add option type

This commit is contained in:
shenlong-tanwen 2025-05-17 08:57:47 +05:30
parent a9e7d0388b
commit a4a3d13f7c
2 changed files with 179 additions and 0 deletions

View File

@ -0,0 +1,86 @@
sealed class Option<T> {
const Option();
bool get isSome => this is Some<T>;
bool get isNone => this is None;
factory Option.some(T value) {
return Some<T>(value);
}
factory Option.none() {
return const None();
}
factory Option.from(T? value) {
if (value == null) {
return const None();
}
return Some<T>(value);
}
T? unwrapOrNull() {
if (this is Some<T>) {
return (this as Some<T>).value;
}
return null;
}
T unwrap() {
if (this is Some<T>) {
return (this as Some<T>).value;
}
throw StateError('Cannot unwrap None');
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other is! Option<T>) return false;
if (this is None && other is None) {
return true;
}
return this is Some<T> &&
other is Some<T> &&
this.unwrap() == other.unwrap();
}
@override
int get hashCode {
if (this is Some<T>) {
return (this.unwrap()).hashCode;
}
return 0;
}
@override
String toString() {
if (this is Some<T>) {
return 'Some(${this.unwrap()})';
}
return 'None';
}
}
class Some<T> extends Option<T> {
final T value;
const Some(this.value);
}
class None extends Option<Never> {
const None();
}
// Implemented as an extension rather than adding to the Option class because
// the type argument for None is not known at compile time, and fallback to Never
// As such, when the method is called on Option<Never>, with a default value of T,
// a runtime error
extension OptionExtensions<T> on Option<T> {
T unwrapOr(T defaultValue) {
if (this is Some<T>) {
return (this as Some<T>).value;
}
return defaultValue;
}
}

View File

@ -0,0 +1,93 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/utils/option.dart';
void main() {
group('Option', () {
test('should create a Some instance', () {
const value = 42;
final option = Option.some(value);
expect(option, isA<Some<int>>());
expect(option.isSome, isTrue);
expect(option.isNone, isFalse);
});
test('Some instance should hold the correct value', () {
const value = 'immich';
final option = Option.some(value);
expect(option.unwrapOrNull(), equals(value));
});
});
test('should create a None instance', () {
final option = Option<int>.none();
expect(option, isA<None>());
expect(option.isSome, isFalse);
expect(option.isNone, isTrue);
});
test('None instance are equal', () {
final option1 = Option<int>.none();
final option2 = Option<int>.none();
expect(option1, equals(option2));
});
group('Option.from', () {
test('should create a Some instance for a non-null value', () {
const value = 100.5;
final option = Option.from(value);
expect(option, isA<Some<double>>());
expect(option.isSome, isTrue);
expect(option.isNone, isFalse);
expect(option.unwrapOrNull(), equals(value));
});
test('should create a None instance for a null value', () {
final String? value = null;
final option = Option.from(value);
expect(option, isA<None>());
expect(option.isSome, isFalse);
expect(option.isNone, isTrue);
});
});
group('unwrap()', () {
test('should return the value for Some', () {
const value = 'immich';
final option = Option.some(value);
expect(option.unwrap(), equals(value));
});
test('should throw StateError for None', () {
final option = Option<int>.none();
expect(() => option.unwrap(), throwsStateError);
});
});
group('unwrapOrNull()', () {
test('should return the value for Some', () {
const value = 'test';
final option = Option.some(value);
expect(option.unwrapOrNull(), equals(value));
});
test('should return null for None', () {
final option = Option<int>.none();
expect(option.unwrapOrNull(), isNull);
});
});
group('unwrapOr()', () {
test('should return the value for Some', () {
const value = true;
const defaultValue = false;
final option = Option.some(value);
expect(option.unwrapOr(defaultValue), equals(value));
});
test('should return the default value for None', () {
const defaultValue = 'immich';
final option = Option<String>.none();
expect(option.unwrapOr(defaultValue), equals(defaultValue));
});
});
}