-
Notifications
You must be signed in to change notification settings - Fork 77
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
add discriminated union trait #30
Conversation
@@ -41,3 +41,7 @@ structure UncheckedExample { | |||
@trait(selector: "string") | |||
structure uuidFormat { | |||
} | |||
|
|||
@trait(selector: "union") | |||
structure discriminated { |
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 this should be parameterized by the field name. Personally I've seen "@type", "type", "kind", "node_kind" and so on, so it should probably be up to the API drafter :)
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 is indeed correct. See "propertyName" in openapi : https://spec.openapis.org/oas/latest.html#discriminator-object.
I think I've seen type
more than anything else, but making it explicit is probably a good call.
So it could be, for instance :
structure discriminated {
propertyName: String
}
We don't care much about the mapping
thing from openapi. By default we'll use the labels of union members, and hopefully @jsonName
will be made usable on union members and we'll be using that.
val decoders: DecoderMap[S] = | ||
(first +: rest).map { case alt @ Alt(_, instance, inject) => | ||
val encoder = { (pp: List[PayloadPath.Segment], doc: Document) => | ||
inject(instance.get.apply(jsonLabel(alt) :: pp, doc)) |
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.
extract the jsonLabel
call from here so that it ain't on the hot path.
jsonLabel(alt) -> encoder | ||
}.toMap | ||
|
||
Hinted[DocumentDecoder].onHintOpt[Discriminated, S] { |
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.
👍
throw new PayloadError( | ||
PayloadPath(pp.reverse), | ||
"Union", | ||
"Expected more than single-key Json object" |
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 necessarily : the alternative could be an empty structure. The if (map.size > 1)
is un-necessary
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.
Ah yeah good call, I will update and add a test case to make sure that is handled
case DObject(map) if (map.size > 1) => | ||
map | ||
.get(discriminated.propertyName) match { | ||
case Some(value: Document.DString) => |
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.
👍
else { | ||
in.rollbackToken() | ||
in.setMark() | ||
val key = if (in.skipToKey(discriminated.propertyName)) { |
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.
oh really cool ! I thought we were gonna have to do that by hand, happy it's not the 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.
Yeah I was super happy to learn of setMark
+ skipToKey
+ rollbackToMark
.. made the backtracking a lot easier
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.
Yeah I was aware of setMark
+ rollbackToMark
. Kudos to @plokhotnyuk, I really appreciate jsoniter's API.
} | ||
|
||
val altCache = new PolyFunction[Alt[JCodecMake, Z, *], JCodec] { |
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 implementation of the schematics look good to me. Maybe we could do with some property tests generating random schemas that have the discriminated trait, but I'm okay with us doing that later. There's two things that I'd like to have before merging :
|
Agreed, thanks for the reminder.
Yeah, I can't think of anything else that would ever be specified in there. I will update it to be a string. |
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.
👍 pre-approving (conditioned by the addition of a validator for the discriminator trait)
public List<ValidationEvent> validate(Model model) { | ||
return model.getShapesWithTrait(DiscriminatedUnionTrait.class).stream().flatMap(unionShape -> { | ||
return unionShape.asUnionShape().get().getAllMembers().entrySet().stream().flatMap(entry -> { | ||
System.out.println(model.getShape(entry.getValue().getTarget())); |
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.
cleanup
if (maybeTarget.isPresent() && maybeTarget.get().isStructureShape()) { // if not defined then shape won't be structure | ||
return Stream.empty(); | ||
} else { | ||
return Stream.of(error(entry.getValue(), String.format("Member shape corresponding to key '%s' is not a structure shape", entry.getKey()))); |
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.
return Stream.of(error(entry.getValue(), String.format("Member shape corresponding to key '%s' is not a structure shape", entry.getKey()))); | |
return Stream.of(error(entry.getValue(), String.format("Target of member '%s' is not a structure shape", entry.getKey()))); |
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.
re-approving
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 only thing that I'm kind of missing is an example in a .smithy
file, but otherwise it looks good
Good call, I will add that |
One thing that we will want to look at (possibly in different PR) is the OpenAPI conversion respecting the discriminated union trait. I think there should be a |
Happy for the openapi conversion to happen in a subsequent PR |
🎉 |
* add `discriminated` trait, indicating that a union should be encoded as an object with a discriminator field. The field is defined by the string value taken by the trait * adds a validator to ensure that all members of a discriminated unions target structure shapes * updates Document codecs and Json codecs to take it in consideration * adds an example
No description provided.