ash-project / ash-project/ash_json_api

Include `tag_value` as discriminator attribute when writing Ash.Type.Union to OpenAPI

Open
#272 1 comment 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Elixir
Stars
94
Forks
80
Avg merge
5h 5m
Merged PRs (30d)
4

Description

It would be really useful to include more information about union type variants inside the OpenAPI output.

Say that I declare these types:

```elixir
defmodule Circle do
use Ash.Resource, data_layer: :embedded

attributes do
attribute :radius, :integer, public?: true, allow_nil?: false
end
end

defmodule Square do
use Ash.Resource, data_layer: :embedded

attributes do
attribute :length, :integer, public?: true, allow_nil?: false
end
end

defmodule EquilateralTriangle do
use Ash.Resource, data_layer: :embedded

attributes do
attribute :length, :integer, public?: true, allow_nil?: false
end
end

defmodule Shape do
use Ash.Type.NewType,
subtype_of: :union,
constraints: [
types: [
circle: [
type: Circle,
tag: :type,
tag_value: :circle
],
square: [
type: Square,
tag: :type,
tag_value: :square
],
equilaterial_triangle: [
type: EquilateralTriangle,
tag: :type,
tag_value: :triangle
],
]
]
end
```

As things stand today, the output will be this:

```json
{
"shape": {
"anyOf": [
{
"properties": {
"length": {
"description": "Field included by default.",
"type": "integer"
}
},
"required": [
"length"
],
"type": "object"
},
{
"properties": {
"radius": {
"description": "Field included by default.",
"type": "integer"
}
},
"required": [
"radius"
],
"type": "object"
}
],
"description": "Field included by default."
}
}
```

Notice how it doesn't include the `tag_value` property defined in the embedded resources, and how `Square` and `EquilaterialTriangle` are merged into the same object due to having the same fields.

This is a bit unfortunate, since it throws away information about which variant the data comes from. This is useful information to have when using the OpenAPI specification to generate e.g. TypeScript or Swift types, since these languages can use the discriminator field to narrow down the current variant.

Ideally, I'd like to see this output instead:

```json
{
"schemas": {
"Shape": {
"anyOf": [
{
"$ref": "#/components/schemas/Circle"
},
{
"$ref": "#/components/schemas/Square"
},
{
"$ref": "#/components/schemas/EquilateralTriangle"
}
],
"discriminator": {
"propertyName": "type",
"mapping": {
"circle": "#/components/schemas/Circle",
"square": "#/components/schemas/Square",
"triangle": "#/components/schemas/EquilateralTriangle",
}
}
},
"Circle": {
"type": "object",
"required": ["type", "radius"],
"properties": {
"type": {
"type": "string",
"enum": ["circle"]
},
"radius": {
"type": "integer"
}
},
},
"Square": {
"type": "object",
"required": ["type", "length"],
"properties": {
"type": {
"type": "string",
"enum": ["square"]
},
"length": {
"type": "integer"
}
}
},
"EquilateralTriangle": {
"type": "object",
"required": ["type", "length"],
"properties": {
"type": {
"type": "string",
"enum": ["triangle"]
},
"length": {
"type": "integer"
}
}
},
}
}
```

In this version, the union type has a [`"discriminator"`](https://swagger.io/docs/specification/v3_0/data-models/inheritance-and-polymorphism/#discriminator) field, and how `"type"` has been added as a single enum property to each variant type.

Is this something you think could be useful? I'm happy to have a go at implementing it and making a PR.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.