-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Apply review feedback to the multi-encoder and multi-decoder surface #3534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,11 +50,20 @@ | |
| * naming what was tried. Add an encoder guarded by {@link EncoderPredicate#any()} last to act as a | ||
| * default, as above. | ||
| * | ||
| * <p>A multi-encoder is itself a {@link PredicatedEncoder}, accepting whatever any of its encoders | ||
| * accepts, so one can be added to another. That is how a library ships a set of encoders as a | ||
| * single unit: given a hypothetical {@code AcmeFeign.encoders()} returning a multi-encoder over | ||
| * that library's encoders, the whole set is added in one go: | ||
| * | ||
| * <pre> | ||
| * Feign.builder().encoders(AcmeFeign.encoders(), new JacksonEncoder()); | ||
| * </pre> | ||
| * | ||
| * @see PredicatedEncoder | ||
| * @see EncoderPredicate | ||
| */ | ||
| @Experimental | ||
| public class MultiEncoder implements Encoder { | ||
| public class MultiEncoder implements PredicatedEncoder { | ||
|
|
||
| private final List<PredicatedEncoder> encoders; | ||
|
|
||
|
|
@@ -67,6 +76,19 @@ public static Builder builder() { | |
| return new Builder(); | ||
| } | ||
|
|
||
| /** | ||
| * Whether any of the encoders accepts the request. | ||
| * | ||
| * @param object {@inheritDoc} | ||
| * @param bodyType {@inheritDoc} | ||
| * @param template {@inheritDoc} | ||
| * @return {@inheritDoc} | ||
| */ | ||
| @Override | ||
| public boolean canEncode(Object object, Type bodyType, RequestTemplate template) { | ||
| return encoders.stream().anyMatch(encoder -> encoder.canEncode(object, bodyType, template)); | ||
| } | ||
|
|
||
| /** | ||
| * Encodes using the first encoder that accepts the request. | ||
| * | ||
|
|
@@ -91,33 +113,61 @@ private String unableToEncode(Type bodyType, RequestTemplate template) { | |
| StringBuilder message = | ||
| new StringBuilder("Unable to encode ") | ||
| .append(bodyType == null ? "request body" : bodyType.getTypeName()) | ||
| .append(" (Content-Type: ") | ||
| .append(contentTypes(template)) | ||
| .append(" (") | ||
| .append(headers(template)) | ||
| .append(')'); | ||
| if (template.method() != null) { | ||
| message.append(" for ").append(template.method()).append(' ').append(template.path()); | ||
| } | ||
| if (encoders.isEmpty()) { | ||
| return message.append(". No encoders were configured.").toString(); | ||
| } | ||
| message.append(". Encoders tried, in order:"); | ||
| for (PredicatedEncoder encoder : encoders) { | ||
| message.append("\n - ").append(PairedEncoder.describe(encoder)); | ||
| } | ||
| appendTo(message, "\n "); | ||
| return message | ||
| .append("\nAdd an encoder guarded by EncoderPredicate.any() last to act as a default.") | ||
| .append("\nRegister an encoder that accepts it, or add a catch-all") | ||
| .append(" (EncoderPredicate.any()) last.") | ||
| .toString(); | ||
| } | ||
|
|
||
| private static String contentTypes(RequestTemplate template) { | ||
| String contentTypes = | ||
| /** | ||
| * Lists the encoders one per line, unfolding nested multi-encoders so that a set contributed as a | ||
| * single unit still shows what it contains. | ||
| */ | ||
| private void appendTo(StringBuilder message, String indent) { | ||
| for (PredicatedEncoder encoder : encoders) { | ||
| if (encoder instanceof MultiEncoder) { | ||
| message.append(indent).append("- MultiEncoder:"); | ||
| ((MultiEncoder) encoder).appendTo(message, indent + " "); | ||
| } else { | ||
| message.append(indent).append("- ").append(PairedEncoder.describe(encoder)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * The headers an encoder is most likely to have been chosen on. Everything else a predicate looks | ||
| * at belongs in that predicate's own description, which is listed alongside it. | ||
| */ | ||
| private static String headers(RequestTemplate template) { | ||
| String contentType = header(template, Util.CONTENT_TYPE); | ||
| StringBuilder headers = | ||
| new StringBuilder(Util.CONTENT_TYPE) | ||
| .append(": ") | ||
| .append(contentType == null ? "not set" : contentType); | ||
| String accept = header(template, Util.ACCEPT); | ||
| if (accept != null) { | ||
| headers.append(", ").append(Util.ACCEPT).append(": ").append(accept); | ||
| } | ||
| return headers.toString(); | ||
| } | ||
|
|
||
| private static String header(RequestTemplate template, String name) { | ||
| String values = | ||
| template.headers().entrySet().stream() | ||
| .filter(header -> Util.CONTENT_TYPE.equalsIgnoreCase(header.getKey())) | ||
| .filter(header -> name.equalsIgnoreCase(header.getKey())) | ||
| .map(Map.Entry::getValue) | ||
| .filter(Objects::nonNull) | ||
| .flatMap(Collection::stream) | ||
| .collect(Collectors.joining(", ")); | ||
| return contentTypes.isEmpty() ? "not set" : contentTypes; | ||
| return values.isEmpty() ? null : values; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -146,7 +196,10 @@ public Builder add(PredicatedEncoder encoder) { | |
|
|
||
| /** | ||
| * Adds any encoder, guarded by the given predicate. Use this for encoders that do not implement | ||
| * {@link PredicatedEncoder}, including ones you do not control. | ||
| * {@link PredicatedEncoder}, including ones you do not control. The predicate is the whole | ||
| * answer: whatever the encoder may declare about itself is replaced, so this can widen an | ||
| * encoder as well as narrow it. Use {@link #narrow(EncoderPredicate, Encoder)} to keep the | ||
| * encoder's own declaration. | ||
| * | ||
| * @param predicate decides whether the encoder handles a request | ||
| * @param encoder the encoder to delegate to | ||
|
|
@@ -155,8 +208,34 @@ public Builder add(EncoderPredicate predicate, Encoder encoder) { | |
| return add(PredicatedEncoder.of(predicate, encoder)); | ||
| } | ||
|
|
||
| /** Builds the multi-encoder. */ | ||
| /** | ||
| * Adds an encoder, narrowed by the given predicate. If the encoder is itself a {@link | ||
| * PredicatedEncoder}, the predicate applies in addition to the encoder's own {@code canEncode} | ||
| * rather than instead of it: both have to accept the request. | ||
| * | ||
| * <pre> | ||
| * MultiEncoder.builder() | ||
| * .narrow(EncoderPredicate.contentType("application/vnd.acme+json"), new GsonEncoder()) | ||
| * .add(EncoderPredicate.any(), new DefaultEncoder()) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For what it's worth, I don't like using EncoderPredicate.any() with DefaultEncoder. The reality is that .any() isn't really a good predicate for any Encoder or Decoder... I suggest: |
||
| * .build(); | ||
| * </pre> | ||
| * | ||
| * @param predicate narrows what the encoder handles | ||
| * @param encoder the encoder to delegate to | ||
| */ | ||
| public Builder narrow(EncoderPredicate predicate, Encoder encoder) { | ||
| return add(PredicatedEncoder.narrowing(predicate, encoder)); | ||
| } | ||
|
|
||
| /** | ||
| * Builds the multi-encoder. | ||
| * | ||
| * @throws IllegalStateException if no encoder was added | ||
| */ | ||
| public MultiEncoder build() { | ||
| if (encoders.isEmpty()) { | ||
| throw new IllegalStateException("at least one encoder is required"); | ||
| } | ||
| return new MultiEncoder(encoders); | ||
| } | ||
| } | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Good call on the indentation - that will make it very clean to diagnose issues.