OpenAPI-Specification icon indicating copy to clipboard operation
OpenAPI-Specification copied to clipboard

[Question] Extend/override properties of a parameter

Open emauricio opened this issue 4 years ago • 49 comments

Hello there, I got a small question about the compoments[parameter]

Currently, im trying to make some parameters reusable and the basic seems pretty simple.

# 
openapi: 3.0.1
info:
  title: An include file to define common parameters
  version: 1.0.0
paths: 
    /test:
      get:
        ...
        parameters:
          - $ref: 'parameters.yml#/components/parameters/reusableParam'
        ...
components:
  parameters:
    reusableParam:
      in: query
      name: reusableParam
      description: filter something
      schema:
        type: number
        default: 30

Now my question is, how can I avoid to duplicate the reusableParam if another path might need the same one but maybe with required: true or different default: 50

what would be the "correct" way to do it?

Thank you in advance.

emauricio avatar Oct 04 '19 03:10 emauricio

Any update about this? It is quite funny that I end up in my own ticket some months later looking for the same. 😄

emauricio avatar Feb 25 '20 07:02 emauricio

This would be very helpful. I'm also defining parameters which differ only with required attribute (true |false).

bilak avatar Apr 14 '20 14:04 bilak

I have the exact same schema I need to use in three places, I just want to change the top-level description. The schema's the same, the description varies just slightly in ways that are subtly important. So instead of re-using it, I have to duplicate it.

geoffreywiseman avatar Apr 23 '20 19:04 geoffreywiseman

@geoffreywiseman OAS (or whatever version the next release ends up as being) will support $ref with a sibling description property.

MikeRalphson avatar Apr 23 '20 20:04 MikeRalphson

OK, now I'm curious about why the next version might not be OAS. ;) Is there somewhere I go to read about that?

geoffreywiseman avatar Apr 23 '20 20:04 geoffreywiseman

Sorry, there should have been a 3.1 in there, which got lost in editing!

MikeRalphson avatar Apr 23 '20 21:04 MikeRalphson

I agree and also need this feature, but with the addition of wanting to override the example values on my parameters to be more relevant to each operation. Or the ability to do some type of allOf on parameters where I can include an existing parameter definition and override one or more properties as you can with schemas.

gfiehler avatar Sep 17 '20 16:09 gfiehler

Also running into a use for this. I've got parameters that are reused across multiple endpoints, but they're sometimes query parameters and sometimes path parameters, depending on whether they're identifying a single resource or filtering a collection of resources.

colin-p-hill avatar Oct 29 '20 12:10 colin-p-hill

Yes, same need here. Also not the first time I end up on this thread 😄

So much duplication going on in my OpenAPI definitions, at the moment.

osteel avatar Dec 08 '20 11:12 osteel

I am also feeling the need for the same. Need to override name and required property for a path parameter.

bansal6432 avatar Jan 28 '21 07:01 bansal6432

I've another use-case for this

No reuse:

openapi: 3.0.0
components:
  schemas:
    TextContainer:
      type: object
      properties:
        format:
          title: Text Format
          description: Type of formatting of this text.
          type: string
          enum:
          - plain
          - html
          - markdown
          default: plain
        text:
          type: string
      required:
      - text

paths:
  /find:
    get:
      summary: FindTexts
      description: Find specific texts
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
        - name: only
          in: query
          required: false
          schema:
            description: Optionally filter a specific text type.
            type: string
            enum:
              - plain
              - html
              - markdown
              - any
            default: any

3 different features I'd like here, please note that:

  1. the two usages have different description
  2. the two usages have different default
  3. the second usage has an additional property "any" compared to the first usage

The way I would like it to work:

openapi: 3.0.0
components:
  schemas:
    TextFormat:
      title: Text Format
      description: Type of formatting of this text.
      type: string
      enum:
        - plain
        - html
        - markdown
    TextContainer:
      type: object
      properties:
        format:
          $ref: '#/components/schemas/TextFormat'
          default: plain
        text:
          type: string
      required:
      - text

paths:
  /find:
    get:
      summary: FindTexts
      description: Find specific texts
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
        - name: only
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/TextFormat'
            // override
            description: Optionally filter a specific text type.
            enum:
              // some way to tell I want to add to the enum (a similar argument could be done for exclude)
              - $add: [any]
            default: any

danielesegato avatar Mar 18 '21 14:03 danielesegato

Allowing to reuse referenced params with option to override "required" flag would be awsome

EmhyrVarEmreis avatar Apr 25 '21 15:04 EmhyrVarEmreis

Is this going to be available in v3.1 ?

Ramesh-X avatar May 06 '21 09:05 Ramesh-X

Is this going to be available in v3.1 ?

3.1.0 is already out. You can override the description of a parameterObject in a $ref, but not (yet) the required field.

MikeRalphson avatar May 06 '21 10:05 MikeRalphson

Any news about overriding required?

mahnunchik avatar Sep 23 '21 13:09 mahnunchik

Why not allow overriding everything instead of field by field ? Here is what I would like to override:

  • type: since in some case the param could be null and in others not (ex: type: ["object", "null"])
  • writeOnly & readOnly: same reason as above
  • deprecated

