add: working on request class port from symfony

This commit is contained in:
Patrick Stewart 2024-07-14 20:14:03 -07:00
parent b4d9009266
commit f88021d0ce
7 changed files with 1590 additions and 3 deletions

View file

@ -0,0 +1,18 @@
import 'package:protevus_http/foundation_exception.dart';
/// Exception thrown when an HTTP request contains headers with conflicting information.
///
/// This exception is a subclass of [UnexpectedValueException] and implements
/// [RequestExceptionInterface]. It is used to indicate that the headers in an
/// HTTP request contain conflicting or inconsistent information.
///
/// Example usage:
/// ```dart
/// throw ConflictingHeadersException('Content-Type and Content-Encoding headers are incompatible');
/// ```
/// @author Magnus Nordlander <magnus@fervo.se>
class ConflictingHeadersException extends UnexpectedValueException
implements RequestExceptionInterface {
/// Creates a new instance of [ConflictingHeadersException].
ConflictingHeadersException([super.message]);
}

View file

@ -0,0 +1,20 @@
import 'package:protevus_http/foundation_exception.dart';
/// This exception is typically thrown when there's an issue with JSON parsing,
/// serialization, or deserialization. It extends [UnexpectedValueException]
/// and implements [RequestExceptionInterface].
///
/// Example usage:
/// ```dart
/// try {
/// // Some JSON operation
/// } catch (e) {
/// throw JsonException('Failed to parse JSON: $e');
/// }
/// ```
/// @author Magnus Nordlander <magnus@fervo.se>
class JsonException extends UnexpectedValueException
implements RequestExceptionInterface {
/// Creates a new instance of [JsonException].
JsonException([super.message]);
}

View file

@ -0,0 +1,15 @@
import 'package:protevus_http/foundation_exception.dart';
import 'package:protevus_mime/mime_exception.dart';
/// Raised when a session does not exist. This happens in the following cases:
/// - the session is not enabled
/// - attempt to read a session outside a request context (i.e., CLI script).
class SessionNotFoundException extends LogicException
implements RequestExceptionInterface {
/// Creates a new [SessionNotFoundException] with the given [message], [code], and [previous] exception.
SessionNotFoundException({
String message = 'There is currently no session available.',
int code = 0,
Exception? previous,
}) : super(message, code, previous);
}

View file

@ -0,0 +1,19 @@
import 'package:protevus_http/foundation_exception.dart';
/// Exception thrown when a suspicious operation is detected during an HTTP request.
///
/// This exception is a subclass of [UnexpectedValueException] and implements
/// [RequestExceptionInterface]. It is used to indicate that a potentially
/// unsafe or unexpected operation has been attempted or detected during
/// the processing of an HTTP request.
///
/// Example usage:
/// ```dart
/// throw SuspiciousOperationException('Attempted to access restricted resource');
/// ```
/// @author Magnus Nordlander <magnus@fervo.se>
class SuspiciousOperationException extends UnexpectedValueException
implements RequestExceptionInterface {
/// Creates a new instance of [ConflictingHeadersException].
SuspiciousOperationException([super.message]);
}

View file

@ -0,0 +1,130 @@
import 'dart:io';
import 'dart:collection';
import 'package:protevus_http/foundation.dart';
import 'package:protevus_http/foundation_file.dart';
import 'package:protevus_mime/mime_exception.dart';
//import 'package:http/http.dart' as http;
/// FileBag is a container for uploaded files.
///
/// This class is ported from Symfony's HttpFoundation component.
class FileBag extends ParameterBag
with IterableMixin<MapEntry<String, dynamic>> {
static const List<String> _fileKeys = [
'error',
'full_path',
'name',
'size',
'tmp_name',
'type'
];
/// Constructs a FileBag instance.
///
/// [parameters] is an array of HTTP files.
FileBag([Map<String, dynamic> parameters = const {}]) {
replace(parameters);
}
/// Replaces the current files with a new set.
@override
void replace(Map<String, dynamic> files) {
parameters.clear();
add(files);
}
/// Sets a file in the bag.
@override
void set(String key, dynamic value) {
if (value is! Map<String, dynamic> && value is! UploadedFile) {
throw InvalidArgumentException(
'An uploaded file must be a Map or an instance of UploadedFile.');
}
super.set(key, _convertFileInformation(value));
}
/// Adds multiple files to the bag.
@override
void add(Map<String, dynamic> files) {
files.forEach((key, file) {
set(key, file);
});
}
/// Converts uploaded files to UploadedFile instances.
dynamic _convertFileInformation(dynamic file) {
if (file is UploadedFile) {
return file;
}
if (file is Map<String, dynamic>) {
file = _fixDartFilesMap(file);
List<String> keys = (file.keys.toList()..add('full_path'))..sort();
if (listEquals(_fileKeys, keys)) {
if (file['error'] == HttpStatus.noContent) {
return null;
} else {
return UploadedFile(
file['tmp_name'],
file['full_path'] ?? file['name'],
file['type'],
file['error'],
false,
);
}
} else {
return file.map((key, value) {
if (value is UploadedFile || value is Map<String, dynamic>) {
return MapEntry(key, _convertFileInformation(value));
}
return MapEntry(key, value);
});
}
}
return file;
}
/// Fixes a malformed Dart file upload map.
///
/// This method is equivalent to PHP's fixPhpFilesArray.
Map<String, dynamic> _fixDartFilesMap(Map<String, dynamic> data) {
List<String> keys = (data.keys.toList()..add('full_path'))..sort();
if (!listEquals(_fileKeys, keys) ||
!data.containsKey('name') ||
data['name'] is! Map) {
return data;
}
Map<String, dynamic> files = Map.from(data);
for (String k in _fileKeys) {
files.remove(k);
}
(data['name'] as Map).forEach((key, name) {
files[key] = _fixDartFilesMap({
'error': data['error'][key],
'name': name,
'type': data['type'][key],
'tmp_name': data['tmp_name'][key],
'size': data['size'][key],
if (data.containsKey('full_path') && data['full_path'].containsKey(key))
'full_path': data['full_path'][key],
});
});
return files;
}
}
/// Utility function to compare two lists for equality.
bool listEquals<T>(List<T> a, List<T> b) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,57 @@
import 'package:protevus_mime/src/exception/exception_interface.dart';
class LogicException implements Exception {
final String message;
final int code;
final Exception? previous;
final StackTrace? stackTrace;
class LogicException extends StateError implements ExceptionInterface {
LogicException(super.message);
LogicException(
[this.message = "", this.code = 0, this.previous, this.stackTrace]);
@override
String toString() {
return "LogicException: $message";
}
String getMessage() {
return message;
}
Exception? getPrevious() {
return previous;
}
int getCode() {
return code;
}
String getFile() {
final frames = stackTrace?.toString().split('\n');
if (frames != null && frames.isNotEmpty) {
final frame = frames.first;
final fileInfo = frame.split(' ').last;
return fileInfo.split(':').first;
}
return "";
}
int getLine() {
final frames = stackTrace?.toString().split('\n');
if (frames != null && frames.isNotEmpty) {
final frame = frames.first;
final fileInfo = frame.split(' ').last;
final lineInfo = fileInfo.split(':');
if (lineInfo.length > 1) {
return int.tryParse(lineInfo[1]) ?? 0;
}
}
return 0;
}
List<String> getTrace() {
return stackTrace?.toString().split('\n') ?? [];
}
String getTraceAsString() {
return stackTrace?.toString() ?? "";
}
}