Skip to content

Commit

Permalink
Made HttpHeader names case-insensitive and added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ricardoboss committed Feb 12, 2024
1 parent d791b30 commit bd4dde1
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 23 deletions.
111 changes: 88 additions & 23 deletions lib/src/http_headers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,79 @@ part of '../kiota_abstractions.dart';

/// Represents the headers of a request.
///
/// Internally, this class uses a [Map] to store the headers.
/// 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, String> {
/// Creates a new instance of [HttpHeaders].
HttpHeaders();

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

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

return null;
}

@override
void operator []=(String key, String value) => _headers[key] = value;
void operator []=(String key, String value) {
_headers[key.toLowerCase()] = value;
_originalNames[key.toLowerCase()] = key;
}

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

@override
void addEntries(Iterable<MapEntry<String, String>> newEntries) =>
_headers.addEntries(newEntries);
void addEntries(Iterable<MapEntry<String, 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();
void clear() {
_headers.clear();
_originalNames.clear();
}

@override
bool containsKey(Object? key) => _headers.containsKey(key);
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, String>> get entries => _headers.entries;
Iterable<MapEntry<String, String>> get entries {
return _headers.entries.map((entry) {
return MapEntry(_originalNames[entry.key]!, entry.value);
});
}

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

@override
bool get isEmpty => _headers.isEmpty;
Expand All @@ -48,39 +83,69 @@ class HttpHeaders implements Map<String, String> {
bool get isNotEmpty => _headers.isNotEmpty;

@override
Iterable<String> get keys => _headers.keys;
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, String value) convert,
) =>
_headers.map(convert);
) {
final result = <K2, V2>{};

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

return result;
}

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

@override
String? remove(Object? key) => _headers.remove(key);
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, String value) test) =>
_headers.removeWhere(test);
void removeWhere(bool Function(String key, 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
String update(
String key,
String Function(String value) update, {
String Function()? ifAbsent,
}) =>
_headers.update(key, update, ifAbsent: ifAbsent);
}) {
return _headers.update(key.toLowerCase(), update, ifAbsent: ifAbsent);
}

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

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

@override
Iterable<String> get values => _headers.values;
Expand Down
37 changes: 37 additions & 0 deletions test/http_headers_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:kiota_abstractions/kiota_abstractions.dart';
import 'package:test/test.dart';

void main() {
test('HttpHeader keys are case-insensitive', () {
final headers = HttpHeaders()..['content-type'] = 'application/json';

expect(headers.length, 1);
expect(headers['content-type'], 'application/json');
expect(headers['Content-Type'], 'application/json');

headers['Content-Type'] = 'application/xml';

expect(headers.length, 1);
expect(headers['content-type'], 'application/xml');
expect(headers['Content-Type'], 'application/xml');

// should not get added as a new key
headers.putIfAbsent('CONTENT-TYPE', () => 'application/json');

expect(headers.length, 1);
expect(headers['CONTENT-TYPE'], 'application/xml');
});

test('HttpHeader preserves original case', () {
final headers = HttpHeaders();

headers['Content-Type'] = 'application/json';

expect(headers.keys.first, 'Content-Type');

// This should overwrite the previous key
headers['content-type'] = 'application/xml';

expect(headers.keys.first, 'content-type');
});
}

0 comments on commit bd4dde1

Please sign in to comment.