OpenAPI-Specification
OpenAPI-Specification copied to clipboard
Ability to define generic wrapper around the output
I am working on using Swagger to define my API's that are complying with the JSON-API spec.
It would be helpful if there was a way to wrapper the defined objects (and the parameters) with the JSON-API specific formatting. Ideally allowing the developers of the documentation to focus just on the contents of their custom content, and not have to duplicate the wrapper code.
Looking at JSON-API you are talking about having the top level root element, normally "data", and then the generic "type, id, attributes" elements that sit above each custom object.
Is that something that could be considered to be added to the spec?
Sure, thanks for bringing that up. Somewhat reminds me of a SOAP envelope (in concept, not meaning).
To help us better assess this, can you provide more details on what the structure would be like with JSON-API? It'd help rather than to dive into the whole spec and digging up the relevant details.
Also, if you're familiar with other API specifications that require something similar, it would help to have references to those as well.
The examples on http://jsonapi.org are pretty self-descriptive of this issue (and apply to HAL and potentially other message formats): http://jsonapi.org/
I'm very interested in this area as well. I did a talk on JSON API last week, if you skip to slide 13 you might be able to get a quicker overview of the key features of JSON API than reading the whole spec (I hope!). However, I'm not sure if this idea can be implemented well without studying the spec in detail.
The other thing that would need to be considered is having a way to specify in the YAML file whether or not JSON API features such as pagination are supported by the server.
It would be really cool if we could specify a 'jsonapi' object directly under the location which could be automatically expanded by some sort of JSON API plugin to Swagger/OpenAPI. e.g:
paths:
/blog/posts:
jsonapi:
summary: "List of blog posts"
type: 'collection'
features:
pagination: true
data:
$ref: '#/definitions/BlogPost'
errors:
- $ref: '#/definitions/BlogPostError'
included:
- $ref: '#/definitions/BlogAuthor'
- $ref: '#/definitions/BlogComment'
This type of thing would abstract the YAML file from details such as headers and query parameters, which I think is great as it would allow API designers to focus on business logic and not the API implementation details - which is a great strength of JSON API.
Just brainstorming here:
JSON Schema for json-api is here: http://jsonapi.org/schema
We could define an allOf
to inherit from that model:
allOf:
- $ref: 'Pet.yaml'
- $ref: 'http://jsonapi.org/schema'
However, if Pet.yaml
redefined anything in the json-api schema, that would violate our REUSE guidelines, right? I'd imagine this is a big problem to resolve which reference's fields win when merging (as it's not parent, but an array of definitions.
Whether you reference definitions locally or remote, you can never override or change their definitions from the referring location. The definitions can only be used as-is.
I guess I'm just making sure we disquality allOf
as a solution, if I'm thinking about this right.
Why would it violate the REUSE guidelines? JSON Schema allows you to define conflicting schema under allOf
. It's useless, but it doesn't violate it. However, if we're looking to make things easier on users, we may want to allow for an easier construct (allOf is fairly horrible, imho). Since wrappers have been requested for other uses as well (pagination, for example), it doesn't have to be specific.
@webron the point is that if Pet.json
and http://jsonapi.org/schema
both define items
, how are those fields merged? Clearly the intent is that Pet.json/items
overrides http://jsonapi.org/schema/items
, but there's really no way to determine that programatically.
Suffice it to say I still don't really understand what that clause in REUSE means.
...and yes, allOf
is terrible. We should all be commenting on the v5 proposals en masse about oneOf
, allOf
, anyOf
: https://github.com/json-schema/json-schema/wiki/v5-Proposals
allOf
is does not merge objects. allOf
means that a given value is validated against all-of the listed schema, one by one. So yeah, they can be conflicting and not make sense. Now, if items
is defined in both, in such a way that you can write a value that validates against both, it's fine, but JSON Schema does not treat it as a merged schema.
Parent issues #579, #586
I'm having a discussion with Ron related to this in https://github.com/swagger-api/swagger-ui/issues/3073.
I'm not sure if this is wrapper is needed. Swagger is agnostic about the data exchange format, so you shouldn't have to specify anything other than consumes and produces. Other tools can use consumes and produces to determine what format to use to exchange data.
As an example, if we have to encode information about the data exchange format into the spec, then we are limited to only using that format for our paths. Instead, if we just use the consumes and produces arrays we can have multiple formats from the same path. This is useful when your app can use the same endpoint for multiple clients and return different formats based on the client. ( Spring can do this. )
We are using JSON-API with Spring and Ember. We generate controllers and models from the swagger specification. The Spring and Ember frameworks are then in charge of converting the models into JSON-API for sending over the wire.
By keeping the data exchange format out of the swagger spec you keep it flexible and agnostic.
Something is needed. It's horrible trying to specify anything using inheritance because of the brokenness of allOf
. I hope that can be fixed in JSON-Schema, but in the meantime, a useful method here would be appreciated.
It's already 2022. How's it going?
It's already 2022. How's it going?
it doesnt seem very difficult to do...
Back again. Do we have a solution for this?
There is a way to do this with OpenAPI 3.1 because it supports dynamic references from JSON Schema 2020-12.
Imagine we have a pretty standard CRUD API. Each resource also has a list operation, but we want to include paging information with the list responses. That means we need a wrapper over the array and we want that wrapper to be the same for every list in our system.
Without dynamic references, we'd have to do something like this for each resource, duplicating the list wrapper every time. If at some point we want to change this wrapper, we would have to change it for every resource. Some of this duplication can be addressed with normal schema composition (allOf
), but not all.
{
"$id": "https://json-schema.hyperjump.io/schema/foo-list"
"type": "object",
"properties": {
"list": {
"type": "array",
"items": { "$ref": "/schema/foo" }
},
"nextPage": { "type": "integer" },
"previousPage": { "type": "integer" },
"perPage": { "type": "integer" },
"page": { "type": "integer" }
}
}
However, with dynamic references, we can make this wrapper generic.
{
"$id": "https://json-schema.hyperjump.io/schema/list"
"type": "object",
"properties": {
"list": {
"type": "array",
"items": { "$dynamicRef": "#list" }
},
"nextPage": { "type": "integer" },
"previousPage": { "type": "integer" },
"perPage": { "type": "integer" },
"page": { "type": "integer" }
},
"$defs": {
"default-list": { "$dynamicAnchor": "list" }
}
}
In this schema, "$dynamicRef": "#list
, becomes a sort of placeholder that can be filled in by another schema that references this one.
{
"$id": "https://json-schema.hyperjump.io/schema/foo-list"
"$ref": "/schema/list",
"$defs": {
"foo": {
"$dynamicAnchor": "list",
"$ref": "/schema/foo"
}
}
}
Here, the "list" dynamic anchor is set to point to the schema for the "foo" resource. There is zero duplication of the wrapper. Anything about the wrapper can change and it only needs to change in one place.
There are a few downsides to this approach. Mainly, it's a lot of boilerplate and can be difficult to understand. Also, dynamic references aren't yet supported in a lot places including ajv and vscode.
The previous comment shows how to do this in 3.1. Dynamic references are also now supported in more tools. See also Using Dynamic References to Support Generic Types.