Skip to content

Commit

Permalink
Convert swagger.json to OAS 3.0 before generating SDK types (#8274)
Browse files Browse the repository at this point in the history
  • Loading branch information
rickyrombo committed May 2, 2024
1 parent 09789c1 commit 621c3ee
Show file tree
Hide file tree
Showing 25 changed files with 900 additions and 462 deletions.
49 changes: 49 additions & 0 deletions packages/discovery-provider/src/api/v1/models/access_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from flask_restx import fields

from .common import ns
from .extensions.models import OneOfModel, WildcardModel

tip_gate = ns.model("tip_gate", {"tip_user_id": fields.Integer(required=True)})
follow_gate = ns.model("follow_gate", {"follow_user_id": fields.Integer(required=True)})
nft_collection = ns.model(
"nft_collection",
{
"chain": fields.String(enum=["eth", "sol"], required=True),
"address": fields.String(required=True),
"name": fields.String(required=True),
"imageUrl": fields.String(),
"externalLink": fields.String(),
},
)
nft_gate = ns.model(
"nft_gate", {"nft_collection": fields.Nested(nft_collection, required=True)}
)

wild_card_split = WildcardModel(
"wild_card_split", {"*": fields.Wildcard(fields.Integer)}
)
ns.add_model("wild_card_split", wild_card_split)

usdc_gate = ns.model(
"usdc_gate",
{
"splits": fields.Nested(wild_card_split, required=True),
"price": fields.Integer(required=True),
},
)
purchase_gate = ns.model(
"purchase_gate", {"usdc_purchase": fields.Nested(usdc_gate, required=True)}
)

access_gate = ns.add_model(
"access_gate",
OneOfModel(
"access_gate",
[
fields.Nested(tip_gate),
fields.Nested(follow_gate),
fields.Nested(purchase_gate),
fields.Nested(nft_gate),
],
),
)
Empty file.
89 changes: 89 additions & 0 deletions packages/discovery-provider/src/api/v1/models/extensions/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from flask_restx import fields, marshal

from .models import OneOfModel


# region: Copied from flask_restx
def is_indexable_but_not_string(obj):
return not hasattr(obj, "strip") and hasattr(obj, "__iter__")


def is_integer_indexable(obj):
return isinstance(obj, list) or isinstance(obj, tuple)


def get_value(key, obj, default=None):
"""Helper for pulling a keyed value off various types of objects"""
if isinstance(key, int):
return _get_value_for_key(key, obj, default)
elif callable(key):
return key(obj)
else:
return _get_value_for_keys(key.split("."), obj, default)


def _get_value_for_keys(keys, obj, default):
if len(keys) == 1:
return _get_value_for_key(keys[0], obj, default)
else:
return _get_value_for_keys(
keys[1:], _get_value_for_key(keys[0], obj, default), default
)


def _get_value_for_key(key, obj, default):
if is_indexable_but_not_string(obj):
try:
return obj[key]
except (IndexError, TypeError, KeyError):
pass
if is_integer_indexable(obj):
try:
return obj[int(key)]
except (IndexError, TypeError, ValueError):
pass
return getattr(obj, key, default)


# endregion Copied from flask_restx


class NestedOneOf(fields.Nested):
"""
Unlike other models, the OneOfModel doesn't inherit dict. fields.Nested
doesn't know how to process the output - it tries to marshal the model
as a dict.
This NestedOneOf is used the same as fields.Nested but only for OneOfModels.
It attempts to marshal to each of the fields specified in OneOfModel and
returns whichever one matches the original object, throwing if none do.
The throwing behavior is different from other fields, and the "marshalling"
behaves more like "validating". Care should be taken to ensure only the
exact matching data is represented in this field.
example:
```
ns.add_model("my_one_of", OneOfModel("my_one_of", [fields.Nested(model_a), fields.Nested(model_b)]))
my_model = ns.model("my_model", { "my_field": NestedOneOf(my_one_of, allow_null=True) })
```
See also: access_gate usage in tracks.py
"""

def __init__(self, model: OneOfModel, **kwargs):
super(NestedOneOf, self).__init__(model, **kwargs)

def output(self, key, data, **kwargs):
value = get_value(key, data)
if value is None:
if self.allow_null:
return None
elif self.default is not None:
return self.default
for field in self.model.fields:
marshalled = marshal(value, field.nested)
if value == marshalled:
return value
raise fields.MarshallingError("No matching oneOf models")
76 changes: 76 additions & 0 deletions packages/discovery-provider/src/api/v1/models/extensions/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from typing import List

from flask_restx import Model, SchemaModel, fields


class OneOfModel(SchemaModel):
"""
This is a dirty, dirty hack.
Swagger 2.0 doesn't support the oneOf composition provided in OAS 3.0.0+.
This model does one possible representation of oneOf.
This model makes the resulting swagger.json schema invalid.
The Swagger UI seems to ignore the invalid schema. The OpenAPI generator
can be configured to ignore it as well. In the Audiuis SDK Typescript
generator, validation is skipped, and an OAS 3.0.0 spec is generated from
the invalid swagger.json. In the conversion, OneOfModels become simple
objects, so they are re-added from the swagger.json before generating
the Typescript types.
When marshalling, the dat must match **exactly** one of the formats.
Unlike normal marshallers, this model is more of a validator. Without
a discriminator, it doesn't know what to marshal to, so it just checks
that the data matches one of the models.
** ONLY USE WITH `NestedOneOf`, it does NOT work with fields.Nested **
example:
```
ns.add_model("my_one_of", OneOfModel("my_one_of", [fields.Nested(model_a), fields.Nested(model_b)]))
my_model = ns.model("my_model", { "my_field": NestedOneOf(my_one_of, allow_null=True) })
```
schema output:
```
{
// ...
"definitions": {
"my_one_of": {
"oneOf" [
{ "ref": "#/definitions/model_a" },
{ "ref": "#/definitinos/model_b" }
]
}
}
}
```
See also: access_gate usage in tracks.py
"""

def __init__(self, name, fields: List[fields.Nested], *args, **kwargs):
super(OneOfModel, self).__init__(
name, {"oneOf": [field.__schema__ for field in fields]}
)
self.fields = fields

# hack to register related models - hijacks Polymorphism pattern
self.__parents__ = [field.nested for field in self.fields]

@property
def __schema__(self):
# override the base model to prevent the polymorph allOf from happening
return self._schema


class WildcardModel(Model):
"""Hack of the Model that allows the schema to be properly formatted for a wildcard field."""

@property
def _schema(self):
# Skip the schema for this and surface the child schema if the wildcard is found
if "*" in self:
return self["*"].__schema__
return super(WildcardModel, self)._schema()
7 changes: 5 additions & 2 deletions packages/discovery-provider/src/api/v1/models/tracks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from flask_restx import fields

from .access_gate import access_gate
from .common import favorite, ns, repost
from .extensions.fields import NestedOneOf
from .users import user_model, user_model_full

track_artwork = ns.model(
Expand Down Expand Up @@ -126,6 +128,7 @@
},
)


track_full = ns.clone(
"track_full",
track,
Expand Down Expand Up @@ -159,9 +162,9 @@
"remix_of": fields.Nested(full_remix_parent),
"is_available": fields.Boolean,
"is_stream_gated": fields.Boolean,
"stream_conditions": fields.Raw(allow_null=True),
"stream_conditions": NestedOneOf(access_gate, allow_null=True),
"is_download_gated": fields.Boolean,
"download_conditions": fields.Raw(allow_null=True),
"download_conditions": NestedOneOf(access_gate, allow_null=True),
"access": fields.Nested(access),
"ai_attribution_user_id": fields.Integer(allow_null=True),
"audio_upload_id": fields.String,
Expand Down
12 changes: 0 additions & 12 deletions packages/discovery-provider/src/api/v1/models/wildcard_model.py

This file was deleted.

2 changes: 1 addition & 1 deletion packages/discovery-provider/src/api/v1/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
)
from src.api.v1.models.common import favorite
from src.api.v1.models.developer_apps import authorized_app, developer_app
from src.api.v1.models.extensions.models import WildcardModel
from src.api.v1.models.grants import managed_user, user_manager
from src.api.v1.models.support import (
supporter_response,
Expand All @@ -77,7 +78,6 @@
user_model_full,
user_subscribers,
)
from src.api.v1.models.wildcard_model import WildcardModel
from src.api.v1.playlists import get_tracks_for_playlist
from src.challenges.challenge_event_bus import setup_challenge_bus
from src.queries.download_csv import (
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.6.0-SNAPSHOT
7.5.0-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.openapi-generator-ignore
apis/ChallengesApi.ts
apis/PlaylistsApi.ts
apis/ReactionsApi.ts
Expand All @@ -9,6 +10,7 @@ apis/UsersApi.ts
apis/index.ts
index.ts
models/Access.ts
models/AccessGate.ts
models/ActivityFull.ts
models/Attestation.ts
models/AttestationReponse.ts
Expand All @@ -19,6 +21,7 @@ models/CoverPhoto.ts
models/DownloadMetadata.ts
models/Favorite.ts
models/FieldVisibility.ts
models/FollowGate.ts
models/FollowingResponse.ts
models/FollowingResponseFull.ts
models/FullBulkSubscribersResponse.ts
Expand Down Expand Up @@ -47,13 +50,16 @@ models/HistoryResponseFull.ts
models/ManagedUser.ts
models/ManagedUsersResponse.ts
models/ManagersResponse.ts
models/NftCollection.ts
models/NftGate.ts
models/PlaylistAddedTimestamp.ts
models/PlaylistArtwork.ts
models/PlaylistFull.ts
models/PlaylistFullWithoutTracks.ts
models/PlaylistLibrary.ts
models/ProfilePicture.ts
models/Purchase.ts
models/PurchaseGate.ts
models/PurchasesCountResponse.ts
models/PurchasesResponse.ts
models/RelatedArtistResponseFull.ts
Expand All @@ -68,6 +74,7 @@ models/StemFull.ts
models/StemParent.ts
models/StemsResponse.ts
models/SupporterReference.ts
models/TipGate.ts
models/TopGenreUsersResponseFull.ts
models/TopUsersResponseFull.ts
models/TrackActivityFull.ts
Expand All @@ -83,6 +90,7 @@ models/TransactionHistoryCountResponse.ts
models/TransactionHistoryResponse.ts
models/TrendingIdsResponse.ts
models/TrendingTimesIds.ts
models/UsdcGate.ts
models/UserFull.ts
models/UserManager.ts
models/UserSubscribers.ts
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.6.0-SNAPSHOT
7.5.0-SNAPSHOT

0 comments on commit 621c3ee

Please sign in to comment.