OAI / OpenAPI-Specification

The OpenAPI Specification Repository
https://openapis.org
Apache License 2.0
29k stars 9.07k forks source link

Support interdependencies between query parameters #256

Closed cwarny closed 9 months ago

cwarny commented 9 years ago

It would be great to be able to specify interdependencies between query parameters. In my app, some query parameters become "required" only when some other query parameter is present. And when conditionally required parameters are missing when the conditions are met, the API fails. Of course I can have the API reply back that some required parameter is missing, but it would be great to have that built into Swagger.

webron commented 9 years ago

This is something that's very complicated to describe elegantly. Feel free to suggest a format that you think would be suitable for it.

cwarny commented 9 years ago

My suggestion would be as follows: instead of having the field "required" boolean, make it either boolean or an array of references to other parameters. If you put required: true, it would have to always be present. If you put required: [$a, $b], it means that this parameter is required only when parameters a and b are present.

mohsen1 commented 9 years ago

How you describe an endpoint where one of it's parameters is required if another query parameter is equal to a string or is larger than a number? This can get very complicated. I would say if a parameter is required sometimes it's not required.

cwarny commented 9 years ago

I'd say either keep it simple and only allow simple interdependencies like, if a is present, then bis required (i.e. required: [$a, $b]), or define simple operations like, required: [$a>3, $b=="hello", "world" in $c]. I would favor keeping it simple, but then I have to admit that my only response to "why not keep it even simpler (i.e. as it is now) and only have a boolean required" is: it's a matter of how useful more sophistications are. For me, having simple interdependencies is useful and better than just a boolean required, but I don't think I need more complicated stuff. Other people might disagree.

webron commented 9 years ago

While this may be a solution, it can also be a validation nightmare. Definitely not something that can be done with a json schema.

iisurge commented 9 years ago

maybe something like "required": false, "depends": [ $a, $b ]

rcork commented 9 years ago

Don't forget the "OR" dependency. I have a service where one of two query parameters must be present but it doesn't matter which one. For now we are just documenting the interdependency and using a error schema with the appropriate code/message but would be preferable to have this defined in the contract up front.

fractalawareness commented 8 years ago

+1 for cwarny simple suggestions

filipnowakswissre commented 8 years ago

My use-case is to have only one property of the parameter required, but from the array of the definitions.

craig-bayley commented 7 years ago

You can solve the OR use case already using an enumerated filter_type paired with a filter_value query parameter(s).

Example:

        - name: filter_type
          in: query
          description: What type of values to filter on
          required: false
          type: string
          enum: [location, service, name]
          default: location
        - name: filter_value
          in: query
          description: The value to apply to the filter_type. Should match what was in the filter, i.e. if filtering on location use a location name.
          required: false
          type: string
          default: all
filipnowakswissre commented 7 years ago

wow, good one, thanks

ePaul commented 7 years ago

@craig-bayley could you format your example as YAML, please? Just adding

```yaml
[...]
```

around it makes it much easier to read.

craig-bayley commented 7 years ago

@ePaul Thanks for the tip. Done.

scottmas commented 7 years ago

+1 Something like this would be very nice.

fehguy commented 7 years ago

Folks just to set expectations, we're not doing this in the initial cut of 3.0

handrews commented 7 years ago

some query parameters become "required" only when some other query parameter is present

