OAI/OpenAPI-Specification

Integer enum with named items

kenjitayama opened this issue Β· 45 comments

I would like to propose a new field called enumNames.

Example:

definitions:
  weather:
    type: object
    required:
      - type
    properties:
      type:
        type: integer
        format: int32
        enum:
          - 1
          - 2
          - 3
        enumNames:
          - Sunny
          - Cloudy
          - Rainy

This information is necessary for generating the following Java enum.

public enum Weather {
    Sunny(1),
    Cloudy(2),
    Rainy(3);

    private final int type;

    private Weather(final int type) {
        this.type = type;
    }

    public type getType() {
        return type;
    }
}

Some web APIs require clients to send/retrieve integer enum items while knowing what they mean. So it is useful to be able to generate this kind of enum.

I had been seeking advice in the following swagger-codegen issue, and got a conclusion that we need a vendor extension (which corresponds to this proposal) using Swagger 2.0 spec.
swagger-api/swagger-codegen#2690

It's not related to json-schema-org/json-schema-spec#348, it's a duplicate for it.

@webron
json-schema-org/json-schema-spec#348 is talking about documenting each enum items, rather than assigning names to enum items (this issues topic).
So I think this issue is not a duplicate.

Fair point, missed that.

I like this, combined with json-schema-org/json-schema-spec#348:

type: integer
format: int32
enum:
 - value: 1
   name: Sunny
   description: Blue sky
 - value: 2
   name: Cloudy
   description: Slightly overcast
 - value: 3
   name: Rainy
   description: Take an umbrella with you

If the names should be unique (probably a prerequisite for code generation) a hashmap would be more appropriate:

type: integer
format: int32
enum:
  Sunny:
    value: 1
    description: Blue sky
  Cloudy:
    value: 2
    description: Slightly overcast
  Rainy:
    value: 3
    description: Take an umbrella with you

So enum could either have an array value with just the list of values, or an object value with named and potentially further annotated values.

Well, either the name or the value has to be unique, doesn't matter which one as long as the intent is clear.

However, where does it end? Just a single value? Technically in Java (referenced in the original comment) allows setting any number of properties for enum values. What about types? There's no indication it's an int, it could be a string a string as well. Should that be inferred? Are we supposed to introduce a big construct to support all these options? Also, I have no idea what other languages support and how it would affect them.

And last but not least, if I just want to define a basic enough, would I have to end up with something like:

enum:
  Sunny: {}
  Cloudy: {}
  Rainy: {}

That's...

This feels like a leaking of implementation details in the API, seemingly no different then describing every possible combination of values for a proper object's properties. I suspect it is the result of attempting to use the standard to automatically create an sdk for functional calls, in which enums are being semantically defined and maintained here. To me, enums are an object-type not a value-type and thus shouldn't get any special treatment.

Now I think @ralfhandl's former format is best.

  1. ralfhandl's former fomat
  2. ralfhandl's latter format
    • This makes assigning a name a requirement, which may be too much of a breaking change?
    • I think the uniqueness of names doesn't have to be baked in to the format. The OAI spec can specify that.

For webron's comments…

Well, either the name or the value has to be unique, doesn't matter which one as long as the intent is clear.

I think both should be unique and should be written in the specs.

However, where does it end?

I don't feel the number needs to be limited.

What about types?

Value types of enums are specified since Swagger 2.0, like this:

type: integer
format: int32
enum:
  :

(Looks like only strings were allowed in 1.0)

For wparad's comments…

I consider "naming enum items" is a missing spec in Swagger 2.0. In general, for an enum to be an enum, each item should have an name.
According to wikipedia:

In computer programming, an enumerated type (also called enumeration or enum, or factor in the R programming language, and a categorical variable in statistics) is a data type consisting of a set of named values called elements, members, enumeral, or enumerators of the type.

ePaul commented

@webron The name (if there is one) needs to be unique in the target programming language. The value needs to be unique "on the wire". But the value is not necessarily a string, it can be anything (string, number, boolean, object, array). Therefore using the value as a keys in a map / property names in an object doesn't really work.

I wasn't advocating the map option, just picked one of the two.

I'd like to clarify that the limitations for the enum declarations are derived from JSON Schema - we did not introduce any special restriction there.

