-
Notifications
You must be signed in to change notification settings - Fork 388
Fix duplicate inherited discriminators in Java models #11824
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
base: main
Are you sure you want to change the base?
Changes from all commits
97bfc28
58badce
5147153
627bdaa
01a8261
6527551
a9044e5
da5ae99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| changeKind: fix | ||
| packages: | ||
| - "@typespec/http-client-java" | ||
| --- | ||
|
|
||
| Prevent duplicate Java discriminator members while preserving inherited discriminators in stream-style XML serialization. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -363,11 +363,13 @@ public ClientModel map(ObjectSchema compositeType) { | |
| // the correct serialization in multi-level polymorphic structures. | ||
| for (ClientModel derivedType : derivedTypes) { | ||
| if (!Objects.equals(polymorphicDiscriminator, derivedType.getPolymorphicDiscriminatorName())) { | ||
| // The child hierarchy stays in one fixed parent discriminator branch. | ||
| ClientModelProperty parentDiscriminator = result.getPolymorphicDiscriminator() | ||
| .newBuilder() | ||
| .defaultValue(result.getPolymorphicDiscriminator() | ||
| .getClientType() | ||
| .defaultValueExpression(derivedType.getSerializedName())) | ||
| .constant(true) | ||
|
Member
Author
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. This fixed checkstyle(field should be final). |
||
| .build(); | ||
|
|
||
| passPolymorphicDiscriminatorToChildren(parentDiscriminator, derivedType); | ||
|
|
@@ -383,13 +385,47 @@ public ClientModel map(ObjectSchema compositeType) { | |
|
|
||
| private static void passPolymorphicDiscriminatorToChildren(ClientModelProperty parentDiscriminator, | ||
| ClientModel child) { | ||
| // Due to the execution order of ModelMapper, where children models complete mapping before the parent model, | ||
| // the parent polymorphic discriminator needs to be added at index 0. Reason, given an example where there are | ||
| // three models, where model #1 is the root parent with discriminator type, model #2 is a child of model #2 with | ||
| // discriminator kind, and model #3 is a child of model #3 with discriminator form. The order if this running | ||
| // will have model #2 add its discriminator to model #3 before model #1 runs adding its discriminator to #2 and | ||
| // #3. We want #3 to have the ordering of [type, kind], to represent the ordering of the parent models. | ||
| child.getParentPolymorphicDiscriminators().add(0, parentDiscriminator); | ||
| // A child that introduces a different discriminator still needs the fixed discriminator value selected by the | ||
| // parent hierarchy. For example, a parent may discriminate on "type", while a child fixes type="message" and | ||
| // discriminates its children on "role". The child branch must retain type="message" while dispatching by | ||
| // "role". | ||
| // | ||
| // If child.getProperties() contains a fixed property with the same serialized name and value as | ||
| // parentDiscriminator, use the matching property to build the parent discriminator entry. Then remove the | ||
| // matching property from child.getProperties() so the generated model does not contain "type" as both a normal | ||
| // property and a parent discriminator. | ||
| ClientModelProperty discriminatorForChild = parentDiscriminator; | ||
| for (int i = 0; i < child.getProperties().size(); i++) { | ||
|
Contributor
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. |
||
| ClientModelProperty childProperty = child.getProperties().get(i); | ||
| if (!Objects.equals(parentDiscriminator.getSerializedName(), childProperty.getSerializedName())) { | ||
| continue; | ||
| } | ||
|
|
||
| if (!childProperty.isConstant() | ||
| || !Objects.equals(parentDiscriminator.getWireType(), childProperty.getWireType()) | ||
| || !Objects.equals(parentDiscriminator.getClientType(), childProperty.getClientType()) | ||
| || !Objects.equals(parentDiscriminator.getDefaultValue(), childProperty.getDefaultValue())) { | ||
|
Comment on lines
+400
to
+407
Contributor
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. Agent raises another possibility of
seems also worth an error? |
||
| throw new IllegalStateException("Property '" + childProperty.getSerializedName() + "' on model '" | ||
| + child.getName() + "' does not match its inherited polymorphic discriminator. Expected (type=" | ||
| + parentDiscriminator.getClientType() + ", value=" | ||
| + String.valueOf(parentDiscriminator.getDefaultValue()) + "), but found (type=" | ||
| + childProperty.getClientType() + ", value=" + String.valueOf(childProperty.getDefaultValue()) | ||
| + ")."); | ||
| } | ||
|
|
||
| discriminatorForChild = childProperty.newBuilder() | ||
| .name(parentDiscriminator.getName()) | ||
| .readOnly(true) | ||
| .required(false) | ||
| .polymorphicDiscriminator(true) | ||
| .build(); | ||
| child.getProperties().remove(i); | ||
| break; | ||
| } | ||
|
|
||
| // Children are mapped before their parents, so insert at index 0 to preserve outer-to-inner discriminator | ||
| // order. | ||
| child.getParentPolymorphicDiscriminators().add(0, discriminatorForChild); | ||
|
|
||
| for (ClientModel derived : child.getDerivedModels()) { | ||
| passPolymorphicDiscriminatorToChildren(parentDiscriminator, derived); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2059,8 +2059,16 @@ private void writeToXml(JavaClass classBlock) { | |
| + propertiesManager.getXmlNamespaceConstant(namespace) + ");")); | ||
|
|
||
| // Assumption for XML is polymorphic discriminators are attributes. | ||
| if (propertiesManager.getDiscriminatorProperty() != null) { | ||
| serializeXml(methodBlock, propertiesManager.getDiscriminatorProperty().getProperty(), false); | ||
| ClientModelPropertyWithMetadata discriminatorProperty | ||
| = propertiesManager.getDiscriminatorProperty(); | ||
| model.getParentPolymorphicDiscriminators() | ||
| .stream() | ||
| .filter(discriminator -> discriminatorProperty == null | ||
| || !Objects.equals(discriminator.getSerializedName(), | ||
| discriminatorProperty.getProperty().getSerializedName())) | ||
| .forEach(discriminator -> serializeXml(methodBlock, discriminator, false)); | ||
| if (discriminatorProperty != null) { | ||
| serializeXml(methodBlock, discriminatorProperty.getProperty(), false); | ||
|
Comment on lines
2061
to
+2071
Member
Author
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. Agent said this fixed xml's nested discriminator issue. Generated code looks good to me. |
||
| } | ||
|
|
||
| propertiesManager.forEachSuperXmlAttribute(property -> serializeXml(methodBlock, property, true)); | ||
|
|
@@ -2214,7 +2222,7 @@ private void writeSuperTypeFromXml(JavaClass classBlock) { | |
| + propertiesManager.getXmlNamespaceConstant(discriminatorProperty.getXmlNamespace()) + ", " | ||
| + "\"" + discriminatorProperty.getSerializedName() + "\");"); | ||
| } else { | ||
| methodBlock.line("String discriminatorValue = reader.getStringAttribute(" + "\"" | ||
| methodBlock.line("String discriminatorValue = reader.getStringAttribute(null, " + "\"" | ||
| + discriminatorProperty.getSerializedName() + "\");"); | ||
| } | ||
|
|
||
|
|
@@ -2226,12 +2234,18 @@ private void writeSuperTypeFromXml(JavaClass classBlock) { | |
| // Add deserialization for all child types. | ||
| List<ClientModel> childTypes = getAllChildTypes(model, new ArrayList<>()); | ||
| for (ClientModel childType : childTypes) { | ||
| boolean sameDiscriminator = Objects.equals(childType.getPolymorphicDiscriminatorName(), | ||
| model.getPolymorphicDiscriminatorName()); | ||
| if (!sameDiscriminator && !Objects.equals(childType.getParentModelName(), model.getName())) { | ||
| continue; | ||
| } | ||
|
|
||
| String deserializationMethod = (isSuperTypeWithDiscriminator(childType) && sameDiscriminator) | ||
| ? ".fromXmlInternal(reader, finalRootElementName)" | ||
| : ".fromXml(reader, finalRootElementName)"; | ||
| ifBlock = ifOrElseIf(methodBlock, ifBlock, | ||
| "\"" + childType.getSerializedName() + "\".equals(discriminatorValue)", | ||
| ifStatement -> ifStatement | ||
| .methodReturn(childType.getName() + (isSuperTypeWithDiscriminator(childType) | ||
| ? ".fromXmlInternal(reader, finalRootElementName)" | ||
| : ".fromXml(reader, finalRootElementName)"))); | ||
| ifStatement -> ifStatement.methodReturn(childType.getName() + deserializationMethod)); | ||
| } | ||
|
|
||
| if (ifBlock == null) { | ||
|
|
@@ -2439,6 +2453,10 @@ private void writeFromXmlDeserialization(JavaBlock methodBlock) { | |
| } | ||
|
|
||
| private void deserializeXmlAttribute(JavaBlock methodBlock, ClientModelProperty attribute, boolean fromSuper) { | ||
| if (attribute.isRequired() && attribute.isConstant() && !attribute.isPolymorphicDiscriminator()) { | ||
| return; | ||
| } | ||
|
|
||
| String xmlAttributeDeserialization = getSimpleXmlDeserialization(attribute.getWireType(), null, | ||
| attribute.getXmlName(), propertiesManager.getXmlNamespaceConstant(attribute.getXmlNamespace()), true); | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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 fixed another edge case(exists before, but exposed by our fix), covered in https://github.com/XiaofeiCao/typespec/blob/a9044e57522bc213f38893a035a6f645afb91bf8/packages/http-client-java/generator/http-client-generator-test/tsp/discriminator-edge-cases.tsp#L33-L39
We remove the child-declared "discriminator" now in this PR and move it into child's
parentDiscriminators. Currently child will generateparentDiscriminatorsas local fields, thus we need to add them back here inthisModelPropertySerializeNamesto prevent duplicated field declarations(which will count in parent's shaded ones).