it looks like for now $ref is just not doing the job for me unless I missed something

schmurfy avatar Sep 26 '21 08:09 schmurfy

We'd also like to override example/examples. We often have endpoints that return the same object, but the examples should be different

see also: https://github.com/nestjs/swagger/issues/1723

tmtron avatar Dec 13 '21 10:12 tmtron

As the Swagger Editor suggests, one could wrap the $ref into allOff to extend or override its properties:

parameters:
  - allOf:
      - $ref: '#/components/parameters/filters'
      - example: "overridden example"
  - $ref: '#/components/parameters/fields'

See https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/#allof

axl8713 avatar Feb 10 '22 09:02 axl8713

@axl8713 your example is unfortunately not a valid construct in OpenAPI.

parameters:
  - allOf:
      - $ref: '#/components/parameters/filters'
      - example: "overridden example"
  - $ref: '#/components/parameters/fields'

hkosova avatar Feb 10 '22 10:02 hkosova

@axl8713 your example is unfortunately not a valid construct in OpenAPI.

parameters:
  - allOf:
      - $ref: '#/components/parameters/filters'
      - example: "overridden example"
  - $ref: '#/components/parameters/fields'

I've got misleaded by the Swagger Editor then, where it seems at least to be rendered correctly (although with some structural errors).

axl8713 avatar Feb 10 '22 10:02 axl8713

Having the same issues. Would very much want a consistent way to override properties. Others have mentioned examples of properties that basically need this functionality, but I don't see why the spec should pick and choose what properties are overridable.

billytetrud avatar May 02 '22 21:05 billytetrud

This, please.

Midorina avatar May 03 '22 13:05 Midorina

Anything below the schema keyword (which is about half of the requests in this thread) is not covered by the OpenAPI specification, but by JSON Schema. Please start a discussion at https://github.com/json-schema-org/community/discussions and we can help find a solution for you.

karenetheridge avatar May 03 '22 18:05 karenetheridge

I wrote a pre-processing (or I guess a mid-processing) step for our own use that adds a command-like word $merge. It works like this:

$merge:
  - $ref: "some/path/to/a.yaml"
  - tags: # This overrides the tags property
      - "A tag"
    someOtherProp: 
      deeperProp: 'This also deeply overrides"

The $merge key should be a list, where each object in the list is merged in sequence (so later things override). This is really what I want in native openapi.

In the above example, the tags list will be overriden (not merged), and deep object properties will be merged as well. So if a.yaml has a someOtherProp object with a bunch of properties, deeperProp will be added alongside those properties.

const yaml = require('js-yaml');

const doc = yaml.load(bundle);
convertMerge(doc)
fs.writeFileSync(bundlePath, yaml.dump(doc))

// Executes $merge commands, mutates object.
// Returns either the mutated object, or a new object created from a merge.
function convertMerge(object) {
  for (const [key, value] of Object.entries(object)) {
    if (value instanceof Object) {
      try {
        object[key] = convertMerge(value)
      } catch(e) {
        if (e.code === 'convertMergeError') {
          updateConvertMergeError(e, key)
        }
        throw e
      }
    }
    if (key === '$merge') {
      if (Object.keys(object).length > 1) {
        throw convertMergeError("mergeNotAlone", key)
      }
      if (!(value instanceof Array)) {
        throw convertMergeError("mergeNotArray", key)
      }
      return convertMergeShallow(value)
    }
  }
  // else
  return object
}

function convertMergeShallow(mergeList) {
  const cur = {}
  mergeList.forEach(object => {
    Object.assign(cur, object)
  })
  return cur
}

function convertMergeError(message, key) {
  const e = new Error("mergeNotArray")
  e.code = 'convertMergeError'
  e.keylist = []
  e._message = message
  updateConvertMergeError(e, key)
  return e
}
function updateConvertMergeError(e, key) {
  e.keylist.unshift(key)
  e.message = e._message+' for key '+e.keylist.join('.')
}

billytetrud avatar May 03 '22 18:05 billytetrud

@karenetheridge I believe others would also like ways to compose structures that are not under the schema keyword as well. Ideally this would have consistent composition semantics both within and outside of the schema keyword, like my $merge extension/hack does.

billytetrud avatar May 04 '22 16:05 billytetrud

I am evaluating this as a possible use case for overlays, https://github.com/OAI/Overlay-Specification/discussions/9.

Overlays more-or-less incorporate @billytetrud's suggestion in https://github.com/OAI/OpenAPI-Specification/issues/2026#issuecomment-1116418601, here is an attempt:

{ 
  "overlay": "1.0.0",
  "info": {
    "title": "Merge example (Targeted)",
    "description": "Example from https://github.com/OAI/OpenAPI-Specification/issues/2026#issuecomment-1115404416",
    "version": "1.0.0"
  },
  "actions": [
    {
      "target": "$.tags",
      "description": "Remove tags entirely to override",
      "remove": true
    },
    {
      "target": "$.",
      "description": "new tag",
      "update": {
        "tags": [
          "A tag"
        ]
      }
    },
    {
      "target": "info.someOtherProp",
      "description": "deep object properties will be merged",
      "update": {
        "new_property": "A different property"
      }
    }
  ]
}