Also, unlike what @kenjitayama mentioned, the type is not derived from the type mentioned in the encompassing definition - again, that's the way JSON Schema works.
For example:

type: string
enum:
  - 1
  - 2
  - 3

doesn't mean that "1" is the valid value - it means that nothing will validate against that schema, because none of the enum values are strings.
Regardless, I was referring to the types of additional values for names enums, not the 'root' values. For simplicity, if we do provide a solution we may limit it to just one value.

There are two problems to breaking away from the current structure:

  • None of the existing JSON Schema validator could handle it, so customized tooling is required - this may hinder adoption.
  • The constraint to require both the names and values to be unique couldn't be enforced by the (meta) JSON Schema and will require external validation.

I'm not sure I understood what webron said about types, but I found that Swagger 2.0 uses JSON Schema Draft 4 (the latest) and enum spec is derived from it.
Looks like it's for validation rather than for supplementing description of data type(so this issue's use case is out of the scope).

Next, I found these proposals for JSON Scheme v5

I wonder if one of this would be accepted, and if OpenAPI v3 will support JSON Schema v5. And when it would happen.

Other reference:

While we've yet to reach that point, it's unlikely draft 5 will be supported because it's not finalized. Even if finalized before finalization of 3.0, it's unlikely they'd be enough tooling around it for us to claim support of it. That said, if it's finalized before, it's easier to derive the structure from it and use it as well.

I see there's a long way to go.
But at least, now it is clear what is necessary to support this.

Is there any recommendation for how to define these possible values in the existing swagger specifications until this becomes supported?

+1

Is this still going on? Because it would help A LOT

Because this breaks away from JSON Schema, we've not included support for it in 3.0.

I saw some great suggestions on using something like:

enum:
  key: value

Along with using the common method. Is this valid yet?

Things haven't changed in the last 6 hours ;)

This is a problem for us too. Are there any hacky work arounds to make this work in the meantime?

@athornz Microsoft tooling uses the extension x-ms-enum

It's been over a year since the last comments, has anything changed on this front? All proposed options would work for me.

From a JSON Schema perspective, I would go with something like what @ralfhandl suggested, or the following (note that in later versions of JSON Schema there is a const keyword which makes this a lot more clear than single-value enum does):

oneOf:
  - enum: [1]
    title: Sunny
    description: Blue sky
  - enum: [2]
    title: Cloudy
    description: Slightly overcast
  - enum: [3]
    title: Rainy
    description: Take an umbrella with you

We have rejected several named enumeration proposals (including some linked here, I suspect) because none of them were as flexible. They all were suited to specific use cases, but not suited to others, and therefore not good candidates for standardization in the main JSON Schema specs. Note that there are numerous UI form generation extensions to JSON Schema that address UI concerns directly- depending on how complicated your UI generation is, you might prefer to check that out.

JSON Schema will not be adding any additional keywords for this, as oneOf plus const works quite well. (for those who do not know, I am one of the two primary editors of the JSON Schema spec).

@handrews Awesome, could you share what the above would look like with const?

In the meantime I also ended up using x-ms-enum and implementing that extension in our parser/generator.

@rlabrecque it's just:

oneOf:
  - const: 1
    title: Sunny
    description: Blue sky
  - const: 2
    title: Cloudy
    description: Slightly overcast
  - const: 3
    title: Rainy
    description: Take an umbrella with you

Yeah, I think that would definitely be ideal!

jmini commented

With OpenAPI v3, some tools supports a x-enum-varnames extension to do so:

  weather:
    type: object
    properties:
      type:
        type: integer
        format: int32
        enum:
          - 1
          - 2
          - 3
        x-enum-varnames:
          - Sunny
          - Cloudy
          - Rainy

Other has started to use x-enum-values (see Extension Properties on OpenAPI Specs)

peol commented

@handrews et. al.: Any idea if OpenAPI v3 will adopt the new JSON schema draft (and subsequently support const in schemas)? I'm curious because otherwise I'd have to introduce x-foo-const for now which will have to live for quite a while :)

