Skip to content

Allow changing options.type when patching options #26

@emk

Description

@emk

Right now, if we have a type with:

members:
  options:
    mutable: true
    schema:
      $interface: "ShapeOptions#SameAsInterface"

...and ShapeOptions is defined using the following (as supported by #18):

components:
  interfaces:
    # A "oneOf" interface can be used to generate a TypeScript type union or a
    # Rust enumeration, including the "Post", etc., variants.
    #
    # This is not a superclass! It's a type union, using `|` in TypeScript to
    # allow more than one type. This affects the design in several surprising
    # ways. Among other things, we might have several union types that include
    # different concrete interface types in their union. For example,
    # `ShapeOptions`, `RoundishShapeOptions`, etc.
    #
    # We can only create a `oneOf` union of multiple interfaces if the
    # individual types _all_ use the same discriminator member (see below).
    ShapeOptions:
      description: "Options for a shape."
      oneOf:
        - $interface: "SquareShapeOptions#SameAsInterface"
        - $interface: "RoundShapeOptions#SameAsInterface"

    SquareShapeOptions:
      # The discriminator is a property of `SquareShapeOptions`, even if this
      # type appears without being part of `ShapeOptions`. This is because it
      # affects how the variants of this type get generated.
      discriminatorMemberName: "type"
      members:
        type:
          required: true
          initializable: true
          schema:
            type: string
            const: square
        height:
          required: true
          mutable: true
          schema:
            type: number
        width:
          required: true
          mutable: true
          schema:
            type: number

    RoundShapeOptions:
      discriminatorMemberName: "type"
      members:
        type:
          required: true
          initializable: true
          schema:
            type: string
            const: round
        radius:
          required: true
          mutable: true
          schema:
            type: number

...then it is impossible to send a Merge Patch changing .options from:

{
  "type": "square",
  "height": 10,
  "round": 20
}

...to:

{
  "type": "round",
  "radius": 15
}

This patch necessary for this conversion looks like:

{
  "type": "round",
  "radius": 15,
  "height": null,
  "width": null,
}

We need to be able to pass other keys as null, so we can remove the original square parameters when PATCHing type.

There are several strategies we can use here:

  1. Allow passing any key with "key": null. We could do this using additionalProperties: { schema: { type: "null" } } }. But we need to check with @blakew about whether this breaks our doc tooling.
  2. Only allow height: null and width: null in RoundShapeOptionsMergePatch. But this would require generating a special version of RoundShapeOptionsMergePatch that knows about SquareShapeOptionsMergePatch. Call it "ShapeOptionsRoundShapeOptionsMergePatch". But since oneOf is a type union, not inheritance, we might need several versions of RoundShapeOptionsMergePatch, one for each type union which includes it. So if we have type RoundedShapeOptions = RoundShapeOptions | EllipticalShapeOptions, then we would need RoundedShapeOptionsRoundShapeOptionsMergePatch and RoundedShapeOptionsEllipticalShapeOptionsMergePatch, and so on. This is one of those classic bad ideas that just keeps getting worse.
  3. Like (2), but we bake RoundShapeOptionsMergePatch and SquareShapeOptionsMergePatch directly into ShapeOptionsMergePatch. This would require a lot of code, but we might be able to make it nice?

@hanakslr and @blakew, I'd love your thoughts here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions