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
Use strong types for additional data in problem reports #28964
base: master
Are you sure you want to change the base?
Conversation
88e9456
to
d46e542
Compare
23c1a4c
to
8629122
Compare
8629122
to
8c46ac0
Compare
8c46ac0
to
6f51fd3
Compare
6f51fd3
to
de41444
Compare
@@ -25,8 +25,6 @@ | |||
public interface TypeAwareProblemBuilder extends InternalProblemSpec { | |||
TypeAwareProblemBuilder withAnnotationType(@Nullable Class<?> classWithAnnotationAttached); | |||
|
|||
TypeAwareProblemBuilder typeIsIrrelevantInErrorMessage(); |
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.
We can identify which type validation problems should not have the type in the error message based on the problem ID; we no longer need to have a separate field for that.
5e482f1
to
6ff6f30
Compare
@@ -78,6 +87,7 @@ public static GsonBuilder createGsonBuilder() { | |||
gsonBuilder.registerTypeHierarchyAdapter(DocLink.class, new DocLinkAdapter()); | |||
gsonBuilder.registerTypeHierarchyAdapter(ProblemLocation.class, new LocationAdapter()); | |||
gsonBuilder.registerTypeHierarchyAdapter(ProblemCategory.class, new ProblemCategoryAdapter()); | |||
gsonBuilder.registerTypeHierarchyAdapter(AdditionalData.class, new AdditionalDataAdapter()); |
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 didn't see this code before. Why are we using JSON for ValidationProblemSerialization while using Kryo for most other things?
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.
Thank you for the review!
I am not sure. The JSON-based serialisation was already there when we got started with the Problems API integration.
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.
It might be interesting to find out what is going on there.
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 introduced the JSON part, it used to be just a file that we would read on the other end.
with no structured information.
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.
public class DefaultProblemBuilder implements InternalProblemBuilder { | ||
public class DefaultProblemBuilder implements InternalProblemBuilder { | ||
|
||
private static List<Class<?>> supportedAdditionalDataTypes = ImmutableList.<Class<?>>of( |
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 you already have plans how to handle extensibility here?
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.
For now, I want to keep the AdditionalData
as sealed class. Gradle should control what are the allowed types and disallow any extensibility from third parties.
If we have a reasonable use-case for third parties, only then I'd proceed with extensibility. The idea is to do what we do around model building: producers register what types of data they want to expose. Consumers would declare what kind of data they expect and the problems API would only forward the known items. I'd only consider this for spec/implementation if we have a very compelling use-case.
|
||
public class DefaultProblemBuilder implements InternalProblemBuilder { | ||
public class DefaultProblemBuilder implements InternalProblemBuilder { |
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.
❓ Is there a spec for this change to help understand the reasoning?
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 considered this as an internal refactoring, so I don't have any spec for this. At the same time, I'll add some description very soon to this PR.
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've updated the PR's description to reflect to your question.
@@ -86,7 +85,7 @@ public RuntimeException getException() { | |||
} | |||
|
|||
@Override | |||
public Map<String, Object> getAdditionalData() { | |||
public AdditionalData<?> getAdditionalData() { |
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 expecting something like this:
- Problem gets a type parameter describing what kind of data it uses.
- ProblemDefinition should also have the type parameter, though it probably doesn't need it in the first place
- This getter returns the type parameter.
I understand that you need the type of the builder when adding the additional data. Though as soon as it has been created, you don't need the builder type any more, right? I'll leave a comment to the method in InternalProblemSpec
.
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.
That was essentially my first attempt, but the change got so big that I decided to step back and deal with the removal of the Map type for additionalData. Which is this PR.
*/ | ||
InternalProblemSpec additionalData(String key, Object value); | ||
<T extends AdditionalData<U>, U> InternalProblemSpec additionalData(Class<? extends AdditionalData<U>> type, Action<? super U> config); |
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.
<T extends AdditionalData<U>, U> InternalProblemSpec additionalData(Class<? extends AdditionalData<U>> type, Action<? super U> config); | |
<T extends AdditionalData, U extends Builder<T>> InternalProblemSpec additionalData(Class<U> builderType, Action<? super U> config); |
❓ Would something like this work? So you'd need to specify the builder type when adding additional data instead of specifying the builder with the additional data itself. That way consumers don't need to worry about the builder type.
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.
That's fair, I'll take a stab at it.
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.
The nice thing about the current implementation is the ease of use of the API. For example, when you write something like
.additionalData(DeprecationData.class,
The type system will only allow you to pass an object of type Action<DeprecationDataSpec>
as a second parameter.
That way consumers don't need to worry about the builder type.
It's not exactly a builder, but a spec type; a companion interface declaring what are the allowed configuration options for particular additional data type. Is this something that you'd disallow seeing on the consumer-side?
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 what you'd pass in the API is the spec type, and then the spec type should know the additional data it can build. The consumer should only be interested in the actual data, not what was used to construct it, right?
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; ptal
*/ | ||
@Incubating | ||
@NonNullApi | ||
public interface TypeValidationData extends AdditionalData { |
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 missing a type parameter.
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 a Tooling API event type; it does not have the same type parameters you have it on the producer side (ie in org.gradle.api.problems.internal.TypeValidationData).
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 do we expose the TypeValidationData
via the TAPI? Is this something we want to have for Android Studio?
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 wanted to expose something to showcase how we can expose custom additional data types. It doesn't have to be TypeValidationData, but now you mentioned it, we probably should not expose anything new on the Tooling API without a clear use-case.
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've removed the tapi event subtype representing type validation problems.
af6e537
to
0cbcad5
Compare
4c7e2ae
to
e59995c
Compare
e59995c
to
cb6a9ca
Compare
@bot-gradle test this |
I've triggered the following builds for you. Click here to see all build failures. |
The internal Problems API allows clients to attach additional data. The data is currently stored in a map object. This change introduces strong, domain-specific types for the same daa.
cb6a9ca
to
0d02c1c
Compare
@bot-gradle test this |
I've triggered the following builds for you. Click here to see all build failures. |
One feature we want to support for the Problems API is to be attach domain-specific additional data to problem reports. There's a(n internal) solution that uses a string map, which is quite rudimentary and we don't want to make it part of the actual API.
The ultimate goal is to use template to declare additional data for problems.
This PR implements the first step to get there: it replaces the string map in the private API with on object reference. The main change is new signature for the
InternalProblemSpec.additionalData()
method:It allows producers to specify an action that configures additional data. The spec types, and their corresponding custom additional data types are predefined, with the (functionally sealed)
AdditionalData
andAdditonalDataSpec
interfaces. The PR add support for three data types: generic data (ie the old string map), type validation data, and deprecation data.We PR does not expose these new types on the Tooling API events, although it establishes the pattern on how to do it, should the use-case arise. For now, all additional data types are exposed with the original string map format with a clear documentation that the content of the map is subject of change between the releases.