-
Notifications
You must be signed in to change notification settings - Fork 212
Look up headers in a case insensitive way #199
Conversation
Codecov Report
@@ Coverage Diff @@
## 1.x #199 +/- ##
============================================
- Coverage 69.57% 69.51% -0.06%
- Complexity 0 651 +651
============================================
Files 138 139 +1
Lines 2879 2916 +37
Branches 226 172 -54
============================================
+ Hits 2003 2027 +24
- Misses 833 846 +13
Partials 43 43
Continue to review full report at Codecov.
|
The headers are still sorted by insertion order and preserve their original letter casing when read.
2352198
to
1dbf28c
Compare
Maybe it's not worth having Headers being an AutoValue type, what do you think? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some questions/comments. I think it's best to leave Headers as an AutoValue, it saves some implementation+conceptual overhead wrt especially equality and such.
} | ||
|
||
public Optional<String> get(String name) { | ||
Preconditions.checkArgument(name != null, "Header names cannot be null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to use Objects.requireNonNull
in this case - generally null arguments lead to NPEs, and it's better to use the JDK classes than Guava where feasible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Fixed
*/ | ||
default Optional<String> header(String name) { | ||
return Optional.ofNullable(headers().get(name)); | ||
Preconditions.checkArgument(name != null, "Header names cannot be null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to permit nulls here, and reverse the comparison below so that it would just fail to find something, rather than throw an exception for a null? I'm not sure. Either way, if nulls shouldn't be permitted, then I think we should use Objects.requireNonNull
for the argument check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked offline about this. I will leave the check with Objects.requireNonNull
Map<String, String> headers(); | ||
|
||
/** | ||
* The response headers | ||
*/ | ||
Iterable<Map.Entry<String, String>> headerEntries(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why Iterable here? Since we're spending some effort on retaining order of headers, would it not make more sense to return a List?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to be conservative and restrictive, conveying the message that this is only for iterating over all the headers. I want to prevent users from looking up keys from whatever object this method returns. Also, I didn't want to lock a specific implementation (we could change the internal implementation for a Map, etc). But I see now that I chose the wrong interface and it should have been Iterator, but maybe it's too annoying, specially since it does not have size(). What do you think would be better: Iterator or List?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iterator is generally wrong, since they are one-use only. Iterable is better in that case.
I would still prefer List; it's still quite generic, and I feel like we have committed to retaining order. We could even specify that in the javadocs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. Changed to List
.
@@ -119,4 +144,5 @@ | |||
static <T> Response<T> of(StatusType statusCode, T payload) { | |||
return ResponseImpl.create(statusCode, payload); | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably remove this empty line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
return Optional.empty(); | ||
} | ||
|
||
public Map<String, String> asMap() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used a lot in apollo. Should I memoized it already in the constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure; doing so would increase the memory footprint - you'd need some measurements to be sure, I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave the optimizations for later then.
|
||
public abstract ImmutableList<Map.Entry<String, String>> entries(); | ||
|
||
private static void insertIfAbsent(List<Map.Entry<String, String>> headerList, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused here. Method name suggest that the value would not be overwritten (as it will be entered only if it's absent), but that's what the method does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I fixed the name to insertOrReplace(). Is this better?
private static final Map<String, String> TEST_BIG_MAP = createBigMap(); | ||
|
||
private static Map<String, String> createDuplicateKeysMap() { | ||
LinkedHashMap<String, String> testMap = new LinkedHashMap<>(3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe declare test map asMap
instead of LinkedHashMap
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
} | ||
|
||
@Test | ||
public void shouldReturnHeaderCaseInsensitive() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to test it again as we already do in HeadersTest.java
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the test should be implementation agnostic. At least the most important parts.
In this first step apollo will look up headers in a case insensitive way but it will still preserve the original letter case of header fields when propagated. This makes services compatible with services that send lowercase headers (like those who propagate headers from an HTTP 2 connection).
I recommend looking at the commits separately.
@pettermahlen @rouzwawi