Skip to content

Support for semantic path templating #576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
trilean opened this issue Feb 22, 2016 · 27 comments
Closed

Support for semantic path templating #576

trilean opened this issue Feb 22, 2016 · 27 comments

Comments

@trilean
Copy link

trilean commented Feb 22, 2016

Hi,

I recently ran into the following issue: I have two different types of URIs which support different requests (verbs) and return different values. I tried to write them down as:

paths:
  /{CollectionURI}:
    get:
      summary: List Collection Members
      ...
    post:
      summary: Create a Resource
      ...
    parameters:
      - $ref: "#/parameters/CollectionURI"
  /{MemberURI}:
    get:
      summary: Retrieve a Resource
      ...
    parameters:
      - $ref: "#/parameters/MemberURI"

...which does show up fine in Swagger-UI but is not officially supported. Swagger-Editor doesn't like it and shows an error.

I asked that question on SO: https://stackoverflow.com/questions/35478531/swagger-equivalent-path-already-exists-despite-different-parameters

Please add support for this kind of path templating in the next version.

Thanks!

@webron
Copy link
Member

webron commented Feb 22, 2016

Can you share more details on the actual parameters? As @fehguy mentioned on SO they are identical path-wise, and without the details on the parameters it's unclear how you differentiate them. It could be different type for the path parameter, but it could be that you depend on other parameters.

@trilean
Copy link
Author

trilean commented Feb 22, 2016

For example a POST to a ${CollectionURI} returns 201, a Location Header to the newly created ${MemberURI} and its body. There is no POST to a ${MemberURI}, only PUT and DELETE (which are not available on a ${CollectionURI}).

I don't have the code with me ATM, I'll post it later.

@webron
Copy link
Member

webron commented Feb 22, 2016

But both of them have a GET operation - how do you tell them apart?

@trilean
Copy link
Author

trilean commented Feb 22, 2016

Here is the code. I removed the descriptions to make it a bit shorter.

paths:
  /{CollectionURI}:
    get:
      summary: List Collection Member
      responses:
        200:
          description: OK
          schema:
            $ref: "#/definitions/AtomFeedDocument"
    post:
      summary: Create a Resource
      responses:
        201:
          description: Created
          schema:
            $ref: "#/definitions/MemberRepresentation"
          headers:
            Location:
              type: string
              format: URI
      parameters:
        - $ref: "#/parameters/MemberRepresentation"
    parameters:
      - $ref: "#/parameters/CollectionURI"
  /{MemberURI}:
    get:
      summary: Retrieve a Resource
      responses:
        200:
          description: OK
          schema:
            $ref: "#/definitions/MemberRepresentation"
    put:
      summary: Edit a Resource
      responses:
        200:
          description: OK
    delete:
      summary: Delete a Resource
      responses:
        200:
          description: OK
    parameters:
      - $ref: "#/parameters/MemberURI"

But both of them have a GET operation - how do you tell them apart?

I want to view a request to /path/to/id as something like edge --> edge --> node (or branch --> branch --> leaf). If the last part of the URI is a node (i.e. a Member in Atom's case) it's a MemberURI, else a CollectionURI.
The problem of course is that you can't know that from in advance.

From @fehguy 's answer on SO I figure this is not (easily?) doable?

@whitlockjc
Copy link
Member

Unfortunately, this doesn't explain how to differentiate the two "different" paths that are semantically the same. (They are semantically the same because they describe a path with a single path segment that itself is a variable.) There has to be something in the path definition that makes that definition unique and your two path definitions do not do that. If you run that through swagger-tools validate, it would tell you that this is invalid Swagger. Not only that but it seems like you're trying to have a variable for one path segment but what you really want (I'm guessing here...) is a wildcard because you're not defining one path segment that is a variable but potentially N path segments as a variable. I'm not sure Swagger supports that, in fact I had to work around this in swagger-tools using a vendor extension. (I'd love to be wrong on this one and learn something.)

So without something concrete in your path definition, it will be impossible for any smart validator to differentiate between the path patterns because the are semantically the same. (Feel free to npm install -g swagger-tools && swagger-tools validate YOUR_FILE to see the results.) Why not change it so your paths were /collections/{URI} and /members/{URI}?

@webron
Copy link
Member

webron commented Feb 22, 2016

So you have an API with a a URI that is identical for two different operations (all the parameter definitions are identical as well including header, path and query parameters), and based on the path parameter the client will get one of two results but the client has no real way of knowing that when calling the API?

@fehguy
Copy link
Contributor

fehguy commented Feb 22, 2016

Unfortunately this is supported in different server frameworks by routing the request based on regexes on the path parameters. I am not saying we should support it but it is somewhat common.

Declaring the regex in the path parameter in the path makes this more tolerable but you still have to deal with the order of the routing, etc

@whitlockjc
Copy link
Member

I know there is regex support in routers and such but the blanket "This API matches anything after the slash" just isn't very beneficial. And while you could assume that this is backed by some regex matching router, which could very well be the case since that is a common feature in routers, even then there is a chance that there is more of a pattern that could be put into the path definition than what is there now.

@webron
Copy link
Member

webron commented Feb 22, 2016

But @trilean never mentioned that the path parameter is differentiated by a regex. Who says a given 'name' can't be used for both a member and a collection? He could just be checking it in the BE, checking out one and falling back to the other if it doesn't exist.

@whitlockjc
Copy link
Member

That's why I said "while you could assume this is backed by some regex". ;) But yes, you're right that this could a the case where the only one differentiating this is on the backend.

@webron
Copy link
Member

webron commented Feb 22, 2016

But I was replying to @fehguy. Not everything is about you, @whitlockjc.

@whitlockjc
Copy link
Member

Blame the async responses @webron, not my theorized ego.

@theganyo
Copy link

Mommy and daddy are fighting again.

@whitlockjc
Copy link
Member

👍

@trilean
Copy link
Author

trilean commented Feb 22, 2016

Thank you for your answers! Sorry for not having been more specific on this. It seems we're talking about two somewhat seperate issues here:

  1. The return types (schemae) for a request to a MemberURI and CollectionURI are not the same. CollectionURIs always yield an Atom Feed Document while a Member may be an image, an XML document or anything in between. Which is why I want to differentiate the two. Usually it'll be an Atom or XHTML document (depending in the accept header).
  2. I have not implemented anything so far because I tried to do a proper specification first 😉 In my head it goes something like this: I want to store my documents in a tree. The (URI) path segments correspond to the branches in the tree. If the path doesn't point to a leaf (i.e. single document), the data store returns a list of all leafs below the branch specified in the URI, hence a collection. E.g. if you GET / you'll get all documents.

Actually I want to use a graph but it's easier to imagine with trees.

Also, there is no fixed number of path segments (depth of the tree). Can wildcards handle this?

Why not change it so your paths were /collections/{URI} and /members/{URI}?

To me that feels like a workaround. It's 2016 and I really really want my URIs to represent my data and not some implementation details / issues.

But yes, you're right that this could a the case where the only one differentiating this is on the backend.

Exactly.

@webron
Copy link
Member

webron commented Feb 22, 2016

In that case, this is pretty much a duplicate of #291.

@whitlockjc
Copy link
Member

What about 2016 means URIs can't have meaningful, differentiating details in them? I know when I design my APIs, it's very clear to tell what resource is being interacted with using a simple name, like /pets, /users or what not. Doing this does not force any implementation details on anyone, it's your choice as to whether you make your URIs hierarchical or not. That's the only implementation detail I can see based on the context we're discussing.

@trilean
Copy link
Author

trilean commented Feb 22, 2016

/pets and /users has meaning to you and me, Google, and every visitor to your site. members and collections don't. And they're not my invention but taken from RFC5023 -- The Atom Publishing Protocol -- which I've tried to translate to Swagger. My view is:

  • A proper translation of that RFC to Swagger must not make assumptions about "distinguishing path segments".
  • An implementation may introduce those -- and I prefer not to.

At the end of the day it shouldn't matter which 'style' you prefer. IMO the fact that Swagger doesn't allow me to specify what I want to is a limitation. I would welcome it if you considered this in the next version.

Nevertheless thank you for your time and answers!

@trilean trilean closed this as completed Feb 22, 2016
@webron
Copy link
Member

webron commented Feb 22, 2016

Normally, there's no reason to close a ticket that's a feature request. However, since it does seem like a duplicate of #291 we can keep it closed. If you feel #291 doesn't cover it, then feel free to either reopen this ticket or add your own comments to the other issue.

@trilean
Copy link
Author

trilean commented Feb 22, 2016

Thanks, I'm kinda new to this. Reopened for better visibility when looking through those requests 😉

@mpnally
Copy link

mpnally commented Feb 23, 2016

I believe I understand what @trilean is trying to do, because I've tried to do something similar myself. I was able to express what I wanted using some 'x-' extensions, but of course that is not satisfactory, because no-one but me understands them. Here is an explanation of the problem and what I did to get around it.

OpenAPI lets you say something like:

paths:
  /widget/{widget-id}:
    parameters:
    - name: widget-id
      ...
    get:
      ...
    put:
      ...

If you like doing hypermedia, you really want to split this information into two parts:

  1. There are widgets at URLs of the form /widget/{widget-id}
  2. Widgets support the following get, put, delete methods

The reason you want this separation is that there may be widgets at other URLs, not just at URLs of the form /widget/{widget-id}. In fact, you really don't want to declare the format of your hypermedia links at all - you want them to be opaque - although you may also support some URLs whose formats you do publish. If you are writing your OpenAPI specs in YAML, you can express this separation by writing this:

x-interfaces:
  Widget: &Widget
    get:
      ...
    put:
      ...
paths:
  /widget/{widget-id}:
    parameters:
    - name: widget-id
      ...
    <<: *Widget  

This use of the YAML merge operator allows you to separate the interface implemented by all widgets from a path that describes specific URLs for some of them. Correctly-written OpenAPI tools will still correctly interpret these paths. I believe that @trilean may have been trying to express the same idea as my 'interface' concept when he wrote:

paths:
  /{CollectionURI}:
    get:

Once you have separated out an interface in this way, you can reference it in the definition of a hyperlink. The simplest form of hyperlink might look like this:

definitions:
  Doohickey:
    properties:
      widget:
        description: hyperlink to a Widget
        format: uri
        type: string
        x-interface:
          $ref: '#/x-interfaces/Widget'

If you are one of those folks who likes to represent your hyperlinks XML-style, nested under a 'links' property, it's a bit more complex, but not too much.

You might challenge the desirability of the x-interface extension. It is fairly common to have APIs where more than one type of resource can be referenced by a hyperlink. In that case you are going to have to retrieve the resource and examine its type to see what you can do with it, in which case the x-interface value isn't really helping. And since it doesn't help in all cases, maybe its better not to have it at all.

In summary, by inventing 'x-interfaces' and 'x-interface' I was able to express what I wanted in a way that allows OpenAPI tools to still understand my paths, while expressing the extra information I needed for hypermedia. The limitation is that my x- extensions are currently known only to me. Another limitation is that the YAML merge operator doesn't understand URLs, so you can't reference interfaces in other documents.

Apologies to @trilean if this is not in fact what he meant.

@mpnally
Copy link

mpnally commented Feb 23, 2016

I might add that I had to invent a third x- extension that is closer to the example shown by @trilean. Extending the example above, consider these OpenAPI paths:

paths:
  /doohickies/{doohickey-id}:
    ...
  /doohickies/{doohickey-id}/widget:
    ...

What this tells you is that, if you have a Doohickey URL of the form /doohickies/{doohickey-id}, you can stick /widget on the end of it to form the URL of a related widget. Suppose that this rule is more general: given the URL of a Doohickey, regardless of its format, I can always stick /widget on the end of it to form the URL of a widget. This is a common thing to want to express in a hypermedia system where the formats of hyperlink URLs are unknown/undeclared. This may be closer to what @trilean was trying to express. The x- extension I used for this looks like:

x-templates:
  {Doohickey}/widget:
    $ref: '#/x-interfaces/Widget'

There is no / in front of {Doohickey} because there is no requirement that a resource referenced by a hypermedia URL must reside on the same domain as the referee.

@trilean
Copy link
Author

trilean commented Feb 23, 2016

@mpnally, thank you for your comment. I think this is the closest I can come with Swagger to what I want to write down, though I'm not quite sure I understood everything you meant. Let me try and implement your suggestions with my spec -- I'll post back here if there are any issues 😉

@trilean trilean reopened this Feb 23, 2016
@fehguy
Copy link
Contributor

fehguy commented Mar 1, 2016

Parent issue #574

@grosch
Copy link

grosch commented Jan 26, 2017

I definitely would like to see this work. My example is something like this that I run into;

/team/{id}/people/{modified}:
get:
/team/{id}/people/{person_id}:
delete:

Those are two very distinct paths, differentiated by the type of call (GET vs. DELETE) but that last parameter name is a distinctly different argument type.

@fehguy
Copy link
Contributor

fehguy commented Feb 1, 2017

See #804

@JhaDmoore
Copy link

JhaDmoore commented Nov 10, 2022

Still seems to be a problem in 2022... Servers can route different data types very nicely,
hence:
GET /resource/{byUUID} and GET/resource/(byString} and GET /resource/{byInt} can all be handled quite easily by most servers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants