Open blast-hardcheese opened 6 years ago
Another, simpler strategy until this is resolved could be to shim Either[Foo, String]
, but there are two problems:
1) $ref
s can't have vendor extensions, preventing the use of x-scala-type
2) x-scala-type
would have to be littered everywhere
One thought I had with this is that we probably want to allow an unknown type for clients, but not for servers (a server should never return an un-enumerated value). Unfortunately model generation still doesn't know if the generation is for a client or server, and often services might share model generation for the same client and server.
I'm leaning toward this being opt-in, though at what level... If it's a guardrail command-line option, that lets the spec consumer decide what they want to do. But a consumer that is using someone else's API may simply not know that they should be using that option; the spec author is in the best position to know if an enum is likely to be added to in the future. But if this toggle goes into the spec, we still have the client vs. server knowledge/sharing issue at generation time.
Another thing to think about is what if a legitimate enum alue, specified in the spec, is unknown
? Then we'd have a name conflict when generating class/member names.
Gah, yes, I forgot about the server/client side of this problem. Permitting servers to return unexpected values would be undesirable. I can think of situations (like generated proxies) that would benefit from this feature being present in the server as well, but it's certainly an edge case.
Another thing to think about is what if a legitimate enum alue, specified in the spec, is
unknown
? Then we'd have a name conflict when generating class/member names.
Maybe whatever feature enables this does so via specifying the name desired to be used? Like ScalaClient(..., extensibleEnumName="Unknown")
Maybe whatever feature enables this does so via specifying the name desired to be used? Like
ScalaClient(..., extensibleEnumName="Unknown")
Yeah, that could work. And we could have a default if it's not provided, and bail if there's a conflict.
fyi, I am aware of prior art in two open source projects:
1) ApiBuilder 2) AWS SDK for Java v2
ApiBuilder uses UNDEFINED as the marker for unknown enumeration values:
Here is example output from the ApiBuilder Scala generator:
https://app.apibuilder.io/flow/delta/0.8.36/scala_models/FlowDeltaV0Models.scala
AWS SDK for Java (version 2) defines a special Java enum value called UNKNOWN_TO_SDK_VERSION
yeah, that's a good point. In Java, can't we define an enum with a held value? I thought Java supported ADT-style enums, @kelnos ?
In Java, can't we define an enum with a held value? I thought Java supported ADT-style enums, @kelnos ?
Unfortunately not, but you can kinda simulate them with an abstract class and static final
instances for the fixed values, with a regular class for the unknown value. i'd probably do something like this (especially this in order to maintain source compat):
public abstract class MyEnum {
public static final MyEnum SOME_VALUE = new MyEnum("some-value);
public static final MyEnum ANOTHER_VALUE = new MyEnum("another-value");
public static final class Unknown {
public Unknown(String value) { super(value); }
}
private final String value;
private MyEnum(String value) { this.value = value; }
public String getValue() { return this.value; }
}
I would also add the methods that come for free from java.lang.Enum
, like valueOf()
, equals()
, etc., but unfortunately you can't use enum
, as java enums are not subclassable.
I think implementation-wise we're fine with one caveat: javac special-cases switch
statements with enums: I think you can't use qualified names for the case
statements, and you also don't need to import the individual values (even though you aren't qualifying them), so that could be a slightly breaking source change (fixable by the consumer adding another import).
To me the only issue is that of handling the feature in guardrail:
x-server-raw-response
).What's the downside for opt-in unknown enumeration generation on the server? The op-in-ness should be enough to keep people on the right track unless they open that particular bag of worms, no?
Actually I did think of a problem for #4 again. If the enum is something that the server also accepts from a client, this would allow clients to put wacky things in there, and then the server would have to explicitly deal with it, rather than the deserializer automatically tossing back a 400.
Right, but that's opt-in behavior
For interoperation with poorly specced or flexible downstream APIs, clients should be able to represent a branch of an enumeration that isn't known. This permits accepting unknown branches and doing work with them without rejecting the response from client calls (currently, enumeration lookup failures result in a decoder failure for clients).
Currently, generated enumerations look like this:
A possible alteration to accept unknown members (currently unsure if this should be opt-in or opt-out, definitely needs more thought):
Initially I thought the
Unknown
branch should not expose a constructor, as it would prevent invalid data from being supplied by the clients into downstream services. This has the unfortunate side-effect of not allowing services to round-trip to persistence layers; consider receiving values of an array via client API call, writing those values into a database, then attempting to use those values to make future API calls.