@peol There are two proposals related to this:

  • PR #1736 for alternativeSchema (Issue #1532) which would, among other things, allow specifying an external schema file of various sorts, including more recent JSON Schema drafts. The Technical Steering Committee (TSC) is considering this for 3.1
  • PR #1766 is a discussion-only PR not intended to be merged, that floats an idea for moving the main supported draft (that you can use without alternativeSchema up to at least draft-06, which would include const. I do not know if the TSC is seriously pursuing this for 3.1 or not, but I am following it with interest :-)
peol commented

@handrews Fantastic, thanks for the update! I'll make sure to follow those PRs and see if I can help in any way.

This is also an issue for C#. NSwag is currently generating the following for an enum because their is no support for name.

Generated Example:
public enum ObjectState
{
_0 = 0,
_1 = 1,
_2 = 2,
_3 = 3
}

Generated Code should like the following:
public enum ObjectState
{
Unchanged = 0,
Add = 1,
Update = 2,
Delete = 3
}

Face with same issue on NSwag client generation.

@BlackGad @Kevweir @rlabrecque @peol etc.

OpenAPI 3.1 is likely to move to JSON Schema draft 2019-09 (formerly known as draft-08) per PR #1977. That would allow the solution I showed above and allows for creating new re-usable JSON Schema vocabularies (sets of keywords) for specific purposes such as code generation. These can be formally identified so that different implementations can recognize and implement them (which was previously not possible in JSON Schema, or at least not in any practical way). So:

Here's an example with a totally fictional codeGen keyword that provides guidance on how to interpret the Schema Object in which it appears (see also Appendix E of the current JSON Schema Core specification for another example of how code generation keywords could be defined).

codeGen: namedEnum
oneOf:
  - const: 1
    title: Sunny
    description: Blue sky
  - const: 2
    title: Cloudy
    description: Slightly overcast
  - const: 3
    title: Rainy
    description: Take an umbrella with you

In this example, the codeGen keyword informs the generator that the Schema Object should be interpreted as a named enum. Presumably, the definition of how this keyword works would say something like "the namedEnum value, when it appears alongside of a oneOf where each branch of the oneOf contains a const and a title, indicates that the oneOf should be treated as a named enumeration, where const gives the value and title gives the name."

This way, we do not need to add more keywords that have an identical validation effect, which adds to the burden of validator implementations. Validators can safely ignore the unknown-to-them codeGen keyword and treat the other keywords as usual for validation purposes, while code generators get the additional information they need from the specialized keyword.

This is just one of many ways this (and other code generation problems) could be solved. Hopefully once OAS 3.1 is out (assuming draft 2019-09 JSON Schema support is indeed included) the OpenAPI community will use the new features to try out different ideas and converge on a standard code generation vocabulary. The advantage of OAS 3.1 + 2019-09 JSON Schema is that this process of producing a new vocabulary can happen independent of both the JSON Schema and OAS specification processes.

@MikeRalphson @webron @darrelmiller OAS 3.1 was released,any update on this issue?

should we use codeGen: namedEnum?

@xiaoxiangmoe I made up codeGen: namedEnum on the spot when writing that comment. A more likely approach would be a code generation vocabulary of keywords rather than a single codeGen keyword which would get very overloaded very quickly. I'm not aware of any such vocabulary, but now that OAS 3.1 is out and fully compatible with JSON Schema 2020-12, anyone (such as a tooling vendor) can create a vocabulary and work to get it supported in implementation without having to wait for a new version of OAS or JSON Schema.

It will obviously take a bit for JSON Schema vendors to add support for vocabularies (meaning, add the annotation output support and enable folks to write plugins to handle previously unknown vocabularies rather than just hardcoding all keyword support) and for OAS vendors to make use of that (meaning make use of annotations from JSON Schema to recognize codegen use cases).

So the solution is now possible in theory, but there are several things that need to be done before it's usable in practice.

Note that if you take the json of an OpenAPI modal directly, without any modifications, and use it as the basis of your JSON Schema Form, you instantly have a nice looking form using all the information from the OpenAPI (title, description, format etc.) where the user can submit data. This way I make the latest developments of an API quickly available to my colleagues.

If this proposal is accepted, that would enable dropdowns where the enumNames are displayed but the underlying enums are in the payload.