I think overlays would cover the use case from @danielesegato's comment as well, https://github.com/OAI/OpenAPI-Specification/issues/2026#issuecomment-801961448. I have seen this pattern used in contracts to reduce duplication, though not eliminate. I believe this is valid openapi:

{
  "openapi": "3.0.0",
  "paths": {
    "/find": {
      "get": {
        "summary": "FindTexts",
        "description": "Find specific texts",
        "parameters": [
          {
            "name": "only",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/TextContainer/properties/format"
            }
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "TextContainer": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "format": {
            "type": "string",
            "enum": [
              "plain",
              "html",
              "markdown"
            ]
          }
        }
      }
    }
  }
}

then add overrides via overlay

{ 
  "overlay": "1.0.0",
  "info": {
    "title": "Add options (Targeted)",
    "description": "Example from https://github.com/OAI/OpenAPI-Specification/issues/2026#issuecomment-801961448",
    "version": "1.0.0"
  },
  "actions": [
    {
      "target": "$.paths['get'].parameters[@.name =='only].schema",
      "description": "Add <any> option to request param",
      "update": {
        "enum": [
          "any"
        ]
      }
    },
    {
      "target": "$.paths['get'].parameters[@.name =='only]",
      "description": "Add any option to request param, add param description and default",
      "update": {
        "description": "Optionally filter a specific text type.",
        "default": "any",
        "schema": {
          "enum": [
            "any"
          ]
        }
      }
    },
    {
      "target": "$..parameters[@.in == 'query']..enum~",
      "description": "any query param with enum, not sure about target syntax",
      "update": {
          "enum": [
            "any"
          ]
        }
    }
  ]
}

kscheirer avatar Aug 25 '22 00:08 kscheirer

I wrote a pre-processing (or I guess a mid-processing) step for our own use that adds a command-like word $merge. It works like this:

$merge:
  - $ref: "some/path/to/a.yaml"
  - tags: # This overrides the tags property
      - "A tag"
    someOtherProp: 
      deeperProp: 'This also deeply overrides"

The $merge key should be a list, where each object in the list is merged in sequence (so later things override). This is really what I want in native openapi.

In the above example, the tags list will be overriden (not merged), and deep object properties will be merged as well. So if a.yaml has a someOtherProp object with a bunch of properties, deeperProp will be added alongside those properties.

const yaml = require('js-yaml');

const doc = yaml.load(bundle);
convertMerge(doc)
fs.writeFileSync(bundlePath, yaml.dump(doc))

// Executes $merge commands, mutates object.
// Returns either the mutated object, or a new object created from a merge.
function convertMerge(object) {
  for (const [key, value] of Object.entries(object)) {
    if (value instanceof Object) {
      try {
        object[key] = convertMerge(value)
      } catch(e) {
        if (e.code === 'convertMergeError') {
          updateConvertMergeError(e, key)
        }
        throw e
      }
    }
    if (key === '$merge') {
      if (Object.keys(object).length > 1) {
        throw convertMergeError("mergeNotAlone", key)
      }
      if (!(value instanceof Array)) {
        throw convertMergeError("mergeNotArray", key)
      }
      return convertMergeShallow(value)
    }
  }
  // else
  return object
}

function convertMergeShallow(mergeList) {
  const cur = {}
  mergeList.forEach(object => {
    Object.assign(cur, object)
  })
  return cur
}

function convertMergeError(message, key) {
  const e = new Error("mergeNotArray")
  e.code = 'convertMergeError'
  e.keylist = []
  e._message = message
  updateConvertMergeError(e, key)
  return e
}
function updateConvertMergeError(e, key) {
  e.keylist.unshift(key)
  e.message = e._message+' for key '+e.keylist.join('.')
}

is it possible to do something similar as to adding a new 'message' field under components : schemas object for each propetry? then used in composition with 'pattern/min/max, etc../ fields

Kenpachi-zara avatar Jan 06 '23 15:01 Kenpachi-zara

Late to the party, but using the same definition with different default values in different endpoints makes a lot of sense. As someone said just enable overriding for any property.

BTW, is there any workaround we can use in the meantime?

ventsyv avatar Mar 20 '23 21:03 ventsyv

BTW, is there any workaround we can use in the meantime?

Yes, use any macro language (e.g. python-jinja2 templates) to generate your OpenAPI specs.

pavelschon avatar Mar 27 '23 09:03 pavelschon

If you are using YAML instead of JSON for your API spec (all of the examples on this thread are), you can use anchors, aliases, and overrides that are part of YAML. https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/

Prior to OpenAPI 3.0, the 2.0 documentation talks about this regarding enums: https://swagger.io/docs/specification/2-0/enums/

@MikeRalphson Would be a good idea to include YAML topics in the OpenAPI specification docs for general use cases.

zalla2100 avatar Mar 27 '23 19:03 zalla2100