The JSON Schema "dependencies" keyword does exactly this. The "if"/"then"/"else" keywords (json-schema-org/json-schema-spec#180) that seem likely to go into a near-future draft would also accomplish this (probably more clearly for most people).

webron commented 7 years ago

I don't see how JSON Schema would be relevant for this at all.

handrews commented 7 years ago

@webron further up you said:

Definitely not something that can be done with a json schema.

so I assumed that meant that JSON Schema was at least potentially relevant, which meant that I should point out that JSON Schema can in fact do this. I think you were specifically referring to an approach based on "required" which would not be valid JSON Schema, but there is JSON Schema support for this use case.

webron commented 7 years ago

Gotcha. That referred to the validation of it, not the representation of it in the spec. I assumed you were referring to the representation, making it my bad.

k11k2 commented 7 years ago

When it going to be on air ? much needed one.

jainpradeep100 commented 7 years ago

I have need to generate @Context HttpHeader parameter by Swagger. Is it possible or not? If possible then how it would be accomplish.

webron commented 7 years ago

@jainpradeep100 for tool-specific questions, please ask in its respective support channel, not here.

HappyTreesDev commented 7 years ago

Required could hold a simple true, false, or it could hold an object like:

"required": [{
    "name" : "name of other param",
    "relationship" : "present, missing, or maybe some equation",
    "grouping" : "AND Vs OR"
}]

Something along the line of that where required would take an array of requirement objects that define the other relationships that the param has with other params.

AndreKR commented 7 years ago

I don't have enough experience with the OpenAPI specs to say if this is a good idea, but how about having an array of constraints, similar to how you would place CHECK constraints on a database schema.

Building upon this example, something like:

paths:
  /report:
    get:
      parameters:
        - name: rdate
          in: query
          schema:
            type: string
          description: >
             A relative date range for the report, such as `Today` or `LastWeek`.
             For an exact range, use `start_date` and `end_date` instead.
        - name: start_date
          in: query
          schema:
            type: string
            format: date
          description: >
            The start date for the report. Must be used together with `end_date`.
            This parameter is incompatible with `rdate`.
        - name: end_date
          in: query
          schema:
            type: string
            format: date
          description: >
            The end date for the report. Must be used together with `start_date`.
            This parameter is incompatible with `rdate`.
      constraints:
        - type: required_either_or
          either: [rdate]
          or: [start_date, end_date]

This way all kinds of relationships can be expressed, they can be extended in a backwards-compatible manner and shorthands for common scenarios make it intuitive to read.

handrews commented 7 years ago

I'm probably going to annoy people by being a broken record, but if parameters was a JSON Schema that was required to be of type "object", then you could express all of this in JSON Schema easily enough. In the forthcoming draft-07, "if"/"then"/"else" makes it a lot more readable and easier to work with in code than implementing conditionals with "oneOf".

AndreKR commented 7 years ago

I didn't notice I had picked an example where it's not a JSON Schema, in fact my constraints idea applies equally to for example requestBody, which is kind of compatible with JSON Schema. So parameters not being a JSON Schema is a related but separate issue.

That said, my experience with JSON Schema and conditionals is limited to the abilities of ajv-keywords, but to be honest that was disappointing. I expected to be able to require and to forbid the existence of keys, values, default values and sub-schemas, using either the existence of keys or their values as a condition. It turned out half of those constraints are impossible to implement with ajv-keyword's if/then/else.

Is the JSON Schema proposal mightier?

handrews commented 7 years ago

@AndreKR ajv-keywords' if-then-else is the preliminary implementation, which got lots of positive feedback. But please feel free to comment at https://github.com/json-schema-org/json-schema-spec/pull/375

I think we can work most of those out but let's not clutter up OAI's issue with that discussion :-)

sivaramankannan commented 7 years ago

This is something I have been looking for a long time. I like the idea of interoperating with the JSON schema for achieving this - please let me know the plans for this feature and if its on the cards.

mewalig commented 6 years ago

In the interest of flexibility... how about implementing the ability to define a third-party syntax (like jq)? From the perspective of the OpenAPI, the specification would merely require that it is specified as a third-party type, and contains the type identifier (e.g. "jq"), and corresponding filter/condition definition (e.g. the jq filter). Long shot but figured I'd ask...

MikeRalphson commented 6 years ago

@mewalig I appreciate you mentioned jq only as an example, but as far as I am aware, the jq syntax is not standardised anywhere, it only exists on the jq website documentation pages, and jq itself is only available as a standalone program, not even a library for other tools to use.

That said, I'd be interested in seeing an example of the sort of syntax which you think might help with this issue.

