Today I learned that enum values in Rust don't have to be strings; they can include tuple and object structs too. In #88, the ApiInstanceState enum was modified to include structs:
pub enum ApiInstanceState {
Creating,
Starting,
Running,
- Stopping
- Stopped
+ Stopping { rebooting: bool },
+ Stopped { rebooting: bool },
Repairing,
Failed,
Destroyed,
}
Here I would like to:
- ask whether the resulting JSON schema is what we expect based on the Rust code (I think it is)
- ask whether the resulting JSON schema is something we want clients to have to handle (not sure)
- note that regardless of whether we like the schema, the TypeScript client generator is handling it very badly
Generated JSON schema
The generated schema is an anyOf containing:
- an enum of the string values
- an object that looks like
{ stopping: { rebooting: true/false } }
- an object that looks like
{ stoppped: { rebooting: true/false } }
I'm no OpenAPI expert, but this seems right to me. I don't know how else you would do it. OpenAPI enums can only have string values.
Expand for JSON
"ApiInstanceState": {
"description": "blah blah blah",
"anyOf": [
{
"type": "string",
"enum": [
"creating",
"starting",
"running",
"repairing",
"failed",
"destroyed"
]
},
{
"type": "object",
"properties": {
"stopping": {
"type": "object",
"properties": {
"rebooting": {
"type": "boolean"
}
},
"required": ["rebooting"]
}
},
"required": ["stopping"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"stopped": {
"type": "object",
"properties": {
"rebooting": {
"type": "boolean"
}
},
"required": ["rebooting"]
}
},
"required": ["stopped"],
"additionalProperties": false
}
]
},
Generated TypeScript client
The client generator, on the other hand, gets it completely wrong. I get something like the following (abbreviated for convenience) with no connection between ApiInstanceStateAnyOf and ApiInstanceState. It's totally broken, but as far as I can tell this is not Dropshot or Nexus's fault at all.
enum ApiInstanceStateAnyOf {
Creating = 'creating',
Starting = 'starting',
Running = 'running',
Repairing = 'repairing',
Failed = 'failed',
Destroyed = 'destroyed',
}
interface ApiInstanceStateAnyOf1Stopping {
rebooting: boolean
}
interface ApiInstanceState {
stopping: ApiInstanceStateAnyOf1Stopping
stopped: ApiInstanceStateAnyOf1Stopping
}
Instead I would expect something like this:
type ApiInstanceState =
| 'creating'
| 'starting'
| 'running'
| 'repairing'
| 'failed'
| 'destroyed'
| { stopped: { rebooting: boolean } }
| { stopping: { rebooting: boolean } }
// both valid
const x: ApiInstanceState = 'creating'
const y: ApiInstanceState = { stopped: { rebooting: true } }
Would it be good even if the generated client was correct
Mixing strings and objects in a single union is a little odd. It's not that bad and the typechecker will help you out a lot, but someone working with a client in a dynamic language would have a hard time getting these values right.
Mixing strings and objects also makes display logic more complicated: if it's a string you can display it directly, otherwise you have to look inside the object and pull out stopped/stopping and rebooting.
Options
- Avoid using structs in enums that are going straight into the API schema
- The above would have to be modeled as something like
stopped, stopped_rebooting, stopping, stopping_rebooting, which is a bit ugly but conceptually pretty much the same
- Fix TS client generator (quite painful)
- Manually create the correct type for the client (slippery slope)
- Stitch together the correct type from the generated types (maybe slightly less slippery of a slope because it at least has to cohere with the generated code)
Today I learned that enum values in Rust don't have to be strings; they can include tuple and object structs too. In #88, the
ApiInstanceStateenum was modified to include structs:pub enum ApiInstanceState { Creating, Starting, Running, - Stopping - Stopped + Stopping { rebooting: bool }, + Stopped { rebooting: bool }, Repairing, Failed, Destroyed, }Here I would like to:
Generated JSON schema
The generated schema is an
anyOfcontaining:{ stopping: { rebooting: true/false } }{ stoppped: { rebooting: true/false } }I'm no OpenAPI expert, but this seems right to me. I don't know how else you would do it. OpenAPI enums can only have string values.
Expand for JSON
Generated TypeScript client
The client generator, on the other hand, gets it completely wrong. I get something like the following (abbreviated for convenience) with no connection between
ApiInstanceStateAnyOfandApiInstanceState. It's totally broken, but as far as I can tell this is not Dropshot or Nexus's fault at all.Instead I would expect something like this:
Would it be good even if the generated client was correct
Mixing strings and objects in a single union is a little odd. It's not that bad and the typechecker will help you out a lot, but someone working with a client in a dynamic language would have a hard time getting these values right.
Mixing strings and objects also makes display logic more complicated: if it's a string you can display it directly, otherwise you have to look inside the object and pull out
stopped/stoppingandrebooting.Options
stopped,stopped_rebooting,stopping,stopping_rebooting, which is a bit ugly but conceptually pretty much the same