From 7b0a8738ce5183d03aa63b226eb82a6ea33e47f3 Mon Sep 17 00:00:00 2001 From: Patrick Stewart Date: Fri, 5 Jul 2024 01:41:43 -0700 Subject: [PATCH] add: adding ported files from symfony httpfoundation component --- packages/http/lib/foundation.dart | 27 ++ packages/http/lib/foundation_exception.dart | 23 + packages/http/lib/src/foundation/cookie.dart | 2 +- .../exception/bad_request_exception.dart | 51 +++ .../request_exception_interface.dart | 22 + .../exception/unexpected_value_exception.dart | 44 ++ .../lib/src/foundation/parameter_bag.dart | 420 ++++++++++++++++++ .../src/foundation/response_header_bag.dart | 4 +- 8 files changed, 589 insertions(+), 4 deletions(-) create mode 100644 packages/http/lib/foundation.dart create mode 100644 packages/http/lib/foundation_exception.dart create mode 100644 packages/http/lib/src/foundation/exception/bad_request_exception.dart create mode 100644 packages/http/lib/src/foundation/exception/request_exception_interface.dart create mode 100644 packages/http/lib/src/foundation/exception/unexpected_value_exception.dart create mode 100644 packages/http/lib/src/foundation/parameter_bag.dart diff --git a/packages/http/lib/foundation.dart b/packages/http/lib/foundation.dart new file mode 100644 index 0000000..1c1ee06 --- /dev/null +++ b/packages/http/lib/foundation.dart @@ -0,0 +1,27 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/// This library exports various foundation classes and utilities for web-related operations. +/// +/// It includes: +/// - [CountableInterface] for objects that can be counted +/// - [Cookie] for handling HTTP cookies +/// - [HeaderUtils] for working with HTTP headers +/// - [HeaderBag] for managing collections of HTTP headers +/// - [ParameterBag] for handling request parameters +/// - [ResponseHeaderBag] for managing response headers +library; + +export 'src/foundation/countable_interface.dart'; +export 'src/foundation/stringable_interface.dart'; +export 'src/foundation/cookie.dart'; +export 'src/foundation/header_utils.dart'; +export 'src/foundation/header_bag.dart'; +export 'src/foundation/parameter_bag.dart'; +export 'src/foundation/response_header_bag.dart'; \ No newline at end of file diff --git a/packages/http/lib/foundation_exception.dart b/packages/http/lib/foundation_exception.dart new file mode 100644 index 0000000..aa34626 --- /dev/null +++ b/packages/http/lib/foundation_exception.dart @@ -0,0 +1,23 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/// This library exports various exception classes and interfaces. +/// +/// The exported classes include: +/// - `BadRequestException`: Used for handling bad HTTP requests. +/// - `RequestExceptionInterface`: An interface for request-related exceptions. +/// - `UnexpectedValueException`: Used when an unexpected value is encountered. +/// +/// These exports allow other parts of the application to use these +/// exception classes and interfaces without needing to import them directly. +library; + +export 'src/foundation/exception/bad_request_exception.dart'; +export 'src/foundation/exception/request_exception_interface.dart'; +export 'src/foundation/exception/unexpected_value_exception.dart'; \ No newline at end of file diff --git a/packages/http/lib/src/foundation/cookie.dart b/packages/http/lib/src/foundation/cookie.dart index db0203d..3a53a7d 100644 --- a/packages/http/lib/src/foundation/cookie.dart +++ b/packages/http/lib/src/foundation/cookie.dart @@ -10,7 +10,7 @@ */ import 'dart:math'; -import 'header_utils.dart'; +import 'package:protevus_http/foundation.dart'; /// Represents an HTTP cookie. /// diff --git a/packages/http/lib/src/foundation/exception/bad_request_exception.dart b/packages/http/lib/src/foundation/exception/bad_request_exception.dart new file mode 100644 index 0000000..b8523b8 --- /dev/null +++ b/packages/http/lib/src/foundation/exception/bad_request_exception.dart @@ -0,0 +1,51 @@ +/* + * This file is part of the Protevus Platform. + * This file is a port of the symfony BadRequestException.php class to Dart + * + * (C) Protevus + * (C) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'package:protevus_http/foundation_exception.dart'; + +/// Exception thrown when a user sends a malformed request. +/// +/// This exception is used to indicate that the client's request was invalid or +/// could not be served. It extends [UnexpectedValueException] and implements +/// [RequestExceptionInterface]. +/// +/// Example usage: +/// ```dart +/// throw BadRequestException('Invalid parameter: id must be a positive integer'); +/// ``` +class BadRequestException extends UnexpectedValueException implements RequestExceptionInterface { + /// Creates a new [BadRequestException] with an optional error message. + /// + /// The [message] parameter is passed to the superclass constructor. + /// If not provided, the exception will be created with an empty message. + /// + /// Example: + /// ```dart + /// throw BadRequestException('Invalid input'); + /// ``` + BadRequestException([super.message]); + + /// Returns a string representation of the [BadRequestException]. + /// + /// If the exception message is empty, it returns 'BadRequestException'. + /// Otherwise, it returns 'BadRequestException: ' followed by the exception message. + /// + /// Example: + /// ```dart + /// var exception = BadRequestException('Invalid input'); + /// print(exception.toString()); // Output: BadRequestException: Invalid input + /// ``` + @override + String toString() { + return message.isEmpty ? 'BadRequestException' : 'BadRequestException: $message'; + } +} + diff --git a/packages/http/lib/src/foundation/exception/request_exception_interface.dart b/packages/http/lib/src/foundation/exception/request_exception_interface.dart new file mode 100644 index 0000000..0a883fc --- /dev/null +++ b/packages/http/lib/src/foundation/exception/request_exception_interface.dart @@ -0,0 +1,22 @@ +/* + * This file is part of the Protevus Platform. + * This file is a port of the symfony RequestExceptionInterface.php class to Dart + * + * (C) Protevus + * (C) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/// An interface for exceptions related to HTTP requests. +/// +/// Implementations of this interface are intended to be used for exceptions +/// that should trigger an HTTP 400 (Bad Request) response in the application. +/// +/// This interface doesn't declare any methods, but serves as a marker +/// to identify exceptions specifically related to request handling. +abstract class RequestExceptionInterface { + +} + diff --git a/packages/http/lib/src/foundation/exception/unexpected_value_exception.dart b/packages/http/lib/src/foundation/exception/unexpected_value_exception.dart new file mode 100644 index 0000000..fd155c7 --- /dev/null +++ b/packages/http/lib/src/foundation/exception/unexpected_value_exception.dart @@ -0,0 +1,44 @@ +/* + * This file is part of the Protevus Platform. + * This file is a port of the symfony UnexpectedValueException.php class to Dart + * + * (C) Protevus + * (C) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'dart:core'; + +/// Exception thrown if a value does not match with a set of values. +/// +/// Typically this happens when a function calls another function and expects +/// the return value to be of a certain type or value not including arithmetic +/// or buffer related errors. +class UnexpectedValueException implements Exception { + /// The error message associated with this exception. + /// + /// This is a final String that stores the descriptive message for the + /// UnexpectedValueException. It provides details about why the exception + /// was thrown and can be used for logging or displaying error information. + final String message; + + /// Constructor for UnexpectedValueException. + /// + /// Creates a new instance of UnexpectedValueException with an optional error message. + /// + /// @param message The error message for this exception. Defaults to an empty string. + UnexpectedValueException([this.message = '']); + + /// Returns a string representation of the UnexpectedValueException. + /// + /// This method overrides the default toString() method to provide a more + /// descriptive string representation of the exception. If the exception + /// message is empty, it returns just the exception name. Otherwise, it + /// returns the exception name followed by a colon and the error message. + /// + /// @return A string representation of the UnexpectedValueException. + @override + String toString() => message.isEmpty ? 'UnexpectedValueException' : 'UnexpectedValueException: $message'; +} diff --git a/packages/http/lib/src/foundation/parameter_bag.dart b/packages/http/lib/src/foundation/parameter_bag.dart new file mode 100644 index 0000000..761b956 --- /dev/null +++ b/packages/http/lib/src/foundation/parameter_bag.dart @@ -0,0 +1,420 @@ +/* + * This file is part of the Protevus Platform. + * This file is a port of the symfony ParameterBag.php class to Dart + * + * (C) Protevus + * (C) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'package:protevus_http/foundation.dart'; +import 'package:protevus_http/foundation_exception.dart'; + +/// ParameterBag is an abstract container for key/value pairs. +/// It implements Iterable> and Countable interfaces. +abstract class ParameterBag implements Iterable>, Countable { + /// The underlying map containing the parameters. + /// + /// This getter provides access to the internal map that stores all the key-value pairs + /// of parameters. The keys are of type [String], and the values can be of any type, + /// hence [dynamic]. + /// + /// This map is the core data structure of the ParameterBag, allowing for storage and + /// retrieval of various parameters used throughout the application. + Map get parameters; + + /// Returns all parameters or a specific nested parameter. + /// + /// If [key] is null, this method returns all parameters as a [Map]. + /// If [key] is provided, it returns the value associated with that key, which must be + /// a [Map]. If the value is not a Map, it throws a [BadRequestException]. + /// + /// Parameters: + /// [key] - Optional. The key of the nested parameter to retrieve. + /// + /// Returns: + /// A [Map] containing either all parameters or the nested parameter. + /// + /// Throws: + /// [BadRequestException] if the value for the given key is not a Map. + Map all([String? key]) { + if (key == null) { + return parameters; + } + + final value = parameters[key]; + if (value is! Map) { + throw BadRequestException( + 'Unexpected value for parameter "$key": expecting "Map", got "${value.runtimeType}".'); + } + + return value; + } + + /// Returns a list of all parameter keys. + /// + /// This method retrieves all the keys from the [parameters] map and returns them + /// as a [List]. This can be useful when you need to iterate over or + /// inspect all the keys in the parameter bag without accessing their values. + /// + /// Returns: + /// A [List] containing all the keys from the parameters map. + List keys() { + return parameters.keys.toList(); + } + + /// Replaces the current parameters with a new set of parameters. + /// + /// This method completely replaces the existing parameters in the ParameterBag + /// with the new parameters provided in the [parameters] argument. + /// + /// Parameters: + /// [parameters] - A Map containing the new set of parameters + /// that will replace the existing ones. + /// + /// Example: + /// parameterBag.replace({'key1': 'value1', 'key2': 42}); + void replace(Map parameters); + + /// Adds new parameters to the existing set of parameters. + /// + /// This method merges the provided [parameters] with the existing parameters + /// in the ParameterBag. If a key already exists, its value will be updated + /// with the new value from the provided map. + /// + /// Parameters: + /// [parameters] - A Map containing the new parameters + /// to be added to the existing set. + /// + /// Example: + /// parameterBag.add({'key1': 'newValue', 'key3': 'value3'}); + void add(Map parameters); + + /// Retrieves the value associated with the given key from the parameters. + /// + /// This method searches for the specified [key] in the parameters map. + /// If the key is found, it returns the corresponding value. + /// If the key is not found, it returns the [defaultValue]. + /// + /// Parameters: + /// [key] - The key to look up in the parameters map. + /// [defaultValue] - Optional. The value to return if the key is not found. + /// If not provided, it defaults to null. + /// + /// Returns: + /// The value associated with the key if found, otherwise the defaultValue. + /// + /// Example: + /// var value = parameterBag.get('username', 'guest'); + dynamic get(String key, [dynamic defaultValue]) { + return parameters.containsKey(key) ? parameters[key] : defaultValue; + } + + /// Sets a parameter value for the given key. + /// + /// This method adds or updates a parameter in the ParameterBag. + /// If the key already exists, its value will be updated. + /// If the key doesn't exist, a new key-value pair will be added. + /// + /// Parameters: + /// [key] - The string key for the parameter. + /// [value] - The value to be associated with the key. Can be of any type. + /// + /// Example: + /// parameterBag.set('username', 'john_doe'); + /// parameterBag.set('age', 30); + void set(String key, dynamic value); + + /// Checks if a parameter with the given key exists in the ParameterBag. + /// + /// This method determines whether the specified [key] is present in the + /// parameters map. It returns true if the key exists, and false otherwise. + /// + /// Parameters: + /// [key] - The string key to check for existence in the parameters map. + /// + /// Returns: + /// A boolean value: true if the key exists, false otherwise. + /// + /// Example: + /// if (parameterBag.has('username')) { + /// // Do something with the username parameter + /// } + bool has(String key) { + return parameters.containsKey(key); + } + + /// Removes a parameter from the ParameterBag. + /// + /// This method removes the key-value pair associated with the given [key] + /// from the parameters map. If the key doesn't exist, this method does nothing. + /// + /// Parameters: + /// [key] - The string key of the parameter to be removed. + /// + /// Example: + /// parameterBag.remove('username'); + void remove(String key); + + /// Returns the alphabetic characters of the parameter value. + /// + /// This method retrieves the value associated with the given [key] as a string, + /// and then removes all non-alphabetic characters from it. If the key doesn't + /// exist or its value is empty, it returns the [defaultValue]. + /// + /// Parameters: + /// [key] - The key of the parameter to retrieve and process. + /// [defaultValue] - Optional. The value to return if the key is not found + /// or its value is empty. Defaults to an empty string. + /// + /// Returns: + /// A string containing only alphabetic characters (a-z and A-Z) from the + /// original parameter value. + /// + /// Example: + /// parameterBag.set('mixed', 'abc123XYZ!@#'); + /// print(parameterBag.getAlpha('mixed')); // Outputs: 'abcXYZ' + String getAlpha(String key, [String defaultValue = '']) { + return getString(key, defaultValue).replaceAll(RegExp(r'[^a-zA-Z]'), ''); + } + + /// Returns the alphanumeric characters of the parameter value. + /// + /// This method retrieves the value associated with the given [key] as a string, + /// and then removes all non-alphanumeric characters from it. If the key doesn't + /// exist or its value is empty, it returns the [defaultValue]. + /// + /// Parameters: + /// [key] - The key of the parameter to retrieve and process. + /// [defaultValue] - Optional. The value to return if the key is not found + /// or its value is empty. Defaults to an empty string. + /// + /// Returns: + /// A string containing only alphanumeric characters (a-z, A-Z, and 0-9) from the + /// original parameter value. + /// + /// Example: + /// parameterBag.set('mixed', 'abc123XYZ!@#'); + /// print(parameterBag.getAlnum('mixed')); // Outputs: 'abc123XYZ' + String getAlnum(String key, [String defaultValue = '']) { + return getString(key, defaultValue).replaceAll(RegExp(r'[^a-zA-Z0-9]'), ''); + } + + /// Returns only the digit characters from the parameter value. + /// + /// This method retrieves the value associated with the given [key] as a string, + /// and then removes all non-digit characters from it. If the key doesn't + /// exist or its value is empty, it returns the [defaultValue]. + /// + /// Parameters: + /// [key] - The key of the parameter to retrieve and process. + /// [defaultValue] - Optional. The value to return if the key is not found + /// or its value is empty. Defaults to an empty string. + /// + /// Returns: + /// A string containing only digit characters (0-9) from the original parameter value. + /// + /// Example: + /// parameterBag.set('mixed', 'abc123XYZ!@#'); + /// print(parameterBag.getDigits('mixed')); // Outputs: '123' + String getDigits(String key, [String defaultValue = '']) { + return getString(key, defaultValue).replaceAll(RegExp(r'[^0-9]'), ''); + } + + /// Returns the parameter value as a string. + /// + /// This method retrieves the value associated with the given [key] and converts it to a string. + /// If the key doesn't exist, it returns the [defaultValue]. + /// + /// Parameters: + /// [key] - The key of the parameter to retrieve. + /// [defaultValue] - Optional. The value to return if the key is not found. + /// Defaults to an empty string. + /// + /// Returns: + /// A string representation of the parameter value. + /// + /// Throws: + /// [UnexpectedValueException] if the value cannot be converted to a string + /// (i.e., if it's not a String, num, or bool). + /// + /// Example: + /// parameterBag.set('number', 42); + /// print(parameterBag.getString('number')); // Outputs: '42' + String getString(String key, [String defaultValue = '']) { + final value = get(key, defaultValue); + if (value is! String && value is! num && value is! bool) { + throw UnexpectedValueException( + 'Parameter value "$key" cannot be converted to "String".'); + } + return value.toString(); + } + + /// Returns the parameter value converted to an integer. + /// + /// This method retrieves the value associated with the given [key] and attempts to convert it to an integer. + /// If the key doesn't exist or the value cannot be converted to an integer, it returns the [defaultValue]. + /// + /// Parameters: + /// [key] - The key of the parameter to retrieve. + /// [defaultValue] - Optional. The value to return if the key is not found or the value cannot be converted to an integer. + /// Defaults to 0. + /// + /// Returns: + /// An integer representation of the parameter value. + /// + /// Example: + /// parameterBag.set('number', '42'); + /// print(parameterBag.getInt('number')); // Outputs: 42 + /// print(parameterBag.getInt('nonexistent', 10)); // Outputs: 10 + int getInt(String key, [int defaultValue = 0]) { + final value = get(key, defaultValue); + if (value is int) return value; + if (value is String) { + return int.tryParse(value) ?? defaultValue; + } + return defaultValue; + } + + /// Returns the parameter value converted to a boolean. + /// + /// This method retrieves the value associated with the given [key] and attempts to convert it to a boolean. + /// If the key doesn't exist, it returns the [defaultValue]. + /// + /// The conversion rules are as follows: + /// - If the value is already a boolean, it is returned as-is. + /// - If the value is a string, it returns true if the string is 'true' (case-insensitive) or '1'. + /// - For all other cases, it returns the [defaultValue]. + /// + /// Parameters: + /// [key] - The key of the parameter to retrieve. + /// [defaultValue] - Optional. The value to return if the key is not found or the value cannot be converted to a boolean. + /// Defaults to false. + /// + /// Returns: + /// A boolean representation of the parameter value. + /// + /// Example: + /// parameterBag.set('flag1', 'true'); + /// parameterBag.set('flag2', '1'); + /// parameterBag.set('flag3', 'false'); + /// print(parameterBag.getBoolean('flag1')); // Outputs: true + /// print(parameterBag.getBoolean('flag2')); // Outputs: true + /// print(parameterBag.getBoolean('flag3')); // Outputs: false + /// print(parameterBag.getBoolean('nonexistent')); // Outputs: false + bool getBoolean(String key, [bool defaultValue = false]) { + final value = get(key, defaultValue); + if (value is bool) return value; + if (value is String) { + return value.toLowerCase() == 'true' || value == '1'; + } + return defaultValue; + } + + /// Returns the parameter value converted to an enum. + /// + /// This method retrieves the value associated with the given [key] and attempts to convert it + /// to an enum of type [T]. The method compares the string representation of each enum value + /// (without the enum type prefix) to the parameter value. + /// + /// Parameters: + /// [key] - The key of the parameter to retrieve. + /// [values] - A list of all possible enum values of type [T]. + /// [defaultValue] - Optional. The value to return if the key is not found or the value + /// cannot be converted to an enum. Defaults to null. + /// + /// Returns: + /// An enum value of type [T] if a match is found, otherwise returns the [defaultValue]. + /// + /// Throws: + /// [UnexpectedValueException] if the value exists but cannot be converted to an enum. + /// + /// Example: + /// enum Color { red, green, blue } + /// parameterBag.set('color', 'red'); + /// var color = parameterBag.getEnum('color', Color.values); // Returns Color.red + T? getEnum(String key, List values, [T? defaultValue]) { + final value = get(key); + if (value == null) return defaultValue; + try { + return values.firstWhere((e) => e.toString().split('.').last == value); + } catch (e) { + throw UnexpectedValueException( + 'Parameter "$key" cannot be converted to enum: ${e.toString()}'); + } + } + + /// Filters and transforms a parameter value using a provided function. + /// + /// This method retrieves the value associated with the given [key] and applies + /// a [filterFunction] to transform or validate it. If the key doesn't exist, + /// it returns the [defaultValue]. + /// + /// Parameters: + /// [key] - The key of the parameter to retrieve and filter. + /// [defaultValue] - Optional. The value to return if the key is not found. + /// [filterFunction] - A function that takes the parameter value as input + /// and returns a transformed or validated value. + /// + /// Returns: + /// The filtered and transformed parameter value, or the defaultValue if + /// the key is not found. + /// + /// Throws: + /// [UnexpectedValueException] if the filterFunction throws an exception, + /// indicating that the parameter value is invalid. + /// + /// Example: + /// var age = parameterBag.filter('age', + /// defaultValue: 0, + /// filterFunction: (value) => int.parse(value.toString())); + dynamic filter(String key, {dynamic defaultValue, required String Function(dynamic) filterFunction}) { + final value = get(key, defaultValue); + if (value == null) return defaultValue; + + try { + return filterFunction(value); + } catch (e) { + throw UnexpectedValueException('Parameter value "$key" is invalid: ${e.toString()}'); + } + } + + /// Returns an iterator for the entries in the parameters map. + /// + /// This getter provides an iterator that allows for traversing all key-value + /// pairs (entries) in the underlying parameters map. It's particularly useful + /// for iterating over all parameters in the ParameterBag. + /// + /// The iterator yields [MapEntry] objects, where each entry contains a String + /// key and a dynamic value, corresponding to a parameter in the ParameterBag. + /// + /// This implementation is part of the [Iterable] interface, allowing + /// ParameterBag to be used in for-in loops and with other Iterable methods. + /// + /// Returns: + /// An [Iterator] of [MapEntry] for the parameters map. + /// + /// Example: + /// for (var entry in parameterBag) { + /// print('${entry.key}: ${entry.value}'); + /// } + @override + Iterator> get iterator => parameters.entries.iterator; + + /// Returns the number of parameters in the ParameterBag. + /// + /// This getter provides the count of key-value pairs in the underlying + /// parameters map. It's an implementation of the [Countable] interface. + /// + /// Returns: + /// An integer representing the number of parameters stored in the ParameterBag. + /// + /// Example: + /// int parameterCount = parameterBag.count; + /// print('Number of parameters: $parameterCount'); + @override + int get count => parameters.length; +} + diff --git a/packages/http/lib/src/foundation/response_header_bag.dart b/packages/http/lib/src/foundation/response_header_bag.dart index f0009fb..d7cd3fd 100644 --- a/packages/http/lib/src/foundation/response_header_bag.dart +++ b/packages/http/lib/src/foundation/response_header_bag.dart @@ -9,9 +9,7 @@ * file that was distributed with this source code. */ -import 'cookie.dart'; -import 'header_utils.dart'; -import 'header_bag.dart'; +import 'package:protevus_http/foundation.dart'; /// ResponseHeaderBag is a container for HTTP response headers. ///