Nb: allowing multiple third-party syntaxes risks, as in OAI/OpenAPI-Specification#764, fragmenting the tooling community as to which tools support which syntaxes.

mewalig commented 6 years ago

@MikeRalphson I agree with those concerns and assume they stem from a requirement, were OpenAPI to go down this path, that the syntax of any supported third-party filter/condition/content be pre-defined and incorporated (directly or by reference) into the OpenAPI standard?

If so, one potential approach would be to offer this functionality as a further supported option of an OpenAPI extension or some other way to essentially require various items to be provided in order for the third-party syntax to conform.

As a limited-scope example, a "jq" extension / package might require that the jq syntax is made available in some specified form. The term "made available" could be realized by any/all of

Regardless of which of the above are supported, there would need to be a grammar specification that is defined in some form such as BNF or similar-- and there would need to be a "meta" grammar specification for the grammar specification. If the spec must be sufficient not just to validate rule syntax but also to validate the rule in the specific context in which it is being used, then the meta-grammar would also need to support a mechanism to identify items it must interpret, when validating, as data elements (for example, the jq rule .parameter.abc == "hello" is valid syntax, but would be invalid in the context of input that is not a dictionary with a member called "parameter" that is a dictionary type. If OpenAPI would require that this latter kind of error be detectable, the grammar spec would need some information to instruct the parser how to know that ".parameter.abc" refers to such a member).

Apart from specifying the grammar, applying a rule could be fairly straightforward. For example, a constraint requiring that the GET param "abc" equals "hello" might look something like the below:

paths:
  /report:
    get:
      parameters:
        abc:
          - type: array
          ...
      constraints:
        - type: extension
          name: arbitrary_name
          input_type: json # specify that the extension will consume the data as JSON. Could also be XML or other predetermined supported types
          content: .parameters | .abc[0] == "hello"
syntaxes:
  arbitrary_name:
    grammar: <grammar definition goes here>
hkosova commented 6 years ago

@AndreKR commented on Aug 31, 2017: Building upon this example, something like:

paths:
  /report:
    get:
      parameters:
        - name: rdate
          in: query
          schema:
            type: string
          description: >
             A relative date range for the report, such as `Today` or `LastWeek`.
             For an exact range, use `start_date` and `end_date` instead.
        - name: start_date
          in: query
          schema:
            type: string
            format: date
          description: >
            The start date for the report. Must be used together with `end_date`.
            This parameter is incompatible with `rdate`.
        - name: end_date
          in: query
          schema:
            type: string
            format: date
          description: >
            The end date for the report. Must be used together with `start_date`.
            This parameter is incompatible with `rdate`.
      constraints:
        - type: required_either_or
          either: [rdate]
          or: [start_date, end_date]

A possible workaround that exists in OpenAPI 3.0 is to define the interdependent parameters as a single object-type parameter with style: form+explode: true, and use the appropriate JSON Schema constraints (oneOf, anyOf, etc.) within the object schema.

paths:
  /report:
    get:
      parameters:
        - in: query
          name: date_range
          required: true
          schema:   # either ?rdate=...  or ?start_date=...&end_date=...
            type: object
            oneOf:
              - properties:
                  rdate:
                    type: string
                    description: A relative date range, such as `Today`.
                required: [rdate]
                additionalProperties: false
              - properties:
                  start_date:
                    type: string
                    format: date
                  end_date:
                    type: string
                    format: date
                required: [start_date, end_date]
                additionalProperties: false
          style: form
          explode: true

But this feels more of a hack than a real solution. Also, handling this in codegen will probably be tricky.

handrews commented 6 years ago

@hkosova

and use the appropriate JSON Schema constraints

