Skip to content

Commit

Permalink
refactor(deriv_env): make package independent of env file (#318)
Browse files Browse the repository at this point in the history
* get the env file content from outside

* make Env class not being singleton

* create EnvLoader class

* fix the tests

* bring back the removed test case

* update the initialize doc

* export classes need to be used by outside

* export BaseEnv class as well

* format deriv_env.dart

* swape names for Env and EnvLoader to reduce the amount of change in the apps using this packagew

* handle exception in bool type

* remove duplicate lint rules

* define common values as variable in env_test.dart

* update README.md file

* remove breaking change by making the laod method deprecated

* Update CHANGELOG.md

* udpate README.md file

* refactor: Improve flexibility by removing dependency to env file
- **Deprecation:** The `load` method is deprecated; it is recommended to use the new `initialize` method for improved flexibility. The `load` method will still function but may be removed in future releases.
- Introduced a new method `initialize` to handle initialization without requiring an env file directly.
- Improved package flexibility by removing the strict dependency on an env file for test environments.
  • Loading branch information
ramin-deriv authored Nov 27, 2023
1 parent 53ffa34 commit a7242c8
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 138 deletions.
6 changes: 3 additions & 3 deletions packages/deriv_env/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ You can check if the environment variable is initialized using the `isInitialize
bool isInitialized = Env().isInitialized;
```

### Loading environment variables
### Initializing the Env

Before using any environment variables, you need to load them from a file. The `load` method loads the variables from a file with the specified filename (default is `.env`).
Before using any environment variables, you need to intialize the `Env` class and pass it an instnace of the EnvLoader and the path to the file. The `initialize` method loads the variables from a file with the specified filename (default is `.env`).

```dart
await Env().load();
await Env().initialize(EnvLoader(filePath: '.env'));
```

The load method expects the file to contain key-value pairs separated by an equals sign (`=`) and each pair separated by a newline character (`\n`). Blank lines and comments (lines starting with a `#`) are ignored.
Expand Down
3 changes: 1 addition & 2 deletions packages/deriv_env/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,11 @@ linter:
- flutter_style_todos
- hash_and_equals
- implementation_imports
- iterable_contains_unrelated_type
- collection_methods_unrelated_type
- join_return_with_assignment
- library_names
- library_prefixes
# - lines_longer_than_80_chars
- list_remove_unrelated_type
- no_adjacent_strings_in_list
- no_duplicate_case_values
- no_leading_underscores_for_local_identifiers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
/// Base class for retrieve environment variables providers.
abstract class BaseEnv {
abstract class BaseEnvLoader {
/// Returns `true` if [Env] is initialized, otherwise `false`.
bool get isInitialized;

/// Returns all environment variables as a [Map].
Map<String, dynamic> get entries;

/// Loads environment variables from a `.env` file.
///
/// If [filename] is not provided, it will default to `.env`.
Future<void> load([String filename = '.env']);
/// Loads environment variables.
Future<void> loadEnvironment();

/// Retrieves an environment variable value by key.
T get<T>(String key, {T? defaultValue});
T get<T>(
String key, {
T? defaultValue,
T Function(String value)? parser,
String decryptionKey = '',
});
}
3 changes: 3 additions & 0 deletions packages/deriv_env/lib/deriv_env.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'env.dart';
export 'env_loader.dart';
export 'base_env_loader.dart';
138 changes: 40 additions & 98 deletions packages/deriv_env/lib/env.dart
Original file line number Diff line number Diff line change
@@ -1,114 +1,56 @@
import 'package:flutter/services.dart';

import 'base_env.dart';
import 'cipher.dart';

/// [Env] class is a singleton class that provides access to environment variables.
class Env extends BaseEnv {
/// Returns the singleton instance of [Env].
factory Env() => _instance;

Env._();
import 'base_env_loader.dart';
import 'env_loader.dart';

/// This class is used to load environment variables from a .env file
class Env {
/// The singleton instance of [EnvLoader].
factory Env() {
_instance ??= Env._internal();
return _instance!;
}

static final Env _instance = Env._();
Env._internal();

bool _isInitialized = false;
static Env? _instance;

final Map<String, dynamic> _entries = <String, dynamic>{};
/// The environment variables provider.
BaseEnvLoader? _env;

@override
bool get isInitialized => _isInitialized;
/// The instance of [BaseEnv].
BaseEnvLoader? get env => _env;

@override
Map<String, dynamic> get entries {
_checkInitialization();
/// Returns `true` if [Env] is initialized, otherwise `false`.
bool get isInitialized => _env?.isInitialized ?? false;

return _entries;
/// Initializes [EnvLoader] with an instance of [BaseEnv].
/// Loads environment variables from a `.env` file.
///
/// If [filename] is not provided, it will default to `.env`.
Future<void> initialize(BaseEnvLoader env) async {
_env = env;
return _env!.loadEnvironment();
}

@override
Future<void> load([String filename = '.env']) async {
_entries.clear();

final List<String> fileEntries = await _getEntriesFromFile(filename);

for (final String entry in fileEntries) {
final List<String> items = entry.split('=');

if (items.length > 1) {
_entries[items.first.trim()] = items.sublist(1).join('=').trim();
}
}

_isInitialized = true;
/// Loads environment variables from a `.env` file.
@Deprecated('Use initialize() method instead.')
Future<void> load([String filePath = '.env']) async {
_env ??= EnvLoader(filePath: filePath);
return _env!.loadEnvironment();
}

@override
/// Retrieves an environment variable value by key.
T get<T>(
String key, {
T? defaultValue,
T Function(String value)? parser,
String decryptionKey = '',
}) {
_checkInitialization();

if (!_entries.containsKey(key)) {
if (defaultValue == null) {
throw Exception('$runtimeType does not contain a value for key: $key.');
}

return defaultValue;
}

final String value = decryptionKey.isEmpty
? _entries[key]
: Cipher().decrypt(message: _entries[key], key: decryptionKey);

if (parser != null) {
return parser(value);
}

switch (T) {
case int:
return int.tryParse(value) as T;
case double:
return double.tryParse(value) as T;
case bool:
return (value.toLowerCase() == 'true') as T;

default:
return value as T;
}
}

Future<List<String>> _getEntriesFromFile(String filename) async {
final String envFileContent = await rootBundle.loadString(filename);

if (envFileContent.isEmpty) {
throw Exception('$runtimeType: $filename is empty.');
}

final List<String> entries = <String>[];
final List<String> content = envFileContent.split('\n');

for (final String line in content) {
if (line.isEmpty || line.startsWith('#')) {
continue;
}

entries.add(line);
}

return entries;
}

void _checkInitialization() {
if (_isInitialized) {
return;
}

throw Exception(
'$runtimeType is not initialized, call load() method first.',
);
}
}) =>
isInitialized
? _env!.get<T>(
key,
defaultValue: defaultValue,
parser: parser,
decryptionKey: decryptionKey,
)
: throw Exception('EnvLoader is not initialized.');
}
119 changes: 119 additions & 0 deletions packages/deriv_env/lib/env_loader.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'package:flutter/services.dart';

import 'base_env_loader.dart';
import 'cipher.dart';

/// [Env] class is a singleton class that provides access to environment variables.
class EnvLoader extends BaseEnvLoader {
/// Creates a new instance of [EnvLoader].
EnvLoader({required this.filePath});

/// The path to the file.
final String filePath;

bool _isInitialized = false;

final Map<String, dynamic> _entries = <String, dynamic>{};

@override
bool get isInitialized => _isInitialized;

@override
Map<String, dynamic> get entries {
_checkInitialization();

return _entries;
}

@override
Future<void> loadEnvironment() async {
_entries.clear();

final List<String> fileEntries = await _getEntriesFromFile(filePath);

for (final String entry in fileEntries) {
final List<String> items = entry.split('=');

if (items.length > 1) {
_entries[items.first.trim()] = items.sublist(1).join('=').trim();
}
}

_isInitialized = true;
}

@override
T get<T>(
String key, {
T? defaultValue,
T Function(String value)? parser,
String decryptionKey = '',
}) {
_checkInitialization();

if (!_entries.containsKey(key)) {
if (defaultValue == null) {
throw Exception('$runtimeType does not contain a value for key: $key.');
}

return defaultValue;
}

final String value = decryptionKey.isEmpty
? _entries[key]
: Cipher().decrypt(message: _entries[key], key: decryptionKey);

if (parser != null) {
return parser(value);
}

switch (T) {
case int:
return int.tryParse(value) as T;
case double:
return double.tryParse(value) as T;
case bool:
if (value.toLowerCase() == 'true') {
return true as T;
} else if (value.toLowerCase() == 'false') {
return false as T;
} else {
throw FormatException('Invalid boolean value: $value');
}

default:
return value as T;
}
}

Future<List<String>> _getEntriesFromFile(String filename) async {
final String envFileContent = await rootBundle.loadString(filename);

if (envFileContent.isEmpty) {
throw Exception('$runtimeType: $filename is empty.');
}

final List<String> entries = <String>[];
final List<String> content = envFileContent.split('\n');

for (final String line in content) {
if (line.isEmpty || line.startsWith('#')) {
continue;
}

entries.add(line);
}

return entries;
}

void _checkInitialization() {
if (_isInitialized) {
return;
}

throw Exception(
'$runtimeType is not initialized, call load() method first.',
);
}
}
Loading

0 comments on commit a7242c8

Please sign in to comment.