Skip to content

Commit

Permalink
Moved logic for case-insensitive map into its own class
Browse files Browse the repository at this point in the history
  • Loading branch information
ricardoboss committed Feb 12, 2024
1 parent 9a9d5fe commit 5438171
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 140 deletions.
155 changes: 155 additions & 0 deletions lib/src/case_insensitive_map.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/// Stores key-value pairs with case-insensitive keys.
///
/// Internally uses two maps to store the data. The first map stores the
/// normalized keys and the values. The second map stores normalized keys and
/// the original keys.
class CaseInsensitiveMap<K extends String, V> implements Map<K, V> {
/// Creates a new instance of [CaseInsensitiveMap].
CaseInsensitiveMap();

/// Normalizes the given [key] to be used as a key in the internal maps.
static String normalizeKey<T extends String>(T key) => key.toUpperCase();

final Map<String, V> _contents = {};
final Map<String, K> _originalKeys = {};

@override
V? operator [](Object? key) {
if (key is K) {
return _contents[normalizeKey(key)];
}

return null;
}

@override
void operator []=(K key, V value) {
final lowerCaseKey = normalizeKey(key);

_originalKeys[lowerCaseKey] = key;
_contents[lowerCaseKey] = value;
}

@override
void addAll(Map<K, V> other) {
other.forEach((key, value) {
this[key] = value;
});
}

@override
void addEntries(Iterable<MapEntry<K, V>> newEntries) {
newEntries.forEach((entry) {
this[entry.key] = entry.value;
});
}

@override
Map<RK, RV> cast<RK, RV>() => _contents.cast<RK, RV>();

@override
void clear() {
_contents.clear();
_originalKeys.clear();
}

@override
bool containsKey(Object? key) {
if (key is K) {
return _contents.containsKey(normalizeKey(key));
}

return false;
}

@override
bool containsValue(Object? value) => _contents.containsValue(value);

@override
Iterable<MapEntry<K, V>> get entries {
return _contents.entries.map((entry) {
return MapEntry(_originalKeys[entry.key]!, entry.value);
});
}

@override
void forEach(void Function(K key, V value) action) {
_contents.forEach((key, value) {
action(_originalKeys[key]!, value);
});
}

@override
bool get isEmpty => _contents.isEmpty;

@override
bool get isNotEmpty => _contents.isNotEmpty;

@override
Iterable<K> get keys => _contents.keys.map((key) => _originalKeys[key]!);

@override
int get length => _contents.length;

@override
Map<K2, V2> map<K2, V2>(
MapEntry<K2, V2> Function(K key, V value) convert,
) {
final result = <K2, V2>{};

_contents.forEach((key, value) {
final entry = convert(_originalKeys[key]!, value);
result[entry.key] = entry.value;
});

return result;
}

@override
V putIfAbsent(K key, V Function() ifAbsent) {
return _contents.putIfAbsent(normalizeKey(key), ifAbsent);
}

@override
V? remove(Object? key) {
if (key is K) {
_originalKeys.remove(normalizeKey(key));
return _contents.remove(normalizeKey(key));
}

return null;
}

@override
void removeWhere(bool Function(K key, V value) test) {
final keys = _contents.keys.toList();

for (final key in keys) {
if (test(_originalKeys[key]!, _contents[key]!)) {
_originalKeys.remove(key);
_contents.remove(key);
}
}
}

@override
V update(
K key,
V Function(V value) update, {
V Function()? ifAbsent,
}) {
return _contents.update(normalizeKey(key), update, ifAbsent: ifAbsent);
}

@override
void updateAll(V Function(K key, V value) update) {
final keys = _contents.keys.toList();

for (final key in keys) {
_contents[key] = update(_originalKeys[key]!, _contents[key]!);
}
}

@override
Iterable<V> get values => _contents.values;
}
146 changes: 6 additions & 140 deletions lib/src/http_headers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ part of '../kiota_abstractions.dart';
/// Internally, the keys are stored in lower case, but the original case is
/// preserved when iterating over the headers.
/// This effectively makes the keys case-insensitive.
class HttpHeaders implements Map<String, Set<String>> {
class HttpHeaders extends CaseInsensitiveMap<String, Set<String>> {
/// Creates a new instance of [HttpHeaders].
HttpHeaders();

Expand All @@ -15,157 +15,23 @@ class HttpHeaders implements Map<String, Set<String>> {
'content-length',
];

final Map<String, Set<String>> _headers = {};
final Map<String, String> _originalNames = {};

@override
Set<String>? operator [](Object? key) {
if (key is String) {
return _headers[key.toLowerCase()];
}

return null;
}

@override
void operator []=(String key, Set<String> value) {
final lowerCaseKey = key.toLowerCase();

_originalNames[lowerCaseKey] = key;

final Set<String> normalizedValue;
if (singleValueHeaders.contains(lowerCaseKey)) {
_headers[lowerCaseKey] = {value.first};
normalizedValue = {value.first};
} else {
_headers[lowerCaseKey] = value;
normalizedValue = value;
}

super[key] = normalizedValue;
}

/// Sets the value of the header with the given [key] to a Set containing only
/// [value].
void put(String key, String value) {
this[key] = {value};
}

@override
void addAll(Map<String, Set<String>> other) {
other.forEach((key, value) {
this[key] = value;
});
}

@override
void addEntries(Iterable<MapEntry<String, Set<String>>> newEntries) {
newEntries.forEach((entry) {
this[entry.key] = entry.value;
});
}

@override
Map<RK, RV> cast<RK, RV>() => _headers.cast<RK, RV>();

@override
void clear() {
_headers.clear();
_originalNames.clear();
}

@override
bool containsKey(Object? key) {
if (key is String) {
return _headers.containsKey(key.toLowerCase());
}

return false;
}

@override
bool containsValue(Object? value) => _headers.containsValue(value);

@override
Iterable<MapEntry<String, Set<String>>> get entries {
return _headers.entries.map((entry) {
return MapEntry(_originalNames[entry.key]!, entry.value);
});
}

@override
void forEach(void Function(String key, Set<String> value) action) {
_headers.forEach((key, value) {
action(_originalNames[key]!, value);
});
}

@override
bool get isEmpty => _headers.isEmpty;

@override
bool get isNotEmpty => _headers.isNotEmpty;

@override
Iterable<String> get keys => _headers.keys.map((key) => _originalNames[key]!);

@override
int get length => _headers.length;

@override
Map<K2, V2> map<K2, V2>(
MapEntry<K2, V2> Function(String key, Set<String> value) convert,
) {
final result = <K2, V2>{};

_headers.forEach((key, value) {
final entry = convert(_originalNames[key]!, value);
result[entry.key] = entry.value;
});

return result;
}

@override
Set<String> putIfAbsent(String key, Set<String> Function() ifAbsent) {
return _headers.putIfAbsent(key.toLowerCase(), ifAbsent);
}

@override
Set<String>? remove(Object? key) {
if (key is String) {
_originalNames.remove(key.toLowerCase());
return _headers.remove(key.toLowerCase());
}

return null;
}

@override
void removeWhere(bool Function(String key, Set<String> value) test) {
final keys = _headers.keys.toList();

for (final key in keys) {
if (test(_originalNames[key]!, _headers[key]!)) {
_originalNames.remove(key);
_headers.remove(key);
}
}
}

@override
Set<String> update(
String key,
Set<String> Function(Set<String> value) update, {
Set<String> Function()? ifAbsent,
}) {
return _headers.update(key.toLowerCase(), update, ifAbsent: ifAbsent);
}

@override
void updateAll(Set<String> Function(String key, Set<String> value) update) {
final keys = _headers.keys.toList();

for (final key in keys) {
_headers[key] = update(_originalNames[key]!, _headers[key]!);
}
}

@override
Iterable<Set<String>> get values => _headers.values;
}

0 comments on commit 5438171

Please sign in to comment.