My point is that the JSON Schema Form specification is exactly the same as OpenAPI except for this one thing and therefore, I fully support this proposal.

@MarkRosemaker parallel arrays are horribly error-prone which is why the proposal as initially shown here has never drawn serious support within JSON Schema. The oneOf + some keyword that signifies to a generator tool (form, code, docs, whatever) to interpret that oneOf as a named/titled/whatever enumeration based on the const value in each branch avoids the parallel array problem and is in general much more flexible as you can include additional information, such as a help string to be extracted and associated with the value somehow.

This is similar. I have several linked fields that could be each in a separate enum, but they go together (OK, system could be static, but code and display need to be linked):

          coding:
            type: array
            items:
              enum:
                - {system: http://terminology.hl7.org/CodeSystem/v3-RoleCode,code: _DedicatedServiceDeliveryLocationRoleType,display: DedicatedServiceDeliveryLocationRoleType} 
                - {system: http://terminology.hl7.org/CodeSystem/v3-RoleCode,code: _DedicatedClinicalLocationRoleType,display: DedicatedClinicalLocationRoleType} 
                - {system: http://terminology.hl7.org/CodeSystem/v3-RoleCode,code: DX,display: Diagnostics or therapeutics unit} 
                - {system: http://terminology.hl7.org/CodeSystem/v3-RoleCode,code: CVDX,display: Cardiovascular diagnostics or therapeutics unit} 

Is there a way to link two or more enum values like this? It doesn't give an error, but the enum values don't render in schema output. Also, the field names are fixed, I can't use "title" and "description".

I've tried the following, but the enum still doesn't render:

          coding:
            type: array
            items:
              type: object
              properties:
                system:
                  type: string
                  default: http://terminology.hl7.org/CodeSystem/v3-RoleCode
                code:
                  type: string
                display:
                  type: string               
              enum:
                - {code: _DedicatedServiceDeliveryLocationRoleType,display: DedicatedServiceDeliveryLocationRoleType} 
                - {code: _DedicatedClinicalLocationRoleType,display: DedicatedClinicalLocationRoleType} 
                - {code: DX,display: Diagnostics or therapeutics unit} 
                - {code: CVDX,display: Cardiovascular diagnostics or therapeutics unit} 

Yes, I know you could say this is an implementation issue with the tools, but I would just like to know how to link two or more value sets so that they are consistent rather than having separate enum sets for "code" and "display" strings.

@Tasselhoff the answer is still the same as in earlier comments. Since this is fundamentally a JSON Schema concern, there is a JSON Schema issue tracking the codegen: namedEnum approach (although the example there uses docHint: enum - I doubt either of those will be the actual name). It is also possible for a 3rd-party to implement this as a JSON Schema extension vocabulary. See also #2542. Anything about anything "rendering" is definitely a tooling issue and not a spec issue.

I've made a suggestiion in swagger-ui that integrates both linked fields and parent-child relationships, but it straddles the rendering and the OpenAPI Specification implementation swagger-api/swagger-ui#7260

Edit: as you can no doubt see, I'm pretty new at this and my desire for certain functionality far exceeds my technical domain knowledge...

We've decided to close this issue, following @handrews's proposal.

For reference:

codeGen: namedEnum
oneOf:
  - const: 1
    title: Sunny
    description: Blue sky
  - const: 2
    title: Cloudy
    description: Slightly overcast
  - const: 3
    title: Rainy
    description: Take an umbrella with you

The oneOf different const values allows you to define an extended enum structure with any additional information required, ending with an equivalent result.

Regarding the concerns about code-generation - we feel code generators can detect such structure and convert it to enum stucts if needed. As proposed by @handrews, there can be a codegen vocabulary that adds annotations to give hints to such tools (see: codeGen: namedEnum). If anyone is interested in working on such a vocabulary, please join the discussion at https://github.com/json-schema-org/vocab-idl.

Since now we're fully aligned with JSON Schema, any changes to how schemas are defined in OpenAPI would be addressed by the JSON Schema team and not the OpenAPI.

@webron I can't find any documentation for the codeGen field in JSON Schema Standard. Could you please provide the corresponding documentation? Thank you a lot.

@xiaoxiangmoe codeGen was an example proposal. It and other code generation ideas need to be fleshed out.