From e6fce42d8797fddfe3c3b899bc05ebd4b5a19c80 Mon Sep 17 00:00:00 2001 From: AJ Foster <2789166+aj-foster@users.noreply.github.com> Date: Thu, 4 Jan 2024 00:35:21 -0500 Subject: [PATCH] Support schemas with allOf intersection types --- lib/open_api/processor/type.ex | 24 +- test/fixture/all-of.yaml | 615 +++++++++++++++++++++++++++++++++ 2 files changed, 638 insertions(+), 1 deletion(-) create mode 100644 test/fixture/all-of.yaml diff --git a/lib/open_api/processor/type.ex b/lib/open_api/processor/type.ex index 86ab2e7a..36caf6f6 100644 --- a/lib/open_api/processor/type.ex +++ b/lib/open_api/processor/type.ex @@ -136,6 +136,11 @@ defmodule OpenAPI.Processor.Type do def from_schema(state, %Schema{one_of: types}) when is_list(types), do: create_union(state, types) + # Intersections + # + def from_schema(state, %Schema{all_of: types} = schema_spec) when is_list(types), + do: create_intersection(state, schema_spec, types) + # Objects # def from_schema(state, %Schema{properties: properties} = schema_spec) when is_map(properties) do @@ -174,7 +179,24 @@ defmodule OpenAPI.Processor.Type do defp string_type(%Schema{format: "uuid"}), do: {:string, :uuid} defp string_type(_schema), do: {:string, :generic} - @spec create_union(State.t(), [Schema.t()]) :: t + @spec create_intersection(State.t(), Schema.t(), [Schema.t()]) :: {State.t(), t} + defp create_intersection(state, schema_spec, types) do + properties = + Enum.reduce(types, %{}, fn type, properties -> + case type do + {:ref, full_path} -> + schema_spec = Map.fetch!(state.schema_specs_by_path, full_path) + Map.merge(properties, schema_spec.properties) + + %Schema{properties: schema_properties} when is_map(properties) -> + Map.merge(properties, schema_properties) + end + end) + + State.put_schema_spec(state, %{schema_spec | properties: properties}) + end + + @spec create_union(State.t(), [Schema.t()]) :: {State.t(), t} defp create_union(state, types) do {state, types} = Enum.reduce(types, {state, []}, fn type, {state, types} -> diff --git a/test/fixture/all-of.yaml b/test/fixture/all-of.yaml new file mode 100644 index 00000000..e0cddfc5 --- /dev/null +++ b/test/fixture/all-of.yaml @@ -0,0 +1,615 @@ +openapi: 3.0.3 +info: + description: | + You can use Spotify's Web API to discover music and podcasts, manage your Spotify library, control audio playback, and much more. Browse our available Web API endpoints using the sidebar at left, or via the navigation bar on top of this page on smaller screens. + + In order to make successful Web API requests your app will need a valid access token. One can be obtained through OAuth 2.0. + + The base URI for all Web API requests is `https://api.spotify.com/v1`. + + Need help? See our Web API guides for more information, or visit the Spotify for Developers community forum to ask questions and connect with other developers. + version: 2023.12.2 + title: Spotify Web API with fixes and improvements from sonallux + termsOfService: https://developer.spotify.com/terms/ + contact: + name: sonallux + url: https://github.com/sonallux/spotify-web-api +servers: + - url: https://api.spotify.com/v1 +tags: + - name: Albums +paths: + /albums/{id}: + get: + operationId: get-an-album + tags: + - Albums + summary: | + Get Album + description: | + Get Spotify catalog information for a single album. + parameters: + - $ref: "#/components/parameters/PathAlbumId" + - $ref: "#/components/parameters/QueryMarket" + responses: + "200": + $ref: "#/components/responses/OneAlbum" + "401": + $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" + "429": + $ref: "#/components/responses/TooManyRequests" + security: + - oauth_2_0: [] +components: + securitySchemes: + oauth_2_0: + type: oauth2 + description: Spotify supports OAuth 2.0 for authenticating all API requests. + flows: + authorizationCode: + authorizationUrl: https://accounts.spotify.com/authorize + tokenUrl: https://accounts.spotify.com/api/token + scopes: + app-remote-control: | + Communicate with the Spotify app on your device. + playlist-read-private: | + Access your private playlists. + playlist-read-collaborative: | + Access your collaborative playlists. + playlist-modify-public: | + Manage your public playlists. + playlist-modify-private: | + Manage your private playlists. + user-library-read: | + Access your saved content. + user-library-modify: | + Manage your saved content. + user-read-private: | + Access your subscription details. + user-read-email: | + Get your real email address. + user-follow-read: | + Access your followers and who you are following. + user-follow-modify: | + Manage your saved content. + user-top-read: | + Read your top artists and content. + user-read-playback-position: | + Read your position in content you have played. + user-read-playback-state: | + Read your currently playing content and Spotify Connect devices information. + user-read-recently-played: | + Access your recently played items. + user-read-currently-playing: | + Read your currently playing content. + user-modify-playback-state: | + Control playback on your Spotify clients and Spotify Connect devices. + ugc-image-upload: | + Upload images to Spotify on your behalf. + streaming: | + Play content and control playback on your other devices. + responses: + OneAlbum: + description: An album + content: + application/json: + schema: + $ref: "#/components/schemas/AlbumObject" + Unauthorized: + description: | + Bad or expired token. This can happen if the user revoked a token or + the access token has expired. You should re-authenticate the user. + content: + application/json: + schema: + type: object + required: + - error + properties: + error: + $ref: "#/components/schemas/ErrorObject" + Forbidden: + description: | + Bad OAuth request (wrong consumer key, bad nonce, expired + timestamp...). Unfortunately, re-authenticating the user won't help here. + content: + application/json: + schema: + type: object + required: + - error + properties: + error: + $ref: "#/components/schemas/ErrorObject" + TooManyRequests: + description: | + The app has exceeded its rate limits. + content: + application/json: + schema: + type: object + required: + - error + properties: + error: + $ref: "#/components/schemas/ErrorObject" + schemas: + AlbumBase: + type: object + required: + - album_type + - total_tracks + - available_markets + - external_urls + - href + - id + - images + - name + - release_date + - release_date_precision + - type + - uri + properties: + album_type: + type: string + description: | + The type of the album. + enum: + - album + - single + - compilation + example: compilation + total_tracks: + type: integer + description: The number of tracks in the album. + example: 9 + available_markets: + type: array + items: + type: string + example: + - CA + - BR + - IT + description: | + The markets in which the album is available: [ISO 3166-1 alpha-2 country codes](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). _**NOTE**: an album is considered available in a market when at least 1 of its tracks is available in that market._ + external_urls: + allOf: + - $ref: "#/components/schemas/ExternalUrlObject" + description: | + Known external URLs for this album. + href: + type: string + description: | + A link to the Web API endpoint providing full details of the album. + id: + type: string + description: | + The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the album. + example: 2up3OPMp9Tb4dAKM2erWXQ + images: + type: array + items: + $ref: "#/components/schemas/ImageObject" + description: | + The cover art for the album in various sizes, widest first. + name: + type: string + description: | + The name of the album. In case of an album takedown, the value may be an empty string. + release_date: + type: string + example: 1981-12 + description: | + The date the album was first released. + release_date_precision: + type: string + enum: + - year + - month + - day + example: year + description: | + The precision with which `release_date` value is known. + restrictions: + allOf: + - $ref: "#/components/schemas/AlbumRestrictionObject" + description: | + Included in the response when a content restriction is applied. + type: + type: string + enum: + - album + description: | + The object type. + uri: + type: string + example: spotify:album:2up3OPMp9Tb4dAKM2erWXQ + description: | + The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the album. + AlbumObject: + type: object + x-spotify-docs-type: AlbumObject + required: + - artists + - tracks + - copyrights + - external_ids + - genres + - label + - popularity + allOf: + - $ref: "#/components/schemas/AlbumBase" + - type: object + properties: + artists: + type: array + items: + $ref: "#/components/schemas/SimplifiedArtistObject" + description: | + The artists of the album. Each artist object includes a link in `href` to more detailed information about the artist. + tracks: + allOf: + - $ref: "#/components/schemas/PagingSimplifiedTrackObject" + description: | + The tracks of the album. + copyrights: + type: array + items: + $ref: "#/components/schemas/CopyrightObject" + description: | + The copyright statements of the album. + external_ids: + allOf: + - $ref: "#/components/schemas/ExternalIdObject" + description: | + Known external IDs for the album. + genres: + type: array + items: + type: string + example: + - Egg punk + - Noise rock + description: | + A list of the genres the album is associated with. If not yet classified, the array is empty. + label: + type: string + description: | + The label associated with the album. + popularity: + type: integer + description: | + The popularity of the album. The value will be between 0 and 100, with 100 being the most popular. + CopyrightObject: + type: object + x-spotify-docs-type: CopyrightObject + properties: + text: + type: string + description: | + The copyright text for this content. + type: + type: string + description: | + The type of copyright: `C` = the copyright, `P` = the sound recording (performance) copyright. + SimplifiedArtistObject: + type: object + x-spotify-docs-type: SimplifiedArtistObject + properties: + external_urls: + allOf: + - $ref: "#/components/schemas/ExternalUrlObject" + description: | + Known external URLs for this artist. + href: + type: string + description: | + A link to the Web API endpoint providing full details of the artist. + id: + type: string + description: | + The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the artist. + name: + type: string + description: | + The name of the artist. + type: + type: string + enum: + - artist + description: | + The object type. + uri: + type: string + description: | + The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the artist. + ExternalUrlObject: + type: object + x-spotify-docs-type: ExternalUrlObject + properties: + spotify: + type: string + description: | + The [Spotify URL](/documentation/web-api/concepts/spotify-uris-ids) for the object. + ExternalIdObject: + type: object + x-spotify-docs-type: ExternalIdObject + properties: + isrc: + type: string + description: | + [International Standard Recording Code](http://en.wikipedia.org/wiki/International_Standard_Recording_Code) + ean: + type: string + description: | + [International Article Number](http://en.wikipedia.org/wiki/International_Article_Number_%28EAN%29) + upc: + type: string + description: | + [Universal Product Code](http://en.wikipedia.org/wiki/Universal_Product_Code) + ImageObject: + type: object + x-spotify-docs-type: ImageObject + required: + - url + - height + - width + properties: + url: + type: string + example: | + https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228 + description: | + The source URL of the image. + height: + type: integer + example: 300 + nullable: true + description: | + The image height in pixels. + width: + type: integer + example: 300 + nullable: true + description: | + The image width in pixels. + PagingSimplifiedTrackObject: + type: object + x-spotify-docs-type: PagingTrackObject + allOf: + - $ref: "#/components/schemas/PagingObject" + - type: object + properties: + items: + type: array + items: + $ref: "#/components/schemas/SimplifiedTrackObject" + PagingObject: + type: object + x-spotify-docs-type: PagingObject + required: + - href + - items + - limit + - next + - offset + - previous + - total + properties: + href: + type: string + example: | + https://api.spotify.com/v1/me/shows?offset=0&limit=20 + description: | + A link to the Web API endpoint returning the full result of the request + limit: + type: integer + example: 20 + description: | + The maximum number of items in the response (as set in the query or by default). + next: + type: string + example: https://api.spotify.com/v1/me/shows?offset=1&limit=1 + nullable: true + description: | + URL to the next page of items. ( `null` if none) + offset: + type: integer + example: 0 + description: | + The offset of the items returned (as set in the query or by default) + previous: + type: string + example: https://api.spotify.com/v1/me/shows?offset=1&limit=1 + nullable: true + description: | + URL to the previous page of items. ( `null` if none) + total: + type: integer + example: 4 + description: | + The total number of items available to return. + AlbumRestrictionObject: + type: object + x-spotify-docs-type: AlbumRestrictionObject + properties: + reason: + type: string + enum: + - market + - product + - explicit + description: | + The reason for the restriction. Albums may be restricted if the content is not available in a given market, to the user's subscription type, or when the user's account is set to not play explicit content. + Additional reasons may be added in the future. + SimplifiedTrackObject: + type: object + x-spotify-docs-type: SimplifiedTrackObject + properties: + artists: + type: array + items: + $ref: "#/components/schemas/SimplifiedArtistObject" + description: + The artists who performed the track. Each artist object includes + a link in `href` to more detailed information about the artist. + available_markets: + type: array + items: + type: string + description: | + A list of the countries in which the track can be played, identified by their [ISO 3166-1 alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) code. + disc_number: + type: integer + description: + The disc number (usually `1` unless the album consists of more + than one disc). + duration_ms: + type: integer + description: The track length in milliseconds. + explicit: + type: boolean + description: + Whether or not the track has explicit lyrics ( `true` = yes + it does; `false` = no it does not OR unknown). + external_urls: + allOf: + - $ref: "#/components/schemas/ExternalUrlObject" + description: | + External URLs for this track. + href: + type: string + description: + A link to the Web API endpoint providing full details of the + track. + id: + type: string + description: | + The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the track. + is_playable: + type: boolean + description: | + Part of the response when [Track Relinking](/documentation/web-api/concepts/track-relinking/) is applied. If `true`, the track is playable in the given market. Otherwise `false`. + linked_from: + allOf: + - $ref: "#/components/schemas/LinkedTrackObject" + description: + "Part of the response when [Track Relinking](/documentation/web-api/concepts/track-relinking/)\ + \ is applied and is only part of the response if the track linking, in\ + \ fact, exists. The requested track has been replaced with a different\ + \ track. The track in the `linked_from` object contains information about\ + \ the originally requested track." + restrictions: + allOf: + - $ref: "#/components/schemas/TrackRestrictionObject" + description: | + Included in the response when a content restriction is applied. + name: + type: string + description: The name of the track. + preview_url: + type: string + nullable: true + description: | + A URL to a 30 second preview (MP3 format) of the track. + track_number: + type: integer + description: | + The number of the track. If an album has several discs, the track number is the number on the specified disc. + type: + type: string + description: | + The object type: "track". + uri: + type: string + description: | + The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the track. + is_local: + type: boolean + description: | + Whether or not the track is from a local file. + ErrorObject: + type: object + x-spotify-docs-type: ErrorObject + required: + - status + - message + properties: + status: + type: integer + minimum: 400 + maximum: 599 + description: | + The HTTP status code (also returned in the response header; see [Response Status Codes](/documentation/web-api/concepts/api-calls#response-status-codes) for more information). + message: + type: string + description: | + A short description of the cause of the error. + LinkedTrackObject: + type: object + x-spotify-docs-type: LinkedTrackObject + properties: + external_urls: + allOf: + - $ref: "#/components/schemas/ExternalUrlObject" + description: | + Known external URLs for this track. + href: + type: string + description: | + A link to the Web API endpoint providing full details of the track. + id: + type: string + description: | + The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) for the track. + type: + type: string + description: | + The object type: "track". + uri: + type: string + description: | + The [Spotify URI](/documentation/web-api/concepts/spotify-uris-ids) for the track. + TrackRestrictionObject: + type: object + x-spotify-docs-type: TrackRestrictionObject + properties: + reason: + type: string + description: | + The reason for the restriction. Supported values: + - `market` - The content item is not available in the given market. + - `product` - The content item is not available for the user's subscription type. + - `explicit` - The content item is explicit and the user's account is set to not play explicit content. + + Additional reasons may be added in the future. + **Note**: If you use this field, make sure that your application safely handles unknown values. + parameters: + PathAlbumId: + in: path + name: id + required: true + schema: + title: Spotify Album ID + description: | + The [Spotify ID](/documentation/web-api/concepts/spotify-uris-ids) of the album. + example: 4aawyAB9vmqN3uQ7FjRGTy + type: string + QueryMarket: + name: market + required: false + in: query + schema: + title: Market + description: | + An [ISO 3166-1 alpha-2 country code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). + If a country code is specified, only content that is available in that market will be returned.
+ If a valid user access token is specified in the request header, the country associated with + the user account will take priority over this parameter.
+ _**Note**: If neither market or user country are provided, the content is considered unavailable for the client._
+ Users can view the country that is associated with their account in the [account settings](https://www.spotify.com/se/account/overview/). + example: ES + type: string