update: updating files with detailed comments
This commit is contained in:
parent
2cb685578b
commit
204b1b998e
6 changed files with 1005 additions and 30 deletions
|
@ -13,7 +13,28 @@ import 'dart:mirrors';
|
||||||
import 'package:protevus_config/config.dart';
|
import 'package:protevus_config/config.dart';
|
||||||
import 'package:protevus_runtime/runtime.dart';
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
|
||||||
|
/// A compiler class for configurations in the Protevus Platform.
|
||||||
|
///
|
||||||
|
/// This class extends the [Compiler] class and provides functionality to
|
||||||
|
/// compile configuration-related data and modify package files.
|
||||||
|
///
|
||||||
|
/// The [compile] method creates a map of configuration names to their
|
||||||
|
/// corresponding [ConfigurationRuntimeImpl] instances by scanning for
|
||||||
|
/// subclasses of [Configuration] in the given [MirrorContext].
|
||||||
|
///
|
||||||
|
/// The [deflectPackage] method modifies the "conduit_config.dart" file in the
|
||||||
|
/// destination directory by removing a specific export statement.
|
||||||
class ConfigurationCompiler extends Compiler {
|
class ConfigurationCompiler extends Compiler {
|
||||||
|
/// Compiles configuration data from the given [MirrorContext].
|
||||||
|
///
|
||||||
|
/// This method scans the [context] for all subclasses of [Configuration]
|
||||||
|
/// and creates a map where:
|
||||||
|
/// - The keys are the names of these subclasses (as strings)
|
||||||
|
/// - The values are instances of [ConfigurationRuntimeImpl] created from
|
||||||
|
/// the corresponding subclass
|
||||||
|
///
|
||||||
|
/// Returns a [Map<String, Object>] where each entry represents a
|
||||||
|
/// configuration class and its runtime implementation.
|
||||||
@override
|
@override
|
||||||
Map<String, Object> compile(MirrorContext context) {
|
Map<String, Object> compile(MirrorContext context) {
|
||||||
return Map.fromEntries(
|
return Map.fromEntries(
|
||||||
|
@ -26,15 +47,26 @@ class ConfigurationCompiler extends Compiler {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Modifies the package file by removing a specific export statement.
|
||||||
|
///
|
||||||
|
/// This method performs the following steps:
|
||||||
|
/// 1. Locates the "config.dart" file in the "lib/" directory of the [destinationDirectory].
|
||||||
|
/// 2. Reads the contents of the file.
|
||||||
|
/// 3. Removes the line "export 'package:protevus_config/src/compiler.dart';" from the file contents.
|
||||||
|
/// 4. Writes the modified contents back to the file.
|
||||||
|
///
|
||||||
|
/// This operation is typically used to adjust the exported modules in the compiled package.
|
||||||
|
///
|
||||||
|
/// [destinationDirectory] is the directory where the package files are located.
|
||||||
@override
|
@override
|
||||||
void deflectPackage(Directory destinationDirectory) {
|
void deflectPackage(Directory destinationDirectory) {
|
||||||
final libFile = File.fromUri(
|
final libFile = File.fromUri(
|
||||||
destinationDirectory.uri.resolve("lib/").resolve("conduit_config.dart"),
|
destinationDirectory.uri.resolve("lib/").resolve("config.dart"),
|
||||||
);
|
);
|
||||||
final contents = libFile.readAsStringSync();
|
final contents = libFile.readAsStringSync();
|
||||||
libFile.writeAsStringSync(
|
libFile.writeAsStringSync(
|
||||||
contents.replaceFirst(
|
contents.replaceFirst(
|
||||||
"export 'package:conduit_config/src/compiler.dart';", ""),
|
"export 'package:protevus_config/src/compiler.dart';", ""),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,17 +14,90 @@ import 'package:protevus_runtime/runtime.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:yaml/yaml.dart';
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
/// Subclasses of [Configuration] read YAML strings and files, assigning values from the YAML document to properties
|
/// A base class for configuration management in Dart applications.
|
||||||
/// of an instance of this type.
|
///
|
||||||
|
/// [Configuration] provides a framework for reading and parsing YAML-based
|
||||||
|
/// configuration files or strings. It offers various constructors to create
|
||||||
|
/// configuration objects from different sources (maps, strings, or files),
|
||||||
|
/// and includes methods for decoding and validating configuration data.
|
||||||
|
///
|
||||||
|
/// Key features:
|
||||||
|
/// - Supports creating configurations from YAML strings, files, or maps
|
||||||
|
/// - Provides a runtime context for configuration-specific operations
|
||||||
|
/// - Includes a default decoding mechanism that can be overridden
|
||||||
|
/// - Offers a validation method to ensure all required fields are present
|
||||||
|
/// - Allows for environment variable substitution in configuration values
|
||||||
|
///
|
||||||
|
/// Subclasses of [Configuration] should implement specific configuration
|
||||||
|
/// structures by defining properties that correspond to expected YAML keys.
|
||||||
|
/// The [decode] and [validate] methods can be overridden to provide custom
|
||||||
|
/// behavior for complex configuration scenarios.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// class MyConfig extends Configuration {
|
||||||
|
/// late String apiKey;
|
||||||
|
/// int port = 8080;
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// void validate() {
|
||||||
|
/// super.validate();
|
||||||
|
/// if (port < 1000 || port > 65535) {
|
||||||
|
/// throw ConfigurationException(this, "Invalid port number");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// final config = MyConfig.fromFile(File('config.yaml'));
|
||||||
|
/// ```
|
||||||
abstract class Configuration {
|
abstract class Configuration {
|
||||||
/// Default constructor.
|
/// Default constructor for the Configuration class.
|
||||||
|
///
|
||||||
|
/// This constructor creates a new instance of the Configuration class
|
||||||
|
/// without any initial configuration data. It can be used as a starting
|
||||||
|
/// point for creating custom configurations, which can then be populated
|
||||||
|
/// using other methods or by setting properties directly.
|
||||||
Configuration();
|
Configuration();
|
||||||
|
|
||||||
|
/// Creates a [Configuration] instance from a given map.
|
||||||
|
///
|
||||||
|
/// This constructor takes a [Map] with dynamic keys and values, converts
|
||||||
|
/// all keys to strings, and then decodes the resulting map into the
|
||||||
|
/// configuration properties. This is useful when you have configuration
|
||||||
|
/// data already in a map format, possibly from a non-YAML source.
|
||||||
|
///
|
||||||
|
/// [map] The input map containing configuration data. Keys will be
|
||||||
|
/// converted to strings, while values remain as their original types.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final configMap = {'key1': 'value1', 'key2': 42};
|
||||||
|
/// final config = MyConfiguration.fromMap(configMap);
|
||||||
|
/// ```
|
||||||
Configuration.fromMap(Map<dynamic, dynamic> map) {
|
Configuration.fromMap(Map<dynamic, dynamic> map) {
|
||||||
decode(map.map<String, dynamic>((k, v) => MapEntry(k.toString(), v)));
|
decode(map.map<String, dynamic>((k, v) => MapEntry(k.toString(), v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [contents] must be YAML.
|
/// Creates a [Configuration] instance from a YAML string.
|
||||||
|
///
|
||||||
|
/// This constructor takes a [String] containing YAML content, parses it into
|
||||||
|
/// a map, and then decodes the resulting map into the configuration properties.
|
||||||
|
/// It's useful when you have configuration data as a YAML string, perhaps
|
||||||
|
/// loaded from a file or received from an API.
|
||||||
|
///
|
||||||
|
/// [contents] A string containing valid YAML data. This will be parsed and
|
||||||
|
/// used to populate the configuration properties.
|
||||||
|
///
|
||||||
|
/// Throws a [YamlException] if the YAML parsing fails.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// final yamlString = '''
|
||||||
|
/// api_key: abc123
|
||||||
|
/// port: 8080
|
||||||
|
/// ''';
|
||||||
|
/// final config = MyConfiguration.fromString(yamlString);
|
||||||
|
/// ```
|
||||||
Configuration.fromString(String contents) {
|
Configuration.fromString(String contents) {
|
||||||
final yamlMap = loadYaml(contents) as Map<dynamic, dynamic>?;
|
final yamlMap = loadYaml(contents) as Map<dynamic, dynamic>?;
|
||||||
final map =
|
final map =
|
||||||
|
@ -32,15 +105,29 @@ abstract class Configuration {
|
||||||
decode(map);
|
decode(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens a file and reads its string contents into this instance's properties.
|
/// Creates a [Configuration] instance from a YAML file.
|
||||||
///
|
///
|
||||||
/// [file] must contain valid YAML data.
|
/// [file] must contain valid YAML data.
|
||||||
Configuration.fromFile(File file) : this.fromString(file.readAsStringSync());
|
Configuration.fromFile(File file) : this.fromString(file.readAsStringSync());
|
||||||
|
|
||||||
|
/// Returns the [ConfigurationRuntime] associated with the current instance's runtime type.
|
||||||
|
///
|
||||||
|
/// This getter retrieves the [ConfigurationRuntime] from the [RuntimeContext.current] map,
|
||||||
|
/// using the runtime type of the current instance as the key. The retrieved value
|
||||||
|
/// is then cast to [ConfigurationRuntime].
|
||||||
|
///
|
||||||
|
/// This is typically used internally to access runtime-specific configuration
|
||||||
|
/// operations and validations.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// The [ConfigurationRuntime] associated with this configuration's type.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// A runtime exception if the retrieved value cannot be cast to [ConfigurationRuntime].
|
||||||
ConfigurationRuntime get _runtime =>
|
ConfigurationRuntime get _runtime =>
|
||||||
RuntimeContext.current[runtimeType] as ConfigurationRuntime;
|
RuntimeContext.current[runtimeType] as ConfigurationRuntime;
|
||||||
|
|
||||||
/// Ingests [value] into the properties of this type.
|
/// Decodes the given [value] and populates the properties of this configuration instance.
|
||||||
///
|
///
|
||||||
/// Override this method to provide decoding behavior other than the default behavior.
|
/// Override this method to provide decoding behavior other than the default behavior.
|
||||||
void decode(dynamic value) {
|
void decode(dynamic value) {
|
||||||
|
@ -58,7 +145,8 @@ abstract class Configuration {
|
||||||
|
|
||||||
/// Validates this configuration.
|
/// Validates this configuration.
|
||||||
///
|
///
|
||||||
/// By default, ensures all required keys are non-null.
|
/// This method is called automatically after the configuration is decoded. It performs
|
||||||
|
/// validation checks on the configuration data to ensure its integrity and correctness.
|
||||||
///
|
///
|
||||||
/// Override this method to perform validations on input data. Throw [ConfigurationException]
|
/// Override this method to perform validations on input data. Throw [ConfigurationException]
|
||||||
/// for invalid data.
|
/// for invalid data.
|
||||||
|
@ -67,6 +155,24 @@ abstract class Configuration {
|
||||||
_runtime.validate(this);
|
_runtime.validate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves an environment variable value or returns the original value.
|
||||||
|
///
|
||||||
|
/// This method checks if the given [value] is a string that starts with '$'.
|
||||||
|
/// If so, it interprets the rest of the string as an environment variable name
|
||||||
|
/// and attempts to retrieve its value from the system environment.
|
||||||
|
///
|
||||||
|
/// If the environment variable exists, its value is returned.
|
||||||
|
/// If the environment variable does not exist, null is returned.
|
||||||
|
/// If the [value] is not a string starting with '$', the original [value] is returned unchanged.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [value]: The value to check. Can be of any type.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// - The value of the environment variable if [value] is a string starting with '$'
|
||||||
|
/// and the corresponding environment variable exists.
|
||||||
|
/// - null if [value] is a string starting with '$' but the environment variable doesn't exist.
|
||||||
|
/// - The original [value] if it's not a string starting with '$'.
|
||||||
static dynamic getEnvironmentOrValue(dynamic value) {
|
static dynamic getEnvironmentOrValue(dynamic value) {
|
||||||
if (value is String && value.startsWith(r"$")) {
|
if (value is String && value.startsWith(r"$")) {
|
||||||
final envKey = value.substring(1);
|
final envKey = value.substring(1);
|
||||||
|
@ -80,10 +186,72 @@ abstract class Configuration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An abstract class representing the runtime behavior for configuration objects.
|
||||||
|
///
|
||||||
|
/// This class provides methods for decoding and validating configuration objects,
|
||||||
|
/// as well as a utility method for handling exceptions during the decoding process.
|
||||||
|
///
|
||||||
|
/// Implementations of this class should provide concrete logic for decoding
|
||||||
|
/// configuration data from input maps and validating the resulting configuration objects.
|
||||||
|
///
|
||||||
|
/// The [tryDecode] method offers a standardized way to handle exceptions that may occur
|
||||||
|
/// during the decoding process, wrapping them in appropriate [ConfigurationException]s
|
||||||
|
/// with detailed key paths for easier debugging.
|
||||||
abstract class ConfigurationRuntime {
|
abstract class ConfigurationRuntime {
|
||||||
|
/// Decodes the input map and populates the given configuration object.
|
||||||
|
///
|
||||||
|
/// This method is responsible for parsing the input map and setting the
|
||||||
|
/// corresponding values in the configuration object. It should handle
|
||||||
|
/// type conversions, nested structures, and any specific logic required
|
||||||
|
/// for populating the configuration.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [configuration]: The Configuration object to be populated with decoded values.
|
||||||
|
/// [input]: A Map containing the raw configuration data to be decoded.
|
||||||
|
///
|
||||||
|
/// Implementations of this method should handle potential errors gracefully,
|
||||||
|
/// possibly by throwing ConfigurationException for invalid or missing data.
|
||||||
void decode(Configuration configuration, Map input);
|
void decode(Configuration configuration, Map input);
|
||||||
|
|
||||||
|
/// Validates the given configuration object.
|
||||||
|
///
|
||||||
|
/// This method is responsible for performing validation checks on the
|
||||||
|
/// provided configuration object. It should ensure that all required
|
||||||
|
/// fields are present and that the values meet any specific criteria
|
||||||
|
/// or constraints defined for the configuration.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [configuration]: The Configuration object to be validated.
|
||||||
|
///
|
||||||
|
/// Implementations of this method should throw a [ConfigurationException]
|
||||||
|
/// if any validation errors are encountered, providing clear and specific
|
||||||
|
/// error messages to aid in debugging and resolution of configuration issues.
|
||||||
void validate(Configuration configuration);
|
void validate(Configuration configuration);
|
||||||
|
|
||||||
|
/// Attempts to decode a configuration property and handles exceptions.
|
||||||
|
///
|
||||||
|
/// This method provides a standardized way to handle exceptions that may occur
|
||||||
|
/// during the decoding process of a configuration property. It wraps the decoding
|
||||||
|
/// logic in a try-catch block and transforms various exceptions into appropriate
|
||||||
|
/// [ConfigurationException]s with detailed key paths for easier debugging.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [configuration]: The Configuration object being decoded.
|
||||||
|
/// [name]: The name of the property being decoded.
|
||||||
|
/// [decode]: A function that performs the actual decoding logic.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// The result of the [decode] function if successful.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// [ConfigurationException]:
|
||||||
|
/// - If a [ConfigurationException] is caught, it's re-thrown with an updated key path.
|
||||||
|
/// - If an [IntermediateException] is caught, it's transformed into a [ConfigurationException]
|
||||||
|
/// with appropriate error details.
|
||||||
|
/// - For any other exception, a new [ConfigurationException] is thrown with the exception message.
|
||||||
|
///
|
||||||
|
/// This method is particularly useful for maintaining a consistent error handling
|
||||||
|
/// approach across different configuration properties and types.
|
||||||
dynamic tryDecode(
|
dynamic tryDecode(
|
||||||
Configuration configuration,
|
Configuration configuration,
|
||||||
String name,
|
String name,
|
||||||
|
@ -134,18 +302,40 @@ abstract class ConfigurationRuntime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible options for a configuration item property's optionality.
|
/// Enumerates the possible options for a configuration item property's optionality.
|
||||||
|
///
|
||||||
|
/// This enum is used to specify whether a configuration property is required or optional
|
||||||
|
/// when parsing configuration data. It helps in determining how to handle missing keys
|
||||||
|
/// in the source YAML configuration.
|
||||||
enum ConfigurationItemAttributeType {
|
enum ConfigurationItemAttributeType {
|
||||||
/// [Configuration] properties marked as [required] will throw an exception
|
/// Indicates that a configuration property is required.
|
||||||
/// if their source YAML doesn't contain a matching key.
|
///
|
||||||
|
/// When a configuration property is marked as [required], it means that
|
||||||
|
/// the corresponding key must be present in the source YAML configuration.
|
||||||
|
/// If the key is missing, an exception will be thrown during the parsing
|
||||||
|
/// or validation process.
|
||||||
|
///
|
||||||
|
/// This helps ensure that all necessary configuration values are provided
|
||||||
|
/// and reduces the risk of runtime errors due to missing configuration data.
|
||||||
required,
|
required,
|
||||||
|
|
||||||
|
/// Indicates that a configuration property is optional.
|
||||||
|
///
|
||||||
|
/// When a configuration property is marked as [optional], it means that
|
||||||
|
/// the corresponding key can be omitted from the source YAML configuration
|
||||||
|
/// without causing an error. If the key is missing, the property will be
|
||||||
|
/// silently ignored during the parsing process.
|
||||||
|
///
|
||||||
|
/// This allows for more flexible configuration structures where some
|
||||||
|
/// properties are not mandatory and can be omitted without affecting
|
||||||
|
/// the overall functionality of the configuration.
|
||||||
|
///
|
||||||
/// [Configuration] properties marked as [optional] will be silently ignored
|
/// [Configuration] properties marked as [optional] will be silently ignored
|
||||||
/// if their source YAML doesn't contain a matching key.
|
/// if their source YAML doesn't contain a matching key.
|
||||||
optional
|
optional
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [Configuration] properties may be attributed with these.
|
/// Represents an attribute for configuration item properties.
|
||||||
///
|
///
|
||||||
/// **NOTICE**: This will be removed in version 2.0.0.
|
/// **NOTICE**: This will be removed in version 2.0.0.
|
||||||
/// To signify required or optional config you could do:
|
/// To signify required or optional config you could do:
|
||||||
|
@ -204,14 +394,83 @@ const ConfigurationItemAttribute requiredConfiguration =
|
||||||
const ConfigurationItemAttribute optionalConfiguration =
|
const ConfigurationItemAttribute optionalConfiguration =
|
||||||
ConfigurationItemAttribute._(ConfigurationItemAttributeType.optional);
|
ConfigurationItemAttribute._(ConfigurationItemAttributeType.optional);
|
||||||
|
|
||||||
/// Thrown when reading data into a [Configuration] fails.
|
/// Represents an exception thrown when reading data into a [Configuration] fails.
|
||||||
|
///
|
||||||
|
/// This exception provides detailed information about the configuration error,
|
||||||
|
/// including the configuration object where the error occurred, the error message,
|
||||||
|
/// and optionally, the key path to the problematic configuration item.
|
||||||
|
///
|
||||||
|
/// The class offers two constructors:
|
||||||
|
/// 1. A general constructor for creating exceptions with custom messages.
|
||||||
|
/// 2. A specialized constructor [ConfigurationException.missingKeys] for creating
|
||||||
|
/// exceptions specifically related to missing required keys.
|
||||||
|
///
|
||||||
|
/// The [toString] method provides a formatted error message that includes the
|
||||||
|
/// configuration type, the key path (if available), and the error message.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// throw ConfigurationException(
|
||||||
|
/// myConfig,
|
||||||
|
/// "Invalid value",
|
||||||
|
/// keyPath: ['server', 'port'],
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Or for missing keys:
|
||||||
|
/// ```dart
|
||||||
|
/// throw ConfigurationException.missingKeys(
|
||||||
|
/// myConfig,
|
||||||
|
/// ['apiKey', 'secret'],
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
class ConfigurationException {
|
class ConfigurationException {
|
||||||
|
/// Creates a new [ConfigurationException] instance.
|
||||||
|
///
|
||||||
|
/// This constructor is used to create an exception that provides information
|
||||||
|
/// about a configuration error.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [configuration]: The [Configuration] object where the error occurred.
|
||||||
|
/// - [message]: A string describing the error.
|
||||||
|
/// - [keyPath]: An optional list of keys or indices that specify the path to
|
||||||
|
/// the problematic configuration item. Defaults to an empty list.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// throw ConfigurationException(
|
||||||
|
/// myConfig,
|
||||||
|
/// "Invalid port number",
|
||||||
|
/// keyPath: ['server', 'port'],
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
ConfigurationException(
|
ConfigurationException(
|
||||||
this.configuration,
|
this.configuration,
|
||||||
this.message, {
|
this.message, {
|
||||||
this.keyPath = const [],
|
this.keyPath = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Creates a [ConfigurationException] for missing required keys.
|
||||||
|
///
|
||||||
|
/// This constructor is specifically used to create an exception when one or more
|
||||||
|
/// required keys are missing from the configuration.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [configuration]: The [Configuration] object where the missing keys were detected.
|
||||||
|
/// - [missingKeys]: A list of strings representing the names of the missing required keys.
|
||||||
|
/// - [keyPath]: An optional list of keys or indices that specify the path to the
|
||||||
|
/// configuration item where the missing keys were expected. Defaults to an empty list.
|
||||||
|
///
|
||||||
|
/// The [message] is automatically generated to list all the missing keys.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// throw ConfigurationException.missingKeys(
|
||||||
|
/// myConfig,
|
||||||
|
/// ['apiKey', 'secret'],
|
||||||
|
/// keyPath: ['server', 'authentication'],
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
ConfigurationException.missingKeys(
|
ConfigurationException.missingKeys(
|
||||||
this.configuration,
|
this.configuration,
|
||||||
List<String> missingKeys, {
|
List<String> missingKeys, {
|
||||||
|
@ -219,17 +478,52 @@ class ConfigurationException {
|
||||||
}) : message =
|
}) : message =
|
||||||
"missing required key(s): ${missingKeys.map((s) => "'$s'").join(", ")}";
|
"missing required key(s): ${missingKeys.map((s) => "'$s'").join(", ")}";
|
||||||
|
|
||||||
/// The [Configuration] in which this exception occurred.
|
/// The [Configuration] instance in which this exception occurred.
|
||||||
|
///
|
||||||
|
/// This field stores a reference to the [Configuration] object that was being
|
||||||
|
/// processed when the exception was thrown. It provides context about which
|
||||||
|
/// specific configuration was involved in the error, allowing for more
|
||||||
|
/// detailed error reporting and easier debugging.
|
||||||
|
///
|
||||||
|
/// The stored configuration can be used to access additional information
|
||||||
|
/// about the configuration state at the time of the error, which can be
|
||||||
|
/// helpful in diagnosing and resolving configuration-related issues.
|
||||||
final Configuration configuration;
|
final Configuration configuration;
|
||||||
|
|
||||||
/// The reason for the exception.
|
/// The reason for the exception.
|
||||||
|
///
|
||||||
|
/// This field contains a string describing the specific error or reason
|
||||||
|
/// why the [ConfigurationException] was thrown. It provides detailed
|
||||||
|
/// information about what went wrong during the configuration process.
|
||||||
|
///
|
||||||
|
/// The message can be used for logging, debugging, or displaying error
|
||||||
|
/// information to users or developers to help diagnose and fix
|
||||||
|
/// configuration-related issues.
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
/// The key of the object being evaluated.
|
/// The key path of the object being evaluated.
|
||||||
///
|
///
|
||||||
/// Either a string (adds '.name') or an int (adds '\[value\]').
|
/// Either a string (adds '.name') or an int (adds '\[value\]').
|
||||||
final List<dynamic> keyPath;
|
final List<dynamic> keyPath;
|
||||||
|
|
||||||
|
/// Provides a string representation of the [ConfigurationException].
|
||||||
|
///
|
||||||
|
/// This method generates a formatted error message that includes:
|
||||||
|
/// - The type of the configuration where the error occurred
|
||||||
|
/// - The key path to the problematic configuration item (if available)
|
||||||
|
/// - The specific error message
|
||||||
|
///
|
||||||
|
/// The key path is constructed by joining the elements in [keyPath]:
|
||||||
|
/// - String elements are joined with dots (e.g., 'server.port')
|
||||||
|
/// - Integer elements are enclosed in square brackets (e.g., '[0]')
|
||||||
|
///
|
||||||
|
/// If [keyPath] is empty, a general error message for the configuration is returned.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string containing the formatted error message.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// [StateError] if an element in [keyPath] is neither a String nor an int.
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
if (keyPath.isEmpty) {
|
if (keyPath.isEmpty) {
|
||||||
|
@ -254,16 +548,78 @@ class ConfigurationException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Thrown when [Configuration] subclass is invalid and requires a change in code.
|
/// Represents an error that occurs when a [Configuration] subclass is invalid and requires a change in code.
|
||||||
|
///
|
||||||
|
/// This exception is thrown when there's a structural or logical issue with a [Configuration] subclass
|
||||||
|
/// that cannot be resolved at runtime and requires modifications to the code itself.
|
||||||
|
///
|
||||||
|
/// The [ConfigurationError] provides information about the specific [Configuration] type that caused the error
|
||||||
|
/// and a descriptive message explaining the nature of the invalidity.
|
||||||
|
///
|
||||||
|
/// Properties:
|
||||||
|
/// - [type]: The Type of the [Configuration] subclass where the error occurred.
|
||||||
|
/// - [message]: A String describing the specific error or invalidity.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// throw ConfigurationError(MyConfig, "Missing required property 'apiKey'");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The [toString] method provides a formatted error message combining the invalid type and the error description.
|
||||||
class ConfigurationError {
|
class ConfigurationError {
|
||||||
|
/// Creates a new [ConfigurationError] instance.
|
||||||
|
///
|
||||||
|
/// This constructor is used to create an error that indicates an invalid [Configuration] subclass
|
||||||
|
/// which requires changes to the code itself to resolve.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [type]: The [Type] of the [Configuration] subclass where the error occurred.
|
||||||
|
/// - [message]: A string describing the specific error or invalidity.
|
||||||
|
///
|
||||||
|
/// This error is typically thrown when there's a structural or logical issue with a [Configuration]
|
||||||
|
/// subclass that cannot be resolved at runtime and requires modifications to the code.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// throw ConfigurationError(MyConfig, "Missing required property 'apiKey'");
|
||||||
|
/// ```
|
||||||
ConfigurationError(this.type, this.message);
|
ConfigurationError(this.type, this.message);
|
||||||
|
|
||||||
/// The type of [Configuration] in which this error appears in.
|
/// The type of [Configuration] in which this error appears.
|
||||||
|
///
|
||||||
|
/// This property stores the [Type] of the [Configuration] subclass that is
|
||||||
|
/// considered invalid or problematic. It provides context about which specific
|
||||||
|
/// configuration class triggered the error, allowing for more precise error
|
||||||
|
/// reporting and easier debugging.
|
||||||
|
///
|
||||||
|
/// The stored type can be used to identify the exact [Configuration] subclass
|
||||||
|
/// that needs to be modified or corrected to resolve the error.
|
||||||
final Type type;
|
final Type type;
|
||||||
|
|
||||||
/// The reason for the error.
|
/// The reason for the error.
|
||||||
|
///
|
||||||
|
/// This field contains a string describing the specific error or reason
|
||||||
|
/// why the [ConfigurationError] was thrown. It provides detailed
|
||||||
|
/// information about what makes the [Configuration] subclass invalid
|
||||||
|
/// or problematic.
|
||||||
|
///
|
||||||
|
/// The message can be used for logging, debugging, or displaying error
|
||||||
|
/// information to developers to help diagnose and fix issues related
|
||||||
|
/// to the structure or implementation of the [Configuration] subclass.
|
||||||
String message;
|
String message;
|
||||||
|
|
||||||
|
/// Returns a string representation of the [ConfigurationError].
|
||||||
|
///
|
||||||
|
/// This method generates a formatted error message that includes:
|
||||||
|
/// - The type of the invalid [Configuration] subclass
|
||||||
|
/// - The specific error message describing the invalidity
|
||||||
|
///
|
||||||
|
/// The resulting string is useful for logging, debugging, or displaying
|
||||||
|
/// error information to developers to help identify and fix issues with
|
||||||
|
/// the [Configuration] subclass implementation.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A string containing the formatted error message.
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return "Invalid configuration type '$type'. $message";
|
return "Invalid configuration type '$type'. $message";
|
||||||
|
|
|
@ -10,17 +10,73 @@
|
||||||
import 'package:protevus_config/config.dart';
|
import 'package:protevus_config/config.dart';
|
||||||
|
|
||||||
/// A [Configuration] to represent a database connection configuration.
|
/// A [Configuration] to represent a database connection configuration.
|
||||||
|
///
|
||||||
|
/// This class extends [Configuration] and provides properties and methods
|
||||||
|
/// for managing database connection settings. It includes properties for
|
||||||
|
/// host, port, database name, username, password, and a flag for temporary
|
||||||
|
/// databases. The class supports initialization from various sources
|
||||||
|
/// (file, string, map) and provides a custom decoder for parsing connection
|
||||||
|
/// strings.
|
||||||
|
///
|
||||||
|
/// Properties:
|
||||||
|
/// - [host]: The host of the database to connect to (required).
|
||||||
|
/// - [port]: The port of the database to connect to (required).
|
||||||
|
/// - [databaseName]: The name of the database to connect to (required).
|
||||||
|
/// - [username]: A username for authenticating to the database (optional).
|
||||||
|
/// - [password]: A password for authenticating to the database (optional).
|
||||||
|
/// - [isTemporary]: A flag to represent permanence, used for test suites (optional).
|
||||||
|
///
|
||||||
|
/// The [decode] method allows parsing of connection strings or maps to
|
||||||
|
/// populate the configuration properties.
|
||||||
class DatabaseConfiguration extends Configuration {
|
class DatabaseConfiguration extends Configuration {
|
||||||
/// Default constructor.
|
/// Default constructor for DatabaseConfiguration.
|
||||||
|
///
|
||||||
|
/// Creates a new instance of DatabaseConfiguration without initializing any properties.
|
||||||
|
/// Properties can be set manually or through the decode method after instantiation.
|
||||||
DatabaseConfiguration();
|
DatabaseConfiguration();
|
||||||
|
|
||||||
|
/// Creates a [DatabaseConfiguration] instance from a file.
|
||||||
|
///
|
||||||
|
/// This named constructor initializes the configuration by reading from a file.
|
||||||
|
/// The file path is passed to the superclass constructor [Configuration.fromFile].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [file]: The path to the configuration file.
|
||||||
DatabaseConfiguration.fromFile(super.file) : super.fromFile();
|
DatabaseConfiguration.fromFile(super.file) : super.fromFile();
|
||||||
|
|
||||||
|
/// Creates a [DatabaseConfiguration] instance from a YAML string.
|
||||||
|
///
|
||||||
|
/// This named constructor initializes the configuration by parsing a YAML string.
|
||||||
|
/// The YAML string is passed to the superclass constructor [Configuration.fromString].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [yaml]: A string containing YAML-formatted configuration data.
|
||||||
DatabaseConfiguration.fromString(super.yaml) : super.fromString();
|
DatabaseConfiguration.fromString(super.yaml) : super.fromString();
|
||||||
|
|
||||||
|
/// Creates a [DatabaseConfiguration] instance from a Map.
|
||||||
|
///
|
||||||
|
/// This named constructor initializes the configuration using a Map of key-value pairs.
|
||||||
|
/// The Map is passed to the superclass constructor [Configuration.fromMap].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [yaml]: A Map containing configuration data.
|
||||||
DatabaseConfiguration.fromMap(super.yaml) : super.fromMap();
|
DatabaseConfiguration.fromMap(super.yaml) : super.fromMap();
|
||||||
|
|
||||||
/// A named constructor that contains all of the properties of this instance.
|
/// Creates a [DatabaseConfiguration] instance with all connection information provided.
|
||||||
|
///
|
||||||
|
/// This named constructor allows for the direct initialization of all database connection
|
||||||
|
/// properties in a single call. It sets both required and optional properties.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [username]: The username for database authentication (optional).
|
||||||
|
/// [password]: The password for database authentication (optional).
|
||||||
|
/// [host]: The host address of the database server (required).
|
||||||
|
/// [port]: The port number on which the database server is listening (required).
|
||||||
|
/// [databaseName]: The name of the specific database to connect to (required).
|
||||||
|
/// [isTemporary]: A flag indicating if this is a temporary database connection (optional, defaults to false).
|
||||||
|
///
|
||||||
|
/// This constructor provides a convenient way to create a fully configured
|
||||||
|
/// [DatabaseConfiguration] object when all connection details are known in advance.
|
||||||
DatabaseConfiguration.withConnectionInfo(
|
DatabaseConfiguration.withConnectionInfo(
|
||||||
this.username,
|
this.username,
|
||||||
this.password,
|
this.password,
|
||||||
|
@ -32,36 +88,102 @@ class DatabaseConfiguration extends Configuration {
|
||||||
|
|
||||||
/// The host of the database to connect to.
|
/// The host of the database to connect to.
|
||||||
///
|
///
|
||||||
/// This property is required.
|
/// This property represents the hostname or IP address of the database server
|
||||||
|
/// that this configuration will connect to. It is a required field and must be
|
||||||
|
/// set before attempting to establish a database connection.
|
||||||
|
///
|
||||||
|
/// The value should be a valid hostname (e.g., 'localhost', 'db.example.com')
|
||||||
|
/// or an IP address (e.g., '192.168.1.100').
|
||||||
|
///
|
||||||
|
/// This property is marked as 'late', which means it must be initialized
|
||||||
|
/// before it's first used, but not necessarily in the constructor.
|
||||||
late String host;
|
late String host;
|
||||||
|
|
||||||
/// The port of the database to connect to.
|
/// The port of the database to connect to.
|
||||||
///
|
///
|
||||||
/// This property is required.
|
/// This property represents the network port number on which the database server
|
||||||
|
/// is listening for connections. It is a required field and must be set before
|
||||||
|
/// attempting to establish a database connection.
|
||||||
|
///
|
||||||
|
/// The value should be a valid port number, typically an integer between 0 and 65535.
|
||||||
|
/// Common database port numbers include 5432 for PostgreSQL, 3306 for MySQL,
|
||||||
|
/// and 1433 for SQL Server, but the actual port may vary depending on the specific
|
||||||
|
/// database configuration.
|
||||||
|
///
|
||||||
|
/// This property is marked as 'late', which means it must be initialized
|
||||||
|
/// before it's first used, but not necessarily in the constructor.
|
||||||
late int port;
|
late int port;
|
||||||
|
|
||||||
/// The name of the database to connect to.
|
/// The name of the database to connect to.
|
||||||
///
|
///
|
||||||
/// This property is required.
|
/// This property represents the specific database name within the database server
|
||||||
|
/// that this configuration will target. It is a required field and must be set
|
||||||
|
/// before attempting to establish a database connection.
|
||||||
|
///
|
||||||
|
/// The value should be a valid database name as defined in your database server.
|
||||||
|
/// For example, it could be 'myapp_database', 'users_db', or 'production_data'.
|
||||||
|
///
|
||||||
|
/// This property is marked as 'late', which means it must be initialized
|
||||||
|
/// before it's first used, but not necessarily in the constructor.
|
||||||
late String databaseName;
|
late String databaseName;
|
||||||
|
|
||||||
/// A username for authenticating to the database.
|
/// A username for authenticating to the database.
|
||||||
///
|
///
|
||||||
/// This property is optional.
|
/// This property represents the username used for authentication when connecting
|
||||||
|
/// to the database. It is an optional field, meaning it can be null if authentication
|
||||||
|
/// is not required or if other authentication methods are used.
|
||||||
|
///
|
||||||
|
/// The value should be a string containing the username as configured in the
|
||||||
|
/// database server for this particular connection. For example, it could be
|
||||||
|
/// 'db_user', 'admin', or 'app_service_account'.
|
||||||
|
///
|
||||||
|
/// If this property is set, it is typically used in conjunction with the [password]
|
||||||
|
/// property to form a complete set of credentials for database authentication.
|
||||||
String? username;
|
String? username;
|
||||||
|
|
||||||
/// A password for authenticating to the database.
|
/// A password for authenticating to the database.
|
||||||
///
|
///
|
||||||
/// This property is optional.
|
/// This property represents the password used for authentication when connecting
|
||||||
|
/// to the database. It is an optional field, meaning it can be null if authentication
|
||||||
|
/// is not required or if other authentication methods are used.
|
||||||
|
///
|
||||||
|
/// The value should be a string containing the password that corresponds to the
|
||||||
|
/// [username] for this database connection. For security reasons, it's important
|
||||||
|
/// to handle this value carefully and avoid exposing it in logs or user interfaces.
|
||||||
|
///
|
||||||
|
/// If this property is set, it is typically used in conjunction with the [username]
|
||||||
|
/// property to form a complete set of credentials for database authentication.
|
||||||
|
///
|
||||||
|
/// Note: In production environments, it's recommended to use secure methods of
|
||||||
|
/// storing and retrieving passwords, such as environment variables or secure
|
||||||
|
/// secret management systems, rather than hardcoding them in the configuration.
|
||||||
String? password;
|
String? password;
|
||||||
|
|
||||||
/// A flag to represent permanence.
|
/// A flag to represent permanence of the database.
|
||||||
///
|
///
|
||||||
/// This flag is used for test suites that use a temporary database to run tests against,
|
/// This flag is used for test suites that use a temporary database to run tests against,
|
||||||
/// dropping it after the tests are complete.
|
/// dropping it after the tests are complete.
|
||||||
/// This property is optional.
|
/// This property is optional.
|
||||||
bool isTemporary = false;
|
bool isTemporary = false;
|
||||||
|
|
||||||
|
/// Decodes and populates the configuration from a given value.
|
||||||
|
///
|
||||||
|
/// This method can handle two types of input:
|
||||||
|
/// 1. A Map: In this case, it delegates to the superclass's decode method.
|
||||||
|
/// 2. A String: It parses the string as a URI to extract database connection details.
|
||||||
|
///
|
||||||
|
/// For string input, it extracts:
|
||||||
|
/// - Host and port from the URI
|
||||||
|
/// - Database name from the path (if present)
|
||||||
|
/// - Username and password from the userInfo part of the URI (if present)
|
||||||
|
///
|
||||||
|
/// After parsing, it calls the validate method to ensure all required fields are set.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [value]: The input to decode. Can be a Map or a String.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// [ConfigurationException]: If the input is neither a Map nor a String.
|
||||||
@override
|
@override
|
||||||
void decode(dynamic value) {
|
void decode(dynamic value) {
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
|
@ -101,27 +223,80 @@ class DatabaseConfiguration extends Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [Configuration] to represent an external HTTP API.
|
/// A [Configuration] to represent an external HTTP API.
|
||||||
|
///
|
||||||
|
/// This class extends [Configuration] and provides properties for managing
|
||||||
|
/// external API connection settings. It includes properties for the base URL,
|
||||||
|
/// client ID, and client secret.
|
||||||
|
///
|
||||||
|
/// The class supports initialization from various sources (file, string, map)
|
||||||
|
/// through its constructors.
|
||||||
|
///
|
||||||
|
/// Properties:
|
||||||
|
/// - [baseURL]: The base URL of the described API (required).
|
||||||
|
/// - [clientID]: The client ID for API authentication (optional).
|
||||||
|
/// - [clientSecret]: The client secret for API authentication (optional).
|
||||||
|
///
|
||||||
|
/// Constructors:
|
||||||
|
/// - Default constructor: Creates an empty instance.
|
||||||
|
/// - [fromFile]: Initializes from a configuration file.
|
||||||
|
/// - [fromString]: Initializes from a YAML string.
|
||||||
|
/// - [fromMap]: Initializes from a Map.
|
||||||
class APIConfiguration extends Configuration {
|
class APIConfiguration extends Configuration {
|
||||||
|
/// Default constructor for APIConfiguration.
|
||||||
|
///
|
||||||
|
/// Creates a new instance of APIConfiguration without initializing any properties.
|
||||||
|
/// Properties can be set manually or through the decode method after instantiation.
|
||||||
APIConfiguration();
|
APIConfiguration();
|
||||||
|
|
||||||
|
/// Creates an [APIConfiguration] instance from a file.
|
||||||
|
///
|
||||||
|
/// This named constructor initializes the configuration by reading from a file.
|
||||||
|
/// The file path is passed to the superclass constructor [Configuration.fromFile].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [file]: The path to the configuration file.
|
||||||
APIConfiguration.fromFile(super.file) : super.fromFile();
|
APIConfiguration.fromFile(super.file) : super.fromFile();
|
||||||
|
|
||||||
|
/// Creates an [APIConfiguration] instance from a YAML string.
|
||||||
|
///
|
||||||
|
/// This named constructor initializes the configuration by parsing a YAML string.
|
||||||
|
/// The YAML string is passed to the superclass constructor [Configuration.fromString].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [yaml]: A string containing YAML-formatted configuration data.
|
||||||
APIConfiguration.fromString(super.yaml) : super.fromString();
|
APIConfiguration.fromString(super.yaml) : super.fromString();
|
||||||
|
|
||||||
|
/// Creates an [APIConfiguration] instance from a Map.
|
||||||
|
///
|
||||||
|
/// This named constructor initializes the configuration using a Map of key-value pairs.
|
||||||
|
/// The Map is passed to the superclass constructor [Configuration.fromMap].
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [yaml]: A Map containing configuration data.
|
||||||
APIConfiguration.fromMap(super.yaml) : super.fromMap();
|
APIConfiguration.fromMap(super.yaml) : super.fromMap();
|
||||||
|
|
||||||
/// The base URL of the described API.
|
/// The base URL of the described API.
|
||||||
///
|
///
|
||||||
|
/// This property represents the root URL for the external API that this configuration
|
||||||
|
/// is describing. It is a required field and must be set before using the API configuration.
|
||||||
|
///
|
||||||
|
/// The value should be a complete URL, including the protocol (http or https),
|
||||||
|
/// domain name, and optionally the port and base path. It serves as the foundation
|
||||||
|
/// for constructing full URLs to specific API endpoints.
|
||||||
|
///
|
||||||
|
/// This property is marked as 'late', which means it must be initialized
|
||||||
|
/// before it's first used, but not necessarily in the constructor.
|
||||||
|
///
|
||||||
/// This property is required.
|
/// This property is required.
|
||||||
/// Example: https://external.api.com:80/resources
|
/// Example: https://external.api.com:80/resources
|
||||||
late String baseURL;
|
late String baseURL;
|
||||||
|
|
||||||
/// The client ID.
|
/// The client ID for API authentication.
|
||||||
///
|
///
|
||||||
/// This property is optional.
|
/// This property is optional.
|
||||||
String? clientID;
|
String? clientID;
|
||||||
|
|
||||||
/// The client secret.
|
/// The client secret for API authentication.
|
||||||
///
|
///
|
||||||
/// This property is optional.
|
/// This property is optional.
|
||||||
String? clientSecret;
|
String? clientSecret;
|
||||||
|
|
|
@ -7,10 +7,31 @@
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/// An exception class used for intermediate error handling.
|
||||||
|
///
|
||||||
|
/// This class encapsulates an underlying exception and a key path,
|
||||||
|
/// allowing for more detailed error reporting in nested structures.
|
||||||
|
///
|
||||||
|
/// [underlying] is the original exception that was caught.
|
||||||
|
/// [keyPath] is a list representing the path to the error in a nested structure.
|
||||||
class IntermediateException implements Exception {
|
class IntermediateException implements Exception {
|
||||||
|
/// Creates an [IntermediateException] with the given [underlying] exception and [keyPath].
|
||||||
|
///
|
||||||
|
/// [underlying] is the original exception that was caught.
|
||||||
|
/// [keyPath] is a list representing the path to the error in a nested structure.
|
||||||
IntermediateException(this.underlying, this.keyPath);
|
IntermediateException(this.underlying, this.keyPath);
|
||||||
|
|
||||||
|
/// The original exception that was caught.
|
||||||
|
///
|
||||||
|
/// This field stores the underlying exception that triggered the creation
|
||||||
|
/// of this [IntermediateException]. It can be of any type, hence the
|
||||||
|
/// [dynamic] type annotation.
|
||||||
final dynamic underlying;
|
final dynamic underlying;
|
||||||
|
|
||||||
|
/// A list representing the path to the error in a nested structure.
|
||||||
|
///
|
||||||
|
/// This field stores the key path as a list of dynamic elements. Each element
|
||||||
|
/// in the list represents a key or index in the nested structure, helping to
|
||||||
|
/// pinpoint the exact location of the error.
|
||||||
final List<dynamic> keyPath;
|
final List<dynamic> keyPath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,47 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:mirrors';
|
import 'dart:mirrors';
|
||||||
|
|
||||||
import 'package:protevus_config/config.dart';
|
import 'package:protevus_config/config.dart';
|
||||||
|
|
||||||
|
/// A codec for decoding and encoding values based on their type using reflection.
|
||||||
|
///
|
||||||
|
/// This class uses the dart:mirrors library to introspect types and provide
|
||||||
|
/// appropriate decoding and encoding logic for various data types including
|
||||||
|
/// int, bool, Configuration subclasses, List, and Map.
|
||||||
|
///
|
||||||
|
/// The class supports:
|
||||||
|
/// - Decoding values from various input formats to their corresponding Dart types.
|
||||||
|
/// - Generating source code strings for decoding operations.
|
||||||
|
/// - Validating Configuration subclasses to ensure they have a default constructor.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final codec = MirrorTypeCodec(reflectType(SomeType));
|
||||||
|
/// final decodedValue = codec._decodeValue(inputValue);
|
||||||
|
/// final sourceCode = codec.source;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note: This class relies heavily on reflection, which may have performance
|
||||||
|
/// implications and is not supported in all Dart runtime environments.
|
||||||
class MirrorTypeCodec {
|
class MirrorTypeCodec {
|
||||||
|
/// Constructor for MirrorTypeCodec.
|
||||||
|
///
|
||||||
|
/// This constructor takes a [TypeMirror] as its parameter and initializes the codec.
|
||||||
|
/// It performs a validation check for Configuration subclasses to ensure they have
|
||||||
|
/// a default (unnamed) constructor with all optional parameters.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [type]: The TypeMirror representing the type for which this codec is being created.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// [StateError]: If the type is a subclass of Configuration and doesn't have
|
||||||
|
/// an unnamed constructor with all optional parameters.
|
||||||
|
///
|
||||||
|
/// The constructor specifically:
|
||||||
|
/// 1. Checks if the type is a subclass of Configuration.
|
||||||
|
/// 2. If so, it verifies the presence of a default constructor.
|
||||||
|
/// 3. Throws a StateError if the required constructor is missing, providing
|
||||||
|
/// a detailed error message to guide the developer.
|
||||||
MirrorTypeCodec(this.type) {
|
MirrorTypeCodec(this.type) {
|
||||||
if (type.isSubtypeOf(reflectType(Configuration))) {
|
if (type.isSubtypeOf(reflectType(Configuration))) {
|
||||||
final klass = type as ClassMirror;
|
final klass = type as ClassMirror;
|
||||||
|
@ -32,8 +69,34 @@ class MirrorTypeCodec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The [TypeMirror] representing the type for which this codec is created.
|
||||||
|
///
|
||||||
|
/// This field stores the reflection information about the type that this
|
||||||
|
/// [MirrorTypeCodec] instance is designed to handle. It is used throughout
|
||||||
|
/// the class to determine how to decode and encode values of this type.
|
||||||
final TypeMirror type;
|
final TypeMirror type;
|
||||||
|
|
||||||
|
/// Decodes a value based on its type using reflection.
|
||||||
|
///
|
||||||
|
/// This method takes a [dynamic] input value and decodes it according to the
|
||||||
|
/// type specified by this codec's [type] property. It supports decoding for:
|
||||||
|
/// - Integers
|
||||||
|
/// - Booleans
|
||||||
|
/// - Configuration subclasses
|
||||||
|
/// - Lists
|
||||||
|
/// - Maps
|
||||||
|
///
|
||||||
|
/// If the input type doesn't match any of these, the original value is returned.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [value]: The input value to be decoded.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// The decoded value, with its type corresponding to the codec's [type].
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// May throw exceptions if decoding fails, particularly for nested structures
|
||||||
|
/// like Lists and Maps.
|
||||||
dynamic _decodeValue(dynamic value) {
|
dynamic _decodeValue(dynamic value) {
|
||||||
if (type.isSubtypeOf(reflectType(int))) {
|
if (type.isSubtypeOf(reflectType(int))) {
|
||||||
return _decodeInt(value);
|
return _decodeInt(value);
|
||||||
|
@ -50,6 +113,21 @@ class MirrorTypeCodec {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes a boolean value from various input types.
|
||||||
|
///
|
||||||
|
/// This method handles the conversion of input values to boolean:
|
||||||
|
/// - If the input is a String, it returns true if the string is "true" (case-sensitive),
|
||||||
|
/// and false otherwise.
|
||||||
|
/// - For non-String inputs, it attempts to cast the value directly to a bool.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [value]: The input value to be decoded into a boolean.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A boolean representation of the input value.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// TypeError: If the input cannot be cast to a bool (for non-String inputs).
|
||||||
dynamic _decodeBool(dynamic value) {
|
dynamic _decodeBool(dynamic value) {
|
||||||
if (value is String) {
|
if (value is String) {
|
||||||
return value == "true";
|
return value == "true";
|
||||||
|
@ -58,6 +136,21 @@ class MirrorTypeCodec {
|
||||||
return value as bool;
|
return value as bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes an integer value from various input types.
|
||||||
|
///
|
||||||
|
/// This method handles the conversion of input values to integers:
|
||||||
|
/// - If the input is a String, it attempts to parse it as an integer.
|
||||||
|
/// - For non-String inputs, it attempts to cast the value directly to an int.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [value]: The input value to be decoded into an integer.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// An integer representation of the input value.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// FormatException: If the input String cannot be parsed as an integer.
|
||||||
|
/// TypeError: If the input cannot be cast to an int (for non-String inputs).
|
||||||
dynamic _decodeInt(dynamic value) {
|
dynamic _decodeInt(dynamic value) {
|
||||||
if (value is String) {
|
if (value is String) {
|
||||||
return int.parse(value);
|
return int.parse(value);
|
||||||
|
@ -66,6 +159,21 @@ class MirrorTypeCodec {
|
||||||
return value as int;
|
return value as int;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes a Configuration object from the given input.
|
||||||
|
///
|
||||||
|
/// This method creates a new instance of the Configuration subclass
|
||||||
|
/// represented by this codec's type, and then decodes the input object
|
||||||
|
/// into it.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [object]: The input object to be decoded into a Configuration instance.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new instance of the Configuration subclass, populated with the decoded data.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// May throw exceptions if the instantiation fails or if the decode
|
||||||
|
/// method of the Configuration subclass throws an exception.
|
||||||
Configuration _decodeConfig(dynamic object) {
|
Configuration _decodeConfig(dynamic object) {
|
||||||
final item = (type as ClassMirror).newInstance(Symbol.empty, []).reflectee
|
final item = (type as ClassMirror).newInstance(Symbol.empty, []).reflectee
|
||||||
as Configuration;
|
as Configuration;
|
||||||
|
@ -75,6 +183,26 @@ class MirrorTypeCodec {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes a List value based on the codec's type parameters.
|
||||||
|
///
|
||||||
|
/// This method creates a new List instance and populates it with decoded elements
|
||||||
|
/// from the input List. It uses an inner decoder to process each element according
|
||||||
|
/// to the type specified in the codec's type arguments.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [value]: The input List to be decoded.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new List containing the decoded elements.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// IntermediateException: If an error occurs during the decoding of any element.
|
||||||
|
/// The exception includes the index of the problematic element in its keyPath.
|
||||||
|
///
|
||||||
|
/// Note:
|
||||||
|
/// - The method creates a growable List.
|
||||||
|
/// - It uses reflection to create the new List instance.
|
||||||
|
/// - Each element is decoded using an inner decoder based on the first type argument.
|
||||||
List _decodeList(List value) {
|
List _decodeList(List value) {
|
||||||
final out = (type as ClassMirror).newInstance(const Symbol('empty'), [], {
|
final out = (type as ClassMirror).newInstance(const Symbol('empty'), [], {
|
||||||
const Symbol('growable'): true,
|
const Symbol('growable'): true,
|
||||||
|
@ -94,6 +222,27 @@ class MirrorTypeCodec {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes a Map value based on the codec's type parameters.
|
||||||
|
///
|
||||||
|
/// This method creates a new Map instance and populates it with decoded key-value pairs
|
||||||
|
/// from the input Map. It uses an inner decoder to process each value according
|
||||||
|
/// to the type specified in the codec's type arguments.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// [value]: The input Map to be decoded.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A new Map containing the decoded key-value pairs.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// StateError: If any key in the input Map is not a String.
|
||||||
|
/// IntermediateException: If an error occurs during the decoding of any value.
|
||||||
|
/// The exception includes the key of the problematic value in its keyPath.
|
||||||
|
///
|
||||||
|
/// Note:
|
||||||
|
/// - The method creates a new Map instance using reflection.
|
||||||
|
/// - It enforces that all keys must be Strings.
|
||||||
|
/// - Each value is decoded using an inner decoder based on the last type argument.
|
||||||
Map<dynamic, dynamic> _decodeMap(Map value) {
|
Map<dynamic, dynamic> _decodeMap(Map value) {
|
||||||
final map =
|
final map =
|
||||||
(type as ClassMirror).newInstance(Symbol.empty, []).reflectee as Map;
|
(type as ClassMirror).newInstance(Symbol.empty, []).reflectee as Map;
|
||||||
|
@ -117,10 +266,36 @@ class MirrorTypeCodec {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a string representation of the expected type for this codec.
|
||||||
|
///
|
||||||
|
/// This getter uses the [reflectedType] property of the [type] field
|
||||||
|
/// to obtain a string representation of the type that this codec is
|
||||||
|
/// expecting to handle. This is useful for generating type-specific
|
||||||
|
/// decoding logic or for debugging purposes.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [String] representing the name of the expected type.
|
||||||
String get expectedType {
|
String get expectedType {
|
||||||
return type.reflectedType.toString();
|
return type.reflectedType.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the source code for decoding a value based on its type.
|
||||||
|
///
|
||||||
|
/// This getter generates and returns a string containing Dart code that can be used
|
||||||
|
/// to decode a value of the type represented by this codec. The returned code varies
|
||||||
|
/// depending on the type:
|
||||||
|
///
|
||||||
|
/// - For [int], it returns code to parse integers from strings or cast to int.
|
||||||
|
/// - For [bool], it returns code to convert strings to booleans or cast to bool.
|
||||||
|
/// - For [Configuration] subclasses, it returns code to create and decode a new instance.
|
||||||
|
/// - For [List], it returns code to decode each element of the list.
|
||||||
|
/// - For [Map], it returns code to decode each value in the map.
|
||||||
|
/// - For any other type, it returns code that simply returns the input value unchanged.
|
||||||
|
///
|
||||||
|
/// The generated code assumes the input value is named 'v'.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [String] containing Dart code for decoding the value.
|
||||||
String get source {
|
String get source {
|
||||||
if (type.isSubtypeOf(reflectType(int))) {
|
if (type.isSubtypeOf(reflectType(int))) {
|
||||||
return _decodeIntSource;
|
return _decodeIntSource;
|
||||||
|
@ -137,6 +312,20 @@ class MirrorTypeCodec {
|
||||||
return "return v;";
|
return "return v;";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates source code for decoding a List value.
|
||||||
|
///
|
||||||
|
/// This getter creates a string containing Dart code that decodes a List
|
||||||
|
/// based on the codec's type parameters. The generated code:
|
||||||
|
/// - Creates a new List to store decoded elements.
|
||||||
|
/// - Defines an inner decoder function for processing each element.
|
||||||
|
/// - Iterates through the input List, decoding each element.
|
||||||
|
/// - Handles exceptions, wrapping them in IntermediateException with the index.
|
||||||
|
///
|
||||||
|
/// The decoder function uses the source code from the inner codec,
|
||||||
|
/// which is based on the first type argument of the List.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A String containing the Dart code for List decoding.
|
||||||
String get _decodeListSource {
|
String get _decodeListSource {
|
||||||
final typeParam = MirrorTypeCodec(type.typeArguments.first);
|
final typeParam = MirrorTypeCodec(type.typeArguments.first);
|
||||||
return """
|
return """
|
||||||
|
@ -159,6 +348,21 @@ return out;
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates source code for decoding a Map value.
|
||||||
|
///
|
||||||
|
/// This getter creates a string containing Dart code that decodes a Map
|
||||||
|
/// based on the codec's type parameters. The generated code:
|
||||||
|
/// - Creates a new Map to store decoded key-value pairs.
|
||||||
|
/// - Defines an inner decoder function for processing each value.
|
||||||
|
/// - Iterates through the input Map, ensuring all keys are Strings.
|
||||||
|
/// - Decodes each value using the inner decoder function.
|
||||||
|
/// - Handles exceptions, wrapping them in IntermediateException with the key.
|
||||||
|
///
|
||||||
|
/// The decoder function uses the source code from the inner codec,
|
||||||
|
/// which is based on the last type argument of the Map.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A String containing the Dart code for Map decoding.
|
||||||
String get _decodeMapSource {
|
String get _decodeMapSource {
|
||||||
final typeParam = MirrorTypeCodec(type.typeArguments.last);
|
final typeParam = MirrorTypeCodec(type.typeArguments.last);
|
||||||
return """
|
return """
|
||||||
|
@ -185,6 +389,17 @@ return map;
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates source code for decoding a Configuration object.
|
||||||
|
///
|
||||||
|
/// This getter returns a string containing Dart code that:
|
||||||
|
/// 1. Creates a new instance of the Configuration subclass represented by [expectedType].
|
||||||
|
/// 2. Calls the `decode` method on this new instance, passing in the input value 'v'.
|
||||||
|
/// 3. Returns the decoded Configuration object.
|
||||||
|
///
|
||||||
|
/// The generated code assumes the input value is named 'v'.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [String] containing Dart code for decoding a Configuration object.
|
||||||
String get _decodeConfigSource {
|
String get _decodeConfigSource {
|
||||||
return """
|
return """
|
||||||
final item = $expectedType();
|
final item = $expectedType();
|
||||||
|
@ -195,6 +410,17 @@ return map;
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates source code for decoding an integer value.
|
||||||
|
///
|
||||||
|
/// This getter returns a string containing Dart code that:
|
||||||
|
/// 1. Checks if the input value 'v' is a String.
|
||||||
|
/// 2. If it is a String, parses it to an integer using `int.parse()`.
|
||||||
|
/// 3. If it's not a String, casts the value directly to an int.
|
||||||
|
///
|
||||||
|
/// The generated code assumes the input value is named 'v'.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [String] containing Dart code for decoding an integer value.
|
||||||
String get _decodeIntSource {
|
String get _decodeIntSource {
|
||||||
return """
|
return """
|
||||||
if (v is String) {
|
if (v is String) {
|
||||||
|
@ -205,6 +431,17 @@ return map;
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates source code for decoding a boolean value.
|
||||||
|
///
|
||||||
|
/// This getter returns a string containing Dart code that:
|
||||||
|
/// 1. Checks if the input value 'v' is a String.
|
||||||
|
/// 2. If it is a String, returns true if it equals "true", false otherwise.
|
||||||
|
/// 3. If it's not a String, casts the value directly to a bool.
|
||||||
|
///
|
||||||
|
/// The generated code assumes the input value is named 'v'.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A [String] containing Dart code for decoding a boolean value.
|
||||||
String get _decodeBoolSource {
|
String get _decodeBoolSource {
|
||||||
return """
|
return """
|
||||||
if (v is String) {
|
if (v is String) {
|
||||||
|
@ -216,6 +453,24 @@ return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a property of a Configuration class, providing metadata and decoding capabilities.
|
||||||
|
///
|
||||||
|
/// This class encapsulates information about a single property within a Configuration
|
||||||
|
/// subclass, including its name, whether it's required, and how to decode its value.
|
||||||
|
///
|
||||||
|
/// It uses the [MirrorTypeCodec] to handle the decoding of values based on the property's type.
|
||||||
|
///
|
||||||
|
/// Key features:
|
||||||
|
/// - Extracts the property name from the [VariableMirror].
|
||||||
|
/// - Determines if the property is required based on its metadata.
|
||||||
|
/// - Provides access to the decoding logic through the [codec] field.
|
||||||
|
/// - Offers a method to decode input values, taking into account environment variables.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```dart
|
||||||
|
/// final property = MirrorConfigurationProperty(someVariableMirror);
|
||||||
|
/// final decodedValue = property.decode(inputValue);
|
||||||
|
/// ```
|
||||||
class MirrorConfigurationProperty {
|
class MirrorConfigurationProperty {
|
||||||
MirrorConfigurationProperty(this.property)
|
MirrorConfigurationProperty(this.property)
|
||||||
: codec = MirrorTypeCodec(property.type);
|
: codec = MirrorTypeCodec(property.type);
|
||||||
|
|
|
@ -8,21 +8,73 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:mirrors';
|
import 'dart:mirrors';
|
||||||
|
|
||||||
import 'package:protevus_config/config.dart';
|
import 'package:protevus_config/config.dart';
|
||||||
import 'package:protevus_runtime/runtime.dart';
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
|
||||||
|
/// ConfigurationRuntimeImpl is a class that extends ConfigurationRuntime and implements SourceCompiler.
|
||||||
|
///
|
||||||
|
/// This class is responsible for handling the runtime configuration of the application. It uses
|
||||||
|
/// Dart's mirror system to introspect and manipulate configuration objects at runtime.
|
||||||
|
///
|
||||||
|
/// Key features:
|
||||||
|
/// - Decodes configuration input from a Map into a strongly-typed Configuration object
|
||||||
|
/// - Validates the configuration to ensure all required fields are present
|
||||||
|
/// - Generates implementation code for decoding and validating configurations
|
||||||
|
/// - Collects and manages configuration properties
|
||||||
|
///
|
||||||
|
/// The class provides methods for decoding input, validating configurations, and compiling
|
||||||
|
/// source code for runtime configuration handling. It also includes utility methods for
|
||||||
|
/// collecting properties and generating implementation strings for decode and validate operations.
|
||||||
class ConfigurationRuntimeImpl extends ConfigurationRuntime
|
class ConfigurationRuntimeImpl extends ConfigurationRuntime
|
||||||
implements SourceCompiler {
|
implements SourceCompiler {
|
||||||
|
/// Constructs a ConfigurationRuntimeImpl instance for the given type.
|
||||||
|
///
|
||||||
|
/// The constructor initializes the type and properties of the configuration runtime.
|
||||||
|
/// It collects properties using the `_collectProperties` method.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - type: The ClassMirror representing the type of the configuration object.
|
||||||
ConfigurationRuntimeImpl(this.type) {
|
ConfigurationRuntimeImpl(this.type) {
|
||||||
// Should be done in the constructor so a type check could be run.
|
// Should be done in the constructor so a type check could be run.
|
||||||
properties = _collectProperties();
|
properties = _collectProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The ClassMirror representing the type of the configuration object.
|
||||||
|
///
|
||||||
|
/// This field stores the reflection information for the configuration class,
|
||||||
|
/// allowing for runtime introspection and manipulation of the configuration object.
|
||||||
final ClassMirror type;
|
final ClassMirror type;
|
||||||
|
|
||||||
|
/// A map of property names to MirrorConfigurationProperty objects.
|
||||||
|
///
|
||||||
|
/// This late-initialized field stores the configuration properties of the class.
|
||||||
|
/// Each key is a string representing the property name, and the corresponding value
|
||||||
|
/// is a MirrorConfigurationProperty object containing metadata about that property.
|
||||||
|
///
|
||||||
|
/// The properties are collected during the initialization of the ConfigurationRuntimeImpl
|
||||||
|
/// instance and are used for decoding, validating, and generating implementation code
|
||||||
|
/// for the configuration.
|
||||||
late final Map<String, MirrorConfigurationProperty> properties;
|
late final Map<String, MirrorConfigurationProperty> properties;
|
||||||
|
|
||||||
|
/// Decodes the input map into the given configuration object.
|
||||||
|
///
|
||||||
|
/// This method takes a [Configuration] object and a [Map] input, and populates
|
||||||
|
/// the configuration object with the decoded values from the input map.
|
||||||
|
///
|
||||||
|
/// The method performs the following steps:
|
||||||
|
/// 1. Creates a copy of the input map.
|
||||||
|
/// 2. Iterates through each property in the configuration.
|
||||||
|
/// 3. For each property, it attempts to decode the corresponding value from the input.
|
||||||
|
/// 4. If the decoded value is not null and of the correct type, it sets the value on the configuration object.
|
||||||
|
/// 5. After processing all properties, it checks if there are any unexpected keys left in the input map.
|
||||||
|
///
|
||||||
|
/// Throws a [ConfigurationException] if:
|
||||||
|
/// - A decoded value is of the wrong type.
|
||||||
|
/// - There are unexpected keys in the input map after processing all known properties.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - [configuration]: The Configuration object to be populated with decoded values.
|
||||||
|
/// - [input]: A Map containing the input values to be decoded.
|
||||||
@override
|
@override
|
||||||
void decode(Configuration configuration, Map input) {
|
void decode(Configuration configuration, Map input) {
|
||||||
final values = Map.from(input);
|
final values = Map.from(input);
|
||||||
|
@ -61,6 +113,24 @@ class ConfigurationRuntimeImpl extends ConfigurationRuntime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates the implementation string for the decode method.
|
||||||
|
///
|
||||||
|
/// This getter creates a String representation of the decode method implementation.
|
||||||
|
/// The generated code does the following:
|
||||||
|
/// 1. Creates a copy of the input map.
|
||||||
|
/// 2. Iterates through each property in the configuration.
|
||||||
|
/// 3. For each property:
|
||||||
|
/// - Retrieves the value from the input, considering environment variables.
|
||||||
|
/// - If a value exists, it attempts to decode it.
|
||||||
|
/// - Checks if the decoded value is of the expected type.
|
||||||
|
/// - If valid, assigns the decoded value to the configuration object.
|
||||||
|
/// 4. After processing all properties, it checks for any unexpected keys in the input.
|
||||||
|
///
|
||||||
|
/// The generated code includes proper error handling, throwing ConfigurationExceptions
|
||||||
|
/// for type mismatches or unexpected input keys.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A String containing the implementation code for the decode method.
|
||||||
String get decodeImpl {
|
String get decodeImpl {
|
||||||
final buf = StringBuffer();
|
final buf = StringBuffer();
|
||||||
|
|
||||||
|
@ -98,6 +168,22 @@ class ConfigurationRuntimeImpl extends ConfigurationRuntime
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates the given configuration object to ensure all required properties are present.
|
||||||
|
///
|
||||||
|
/// This method performs the following steps:
|
||||||
|
/// 1. Creates a mirror of the configuration object for reflection.
|
||||||
|
/// 2. Iterates through all properties of the configuration.
|
||||||
|
/// 3. For each property, it checks if:
|
||||||
|
/// - The property is required.
|
||||||
|
/// - The property value is null or cannot be accessed.
|
||||||
|
/// 4. Collects a list of all required properties that are missing or null.
|
||||||
|
/// 5. If any required properties are missing, it throws a ConfigurationException.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - configuration: The Configuration object to be validated.
|
||||||
|
///
|
||||||
|
/// Throws:
|
||||||
|
/// - ConfigurationException: If any required properties are missing or null.
|
||||||
@override
|
@override
|
||||||
void validate(Configuration configuration) {
|
void validate(Configuration configuration) {
|
||||||
final configMirror = reflect(configuration);
|
final configMirror = reflect(configuration);
|
||||||
|
@ -121,6 +207,23 @@ class ConfigurationRuntimeImpl extends ConfigurationRuntime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collects and returns a map of configuration properties for the current type.
|
||||||
|
///
|
||||||
|
/// This method traverses the class hierarchy, starting from the current type
|
||||||
|
/// up to (but not including) the Configuration class, collecting all non-static
|
||||||
|
/// and non-private variable declarations. It then creates a map where:
|
||||||
|
///
|
||||||
|
/// - Keys are the string names of the properties
|
||||||
|
/// - Values are MirrorConfigurationProperty objects created from the VariableMirrors
|
||||||
|
///
|
||||||
|
/// The method performs the following steps:
|
||||||
|
/// 1. Initializes an empty list to store VariableMirror objects.
|
||||||
|
/// 2. Traverses the class hierarchy, collecting relevant VariableMirrors.
|
||||||
|
/// 3. Creates a map from the collected VariableMirrors.
|
||||||
|
/// 4. Returns the resulting map of property names to MirrorConfigurationProperty objects.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Map<String, MirrorConfigurationProperty> representing the configuration properties.
|
||||||
Map<String, MirrorConfigurationProperty> _collectProperties() {
|
Map<String, MirrorConfigurationProperty> _collectProperties() {
|
||||||
final declarations = <VariableMirror>[];
|
final declarations = <VariableMirror>[];
|
||||||
|
|
||||||
|
@ -142,6 +245,22 @@ class ConfigurationRuntimeImpl extends ConfigurationRuntime
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates the implementation string for the validate method.
|
||||||
|
///
|
||||||
|
/// This getter creates a String representation of the validate method implementation.
|
||||||
|
/// The generated code does the following:
|
||||||
|
/// 1. Initializes a list to store missing keys.
|
||||||
|
/// 2. Iterates through each property in the configuration.
|
||||||
|
/// 3. For each property:
|
||||||
|
/// - Attempts to retrieve the property value from the configuration object.
|
||||||
|
/// - Checks if the property is required and its value is null.
|
||||||
|
/// - If required and null, or if an error occurs during retrieval, adds the property name to the missing keys list.
|
||||||
|
/// 4. After checking all properties, throws a ConfigurationException if any keys are missing.
|
||||||
|
///
|
||||||
|
/// The generated code includes error handling to catch any issues during property access.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A String containing the implementation code for the validate method.
|
||||||
String get validateImpl {
|
String get validateImpl {
|
||||||
final buf = StringBuffer();
|
final buf = StringBuffer();
|
||||||
|
|
||||||
|
@ -170,6 +289,23 @@ class ConfigurationRuntimeImpl extends ConfigurationRuntime
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compiles the configuration runtime implementation into a string representation.
|
||||||
|
///
|
||||||
|
/// This method generates the source code for a ConfigurationRuntimeImpl class
|
||||||
|
/// that extends ConfigurationRuntime. The generated class includes implementations
|
||||||
|
/// for the 'decode' and 'validate' methods.
|
||||||
|
///
|
||||||
|
/// The method performs the following steps:
|
||||||
|
/// 1. Retrieves import directives for the current type and adds them to the generated code.
|
||||||
|
/// 2. Adds an import for the intermediate_exception.dart file.
|
||||||
|
/// 3. Creates an instance of ConfigurationRuntimeImpl.
|
||||||
|
/// 4. Generates the class definition with implementations of decode and validate methods.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - ctx: A BuildContext object used to retrieve import directives.
|
||||||
|
///
|
||||||
|
/// Returns:
|
||||||
|
/// A Future<String> containing the generated source code for the ConfigurationRuntimeImpl class.
|
||||||
@override
|
@override
|
||||||
Future<String> compile(BuildContext ctx) async {
|
Future<String> compile(BuildContext ctx) async {
|
||||||
final directives = await ctx.getImportDirectives(
|
final directives = await ctx.getImportDirectives(
|
||||||
|
|
Loading…
Reference in a new issue