oasis-tcs / odata-openapi

OASIS OData TC: Tools for producing API descriptions for OData services that adhere to the OpenAPI Specification
https://oasis-tcs.github.io/odata-openapi/
Other
196 stars 80 forks source link

Openapi specification only documents DELETE and PATCH for single entity URLs #228

Closed fisehara closed 1 year ago

fisehara commented 1 year ago

Hello and Happy New Year!

I'd like to understand if there is a way to document on the resource path /<resource> the DELETE and PATCH actions. I understand that from the http://docs.oasis-open.org/odata/odata-openapi/v1.0/odata-openapi-v1.0.html there is no mapping specified for OData to Openapi for DELETE or PATCH a collection on a resource, but from OData itself it's an allowed action. From the OData core specification: http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity I read a MAY restrict to the single entity URL

Services MAY restrict updates only to requests addressing the edit URL of the entity. Services MAY restrict deletes only to requests addressing the of the entity.

From my understanding this would allow requests to a resource as follows eg OData URL convention examples

DELETE http://host/service/Products?$filter=Name in ('Milk', 'Cheese') PATCH http://host/service/Products?$filter=Name in ('Milk', 'Cheese') { payload }

Thanks in advance and best regards

ralfhandl commented 1 year ago

Hi Harald,

Happy New Year!

PATCH and DELETE requests to filtered collections of entities aren't yet supported by the generator because I don't know of any OData services that support this advanced feature.

It could be added for services that advertise this feature via the FilterSegmentSupported property of UpdateRestrictions and DeleteRestrictions annotations.

I'll keep this issue open as a reminder.

If you know of services providing this feature, please tell me.

Thanks in advance Ralf

fisehara commented 1 year ago

Hi Ralf,

thanks for the fast and precise answer! We are developing and using @balena/pinejs to auto-generate our OData API at balena.io. PineJs implements DELETE and Patch on filtered collections generically for every collection as it generates the end-points for each resource in the same way.

Thus, every instance of our open source published IoT fleet management tool https://github.com/balena-io/open-balena and our balenaCloud API ( https://api.balena-cloud.com/ ) are supporting DELETE and PATCH on filtered collections.

The overall idea to use odata-openapi translator is to have it build into PineJS so that documentation renders, SDK generators and other tools can either use the OData CSDL or openapi spec to generate their outputs. In particular the next goal are automatic updated openapi specs for documenetation, provided by PineJS directly.

I'll keep it on a watch and in the meantime I'll figure out how to add it to the openapi spec, I'm thinking of copy/pasta the block from the single entity documentation.

Thanks a lot again, best regards and have a good start into 2023

Harald

ralfhandl commented 1 year ago

Hi Harald,

Thanks for the info, and cool that you are building APIs with this feature.

I'll put support for FilterSegmentSupported into the backlog.

ralfhandl commented 1 year ago

Hi Harald,

Could you please check whether https://petstore.swagger.io/?url=https://raw.githubusercontent.com/oasis-tcs/odata-openapi/a91398b3bbb24fb49054af7e275e6a673981a60c/examples/annotations.openapi3.json#/ matches your expectation?

The example shows /$filter(...) segments on a root entity set and on a collection-valued navigation property, each with DELETE and PATCH.

Thanks in advance

fisehara commented 1 year ago

Hi Ralf,

big thanks for the responsiveness and sending over an example to review!

The provided openapi spec looks exactly as the OData V4 core spec, so I think it's as expected. We internally discussed it and unfortunately, our filtered collection actions for Delete and Patch follow the old V3 implementation:

DELETE /AllSet?$filter = {filter_expression}
PATCH /AllSet?$filter = {filter_expression}

How we will approach this now is:

One completely just curious question during our internal discussion was, why the filteredCollection PATCH and DELETE are ended with a $each. This feels redundant and from our understanding doesn't represent the otherwise full and unambiguous expressiveness of OData.

Thanks for your friendly and open support!

Harald

ralfhandl commented 1 year ago

Hi Harald,

Assume we have a bound function foo with two overloads, one bound to a single thingy, and one bound to a collection of thingies.

This leads to the following request patterns

Request Meaning
GET /thingies/foo() call the collection-bound overload on all thingies
GET /thingies/42/foo() call the single-bound overload on the thingy with key 42
GET /thingies/$filter(color eq 'red')/foo() call the collection-bound overload on all red thingies
GET /thingies/$filter(color eq 'red')/$each/foo() call the single-bound overload on each single red thingy

The request with the URL ending in /$each is kind of an iterator over the filtered collection and allows us to decide which overload to call.

We have the same ambiguity with PATCH because we allow update requests to collections now, sending a delta payload as the request body.

Request Meaning
PATCH /thingies update the collection of all thingies with the provided delta request body
PATCH /thingies/$each update each thingy with the provided single-update request body

And we use the same pattern for DELETE requests although we haven't yet specified a meaning for DELETE /thingies.

The function call example also shows why we added the filter in the path. If the collection-bound overload returns a collection, there are two collections to filter: the input and the output of the function.

Having the filter query option always filter the output of an operation simplifies things.

Hope this explains how we arrived at the current URL syntax.

Ralf