This is exactly why URI Template parameters in JSON Hyper-Schema are handled with a single schema (the hrefSchema keyword plus some supporting modifiers). It treats the URI Template as an object in the data model, and applies hrefSchema to the object as a whole. So yes, you almost always end up having to say {"properties": {...}"} which is a tiny bit of annoying boilerplate, but it means that every single feature of JSON Schema works with parametrized URIs. hrefSchema covers path and query parameters.

Likewise headerSchema, which would cover header and (indirectly) cookie parameters, although you can't mix it with hrefSchema. So at least in the current draft you can't have dependencies between hrefSchema parameters and headerSchema parameters.

This would obviously have to be an OpenAPI 4 proposal, and even then might be too fundamentally against OAS's architectural principles, but the Parameter object could easily be replaced by a schema augmented with an extension vocabulary for for things like deepObject and other non-RFC 6570 / non-standard-JSON-Schema concepts.

[EDIT: There's also work towards an extension vocabulary to disambiguate validation keywords like allOf for codegen]

cernael commented 6 years ago

Would allowing composition for the parameters object be an option? I'm thinking something like:

paths:
  /pets/{pet_type}/{name}:
    get:
      summary: retrieve pet
      description: returns pet object
      parameters:
        oneOf:
          - 
            - name: pet_type
              in: path
              required: true
              description: type of pet
              schema:
                enum:
                  - cat
            - name: name
              in: path
              required: true
              description: name of cat
              schema:
                enum:
                  - Quaxo
                  - Corricopat
                  - Jellylorum
          - 
            - name: pet_type
              in: path
              required: true
              description: type of pet
              schema:
                enum:
                  - dog
            - name: name
              in: path
              required: true
              description: name of dog
              schema:
                enum:
                  - Buster
                  - Sture
                  - Old Yeller
sleon-fmi commented 6 years ago

This is a very common requirement in APIs I've encountered 'in the wild'. It would help a lot in describing legacy systems.

mewalig commented 6 years ago

I agree. However, I am not sure that the current approach can, or is even attempting to be able to, fully describe legacy APIs that did not have the OpenAPI limitations in mind when created. In my ideal world, I'd like for the API specification to be able to:

  1. describe any number of validation expressions, each of arbitrary complexity
  2. provide error messages corresponding to each of those validation expressions
  3. support placeholders and substitutions within those error messages
  4. support locale-specific error messages

Of these, to me, the first and second points are the most important, and it doesn't seem to me like it's being addressed in this project (though if OpenAPI supported a jq option or similar, I think it could be). That said, I'm not completely up-to-date on the OpenAPI developments so anyone, please correct me and/or clarify if I'm wrong or off base.

handrews commented 6 years ago

@mewalig number 1 is more of a JSON Schema thing than an OpenAPI thing. As of OAS 3, parameter validation is done with OAS Schema Objects.

OAS 3.1 will have the alternativeSchema keyword, so if JSON Schema does not provide the sort of validation you want, it would be possible to find another system and register it as an available alternative.

Locale-specific strings in general may be best addressed by overlays (#1442, OAI/Overlay-Specification#36).

It's a bit hard to figure out where 2 and 3 are best solved. In part it depends on how you want to fill out the placeholders. OAS is a design-time system so runtime resolution seems a bit out of scope. If I'm understanding you correctly, anyway.

RexMagnus commented 5 years ago

There are many complex cases being discussed here, but my case is very straight forward and I think it's VERY common. Consider a user object, where you have both a userId and an email adress. Both are unique and can be used as the key for that object. You need to require either or - not both. Another example would be a vehicle object having both a unique VIN and a vehicleId.

It would be super nice to have support for something like this: required:

Could this more simple and more common case get a bump up in the prioritization perhaps?

Thanks!

kurt-o-sys commented 5 years ago

I somewhat related, but slightly different case, modelling this in openapi:

{
 "caseT": {... spec T...}
 "exts":
   {"caseU": {... spec U...},
    "caseX": {... spec X...}
  }
}

I have a number of (known) cases - this means, all possible keys are known (case T, case U, case X, case ..., this is limited). For each case, the spec is known as well (there's no problem with modelling the specs). The model here should be:

The model/schema would be something like:

    star:
      type: object
      required:
        - oneOf:
            - caseU
            - caseT
            - caseX
      properties:
        caseU:
          type: object
          schema: $ref '...'
        caseT:
          type: object
          schema: $ref '...'
        caseX:
          type: object
          schema: $ref '...'
        exts:
          type: object
          required:
            - anyOf:
              - caseU
              - caseT
              - caseX
          properties:
            caseU:
              type: object
              schema: $ref '...'
            caseT:
              type: object
              schema: $ref '...'
            caseX:
              type: object
              schema: $ref '...'

There's still some duplication here, but I don't see how to do that better. Anyway, using oneOf/anyOf inside required is not possible :p.

Going further: if possible at all any case except the one that's in the root (which I don't think is possible). Or, I want to model a star schema: one central case and any number of other cases around that central case, e.g. (in my case, the central table/case is not known, it can be any of all known tables/cases):

afbeelding

mewalig commented 5 years ago

Another potential approach would be to borrow Typescript syntax (which supports a broad range of types including mutually-exclusive properties) to allow the creation and referencing of user-defined types that can be substituted for built-in types ("string", "number", "object" etc). This has the advantage of being a proven model for defining data types and would allow you to use a syntax that is already documented, together with a huge and growing opensource code base of typescript tools. In fact this would clearly provide numerous ways that OpenAPI could functionally scale. I'm having a hard time thinking of any drawbacks but I'm sure others can/will fill that gap...

handrews commented 5 years ago

@RexMagnus your use case is just:

{
  "oneOf": [
    {
      "required": ["userId"],
      "not": {"required": ["email"]}
    },
    {
      "required": ["email"],
      "not": {"required": ["userId"]}
    }
  ]
}
handrews commented 5 years ago

@RexMagnus oh wait never mind, these are Parameter Objects :-(
I lost track of which repository I was commenting on, sorry. That only works inside of a Schema Object / JSON Schema.

yami12376 commented 5 years ago

It's still open - after all these years?

Somebi commented 5 years ago

bump

kryvel commented 5 years ago

Any updates on this ?

msudgh commented 5 years ago

@webron 4 years open proposal! any updates?

webron commented 5 years ago

With no indication of importance, as far as I know, this is currently not on the radar of anyone from the @OAI/tsc. We have a list of topics we're more focused on for 3.1 described in OAI/OpenAPI-Specification#1466

That said, we have a new proposals process any anyone is welcome to try making an official proposal so we can have a discussion on it. It's probably the best next step. There's no guarantee proposals would be accepted, but hopefully it can drive the discussion.

darrelmiller commented 5 years ago

Although there are some interesting suggestions in this thread, I don't see any clear proposal for addressing the raised concerns. As @webron mentioned this is not an issue that we see raised commonly by the OAI community and as there are more pressing issues, I don't expect to see the @OAI/tsc doing any work on this in the near future.

skretzig commented 5 years ago

I've used the if/then/else pattern with JSON schema and it works like a charm. In my case, I'm trying to support versioning and backward compatibility in GET messages. If I can have conditional sets of parameters based on the value of the version query parameter, my problems are solved. Just saying

yigitsa commented 5 years ago

TL;DR Wouldn't the simple case be resolved by allowing duplicate path mapping keys?

GET /resourceX/{somePathParam}

GET /resourceX/{somePathParam}

There are many complex cases being discussed here, but my case is very straight forward and I think it's VERY common.

I think @RexMagnus has a very valid point here, no need for complex cases. IMO if you need to write a DSL to define the interdependencies between query parameters, most likely you need to reconsider your design choices.

BUT, Wouldn't this simple case be resolved by allowing duplicate path mapping keys?

For instance, if you can define 2 so-called Path Item Objects with the same HTTP verb (Operation Object) for the same Paths Object, then you can easily define parallel such "either both or none" query parameter groupings.

And IMO it'd be much cleaner to understand from the client's POV - Otherwise the client needs to understand the logic of interdependencies which may become really complicated as everyone has pointed out...

What am I missing here?