Skip to content

Commit

Permalink
fix: UI OpenApi generator fails to create a proper Feed's discriminat…
Browse files Browse the repository at this point in the history
…or (#555)
  • Loading branch information
davidgamez authored Jul 16, 2024
1 parent 85b283b commit 19c9f71
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 65 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/typescript-generator-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Verify TypeScript Types Generation
on:
pull_request:
branches:
- main
paths:
- "docs/DatabaseCatalogAPI.yaml"

env:
NODE_VERSION: "18"

jobs:
generate-and-compare:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
cache-dependency-path: 'web-app/yarn.lock'

- name: Cache Yarn dependencies
uses: actions/cache@v4
id: yarn-cache
with:
path: |
**/node_modules
**/.eslintcache
key: ${{ runner.os }}-yarn-${{ hashFiles('web-app/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
working-directory: web-app
run: yarn install --frozen-lockfile --prefer-offline

- name: Generate TypeScript types
working-directory: web-app
run: yarn generate:api-types:output
env:
OUTPUT_PATH_TYPES: src/app/services/feeds/generated/types.ts

- name: Compare TypeScript types with existing types
working-directory: web-app
run: diff src/app/services/feeds/generated/types.ts src/app/services/feeds/types.ts || (echo "Types are different!" && exit 1)
2 changes: 1 addition & 1 deletion api/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ src/feeds_gen/models/latest_dataset_validation_report.py
src/feeds_gen/models/location.py
src/feeds_gen/models/metadata.py
src/feeds_gen/models/redirect.py
src/feeds_gen/models/search_feed_item_result.py
src/feeds_gen/models/search_feeds200_response.py
src/feeds_gen/models/search_feeds200_response_results_inner.py
src/feeds_gen/models/source_info.py
src/feeds_gen/models/validation_report.py
src/feeds_gen/security_api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from feeds_gen.models.latest_dataset import LatestDataset
from feeds_gen.models.search_feeds200_response_results_inner import SearchFeeds200ResponseResultsInner
from feeds_gen.models.search_feed_item_result import SearchFeedItemResult
from feeds_gen.models.source_info import SourceInfo


class SearchFeeds200ResponseResultsInnerImpl(SearchFeeds200ResponseResultsInner):
"""Implementation of the `SearchFeeds200ResponseResultsInner` model.
class SearchFeedItemResultImpl(SearchFeedItemResult):
"""Implementation of the `SearchFeedItemResult` model.
This class converts a SQLAlchemy row object to a Pydantic model instance taking in consideration the data type.
"""

Expand Down
4 changes: 2 additions & 2 deletions api/src/feeds/impl/search_api_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from database.database import Database
from database_gen.sqlacodegen_models import t_feedsearch
from feeds.impl.models.search_feeds200_response_results_inner_impl import SearchFeeds200ResponseResultsInnerImpl
from feeds.impl.models.search_feed_item_result_impl import SearchFeedItemResultImpl
from feeds_gen.apis.search_api_base import BaseSearchApi
from feeds_gen.models.search_feeds200_response import SearchFeeds200Response

Expand Down Expand Up @@ -92,7 +92,7 @@ def search_feeds(
total=0,
)

results = list(map(lambda feed: SearchFeeds200ResponseResultsInnerImpl.from_orm(feed), feed_rows))
results = list(map(lambda feed: SearchFeedItemResultImpl.from_orm(feed), feed_rows))
return SearchFeeds200Response(
results=results,
total=feed_total_count[0][0] if feed_total_count and feed_total_count[0] else 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import copy
from faker import Faker

from feeds.impl.models.search_feeds200_response_results_inner_impl import SearchFeeds200ResponseResultsInnerImpl
from feeds.impl.models.search_feed_item_result_impl import SearchFeedItemResultImpl
from feeds_gen.models.latest_dataset import LatestDataset
from feeds_gen.models.source_info import SourceInfo

Expand Down Expand Up @@ -50,9 +50,9 @@ class TestSearchFeeds200ResponseResultsInnerImpl(unittest.TestCase):
def test_from_orm_gtfs(self):
item = copy.deepcopy(search_item)
item.data_type = "gtfs"
result = SearchFeeds200ResponseResultsInnerImpl.from_orm_gtfs(item)
result = SearchFeedItemResultImpl.from_orm_gtfs(item)
assert result.data_type == "gtfs"
expected = SearchFeeds200ResponseResultsInnerImpl(
expected = SearchFeedItemResultImpl(
id=item.feed_stable_id,
data_type=item.data_type,
status=item.status,
Expand Down Expand Up @@ -82,9 +82,9 @@ def test_from_orm_gtfs(self):
def test_from_orm_gtfs_rt(self):
item = copy.deepcopy(search_item)
item.data_type = "gtfs_rt"
result = SearchFeeds200ResponseResultsInnerImpl.from_orm_gtfs_rt(item)
result = SearchFeedItemResultImpl.from_orm_gtfs_rt(item)
assert result.data_type == "gtfs_rt"
expected = SearchFeeds200ResponseResultsInnerImpl(
expected = SearchFeedItemResultImpl(
id=item.feed_stable_id,
data_type=item.data_type,
status=item.status,
Expand All @@ -110,17 +110,17 @@ def test_from_orm_gtfs_rt(self):
def test_from_orm(self):
item = copy.deepcopy(search_item)
item.data_type = "gtfs"
result = SearchFeeds200ResponseResultsInnerImpl.from_orm(item)
result = SearchFeedItemResultImpl.from_orm(item)
assert result.data_type == "gtfs"

item = copy.deepcopy(search_item)
item.data_type = "gtfs_rt"
result = SearchFeeds200ResponseResultsInnerImpl.from_orm(item)
result = SearchFeedItemResultImpl.from_orm(item)
assert result.data_type == "gtfs_rt"

assert SearchFeeds200ResponseResultsInnerImpl.from_orm(None) is None
assert SearchFeedItemResultImpl.from_orm(None) is None

with pytest.raises(ValueError):
item = copy.deepcopy(search_item)
item.data_type = "unknown"
SearchFeeds200ResponseResultsInnerImpl.from_orm(item)
SearchFeedItemResultImpl.from_orm(item)
108 changes: 103 additions & 5 deletions docs/DatabaseCatalogAPI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,7 @@ paths:
results:
type: array
items:
allOf:
- $ref: '#/components/schemas/GtfsFeed'
- $ref: '#/components/schemas/GtfsRTFeed'

$ref: "#/components/schemas/SearchFeedItemResult"

components:
schemas:
Expand All @@ -316,6 +313,11 @@ components:
example: Redirected because of a change of URL.
BasicFeed:
type: object
discriminator:
propertyName: data_type
mapping:
gtfs: '#/components/schemas/GtfsFeed'
gtfs_rt: '#/components/schemas/GtfsRTFeed'
properties:
id:
description: Unique identifier used as a key for the feeds table.
Expand Down Expand Up @@ -350,7 +352,6 @@ components:
type: string
example: 2023-07-10T22:06:00Z
format: date-time

external_ids:
$ref: "#/components/schemas/ExternalIds"
provider:
Expand Down Expand Up @@ -418,6 +419,103 @@ components:
locations:
$ref: "#/components/schemas/Locations"

SearchFeedItemResult:
# The following schema is used to represent the search results for feeds.
# The schema is a union of all the possible types(BasicFeed, GtfsFeed and GtfsRTFeed) of feeds that can be returned.
# This union is not based on its original types due to the limitations of openapi-generator.
# For the same reason it's not defined as anyOf, but as a single object with all the possible properties.
type: object
required:
- id
- data_type
- status
properties:
id:
description: Unique identifier used as a key for the feeds table.
type: string
example: mdb-1210
data_type:
type: string
enum:
- gtfs
- gtfs_rt
example: gtfs
# Have to put the enum inline because of a bug in openapi-generator
# $ref: "#/components/schemas/DataType"
status:
description: >
Describes status of the Feed. Should be one of
* `active` Feed should be used in public trip planners.
* `deprecated` Feed is explicitly deprecated and should not be used in public trip planners.
* `inactive` Feed hasn't been recently updated and should be used at risk of providing outdated information.
* `development` Feed is being used for development purposes and should not be used in public trip planners.
type: string
enum:
- active
- deprecated
- inactive
- development
example: deprecated
# Have to put the enum inline because of a bug in openapi-generator
# $ref: "#/components/schemas/FeedStatus"
created_at:
description: The date and time the feed was added to the database, in ISO 8601 date-time format.
type: string
example: 2023-07-10T22:06:00Z
format: date-time
external_ids:
$ref: "#/components/schemas/ExternalIds"
provider:
description: A commonly used name for the transit provider included in the feed.
type: string
example: Los Angeles Department of Transportation (LADOT, DASH, Commuter Express)
feed_name:
description: >
An optional description of the data feed, e.g to specify if the data feed is an aggregate of
multiple providers, or which network is represented by the feed.
type: string
example: Bus
note:
description: A note to clarify complex use cases for consumers.
type: string
feed_contact_email:
description: Use to contact the feed producer.
type: string
example: [email protected]
source_info:
$ref: "#/components/schemas/SourceInfo"
redirects:
type: array
items:
$ref: "#/components/schemas/Redirect"
locations:
$ref: "#/components/schemas/Locations"
latest_dataset:
$ref: "#/components/schemas/LatestDataset"
entity_types:
type: array
items:
type: string
enum:
- vp
- tu
- sa
example: vp
description: >
The type of realtime entry:
* vp - vehicle positions
* tu - trip updates
* sa - service alerts
# Have to put the enum inline because of a bug in openapi-generator
# $ref: "#/components/schemas/EntityTypes"
feed_references:
description:
A list of the GTFS feeds that the real time source is associated with, represented by their MDB source IDs.
type: array
items:
type: string
example: "mdb-20"

BasicFeeds:
type: array
items:
Expand Down
3 changes: 2 additions & 1 deletion web-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"lint:fix": "eslint 'src/app/**/*.{js,ts,tsx}' --fix",
"cypress:run": "cypress run",
"cypress:open": "cypress open",
"generate:api-types": "npx openapi-typescript ../docs/DatabaseCatalogAPI.yaml -o src/app/services/feeds/types.ts && eslint src/app/services/feeds/types.ts --fix"
"generate:api-types:output": "npx openapi-typescript ../docs/DatabaseCatalogAPI.yaml -o $OUTPUT_PATH_TYPES && eslint $OUTPUT_PATH_TYPES --fix",
"generate:api-types": "OUTPUT_PATH_TYPES=src/app/services/feeds/types.ts npm run generate:api-types:output"
},
"eslintConfig": {
"extends": [
Expand Down
Loading

0 comments on commit 19c9f71

Please sign in to comment.