From 5ce13ac3e8fcfdd03b053fb8e8d5a36a7686f71e Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:04:59 +0100 Subject: [PATCH 01/13] getting examples working with microcks --- openapi.yaml | 182 +++++++++++++++++++++++++++++---------------------- 1 file changed, 104 insertions(+), 78 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 95bacc6..f3653b7 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -122,22 +122,25 @@ paths: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - example: - data: - - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - name: Berlin Hauptbahnhof - address: Invalidenstraße 10557 Berlin, Germany - country_code: DE - timezone: Europe/Berlin - - id: b2e783e1-c824-4d63-b37a-d8d698862f1d - name: Paris Gare du Nord - address: 18 Rue de Dunkerque 75010 Paris, France - country_code: FR - timezone: Europe/Paris - links: - self: https://api.example.com/stations&page=2 - next: https://api.example.com/stations?page=3 - prev: https://api.example.com/stations?page=1 + examples: + Stations: + summary: A list of train stations + value: + data: + - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + name: Berlin Hauptbahnhof + address: Invalidenstraße 10557 Berlin, Germany + country_code: DE + timezone: Europe/Berlin + - id: b2e783e1-c824-4d63-b37a-d8d698862f1d + name: Paris Gare du Nord + address: 18 Rue de Dunkerque 75010 Paris, France + country_code: FR + timezone: Europe/Paris + links: + self: https://api.example.com/stations&page=2 + next: https://api.example.com/stations?page=3 + prev: https://api.example.com/stations?page=1 application/xml: schema: allOf: @@ -239,29 +242,32 @@ paths: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - example: - data: - - id: ea399ba1-6d95-433f-92d1-83f67b775594 - origin: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - destination: b2e783e1-c824-4d63-b37a-d8d698862f1d - departure_time: '2024-02-01T10:00:00Z' - arrival_time: '2024-02-01T16:00:00Z' - price: 50 - operator: Deutsche Bahn - bicycles_allowed: true - dogs_allowed: true - - id: 4d67459c-af07-40bb-bb12-178dbb88e09f - origin: b2e783e1-c824-4d63-b37a-d8d698862f1d - destination: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - departure_time: '2024-02-01T12:00:00Z' - arrival_time: '2024-02-01T18:00:00Z' - price: 50 - operator: SNCF - bicycles_allowed: true - dogs_allowed: true - links: - self: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01 - next: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01&page=2 + examples: + Trips: + summary: A list of available train trips + value: + data: + - id: ea399ba1-6d95-433f-92d1-83f67b775594 + origin: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + destination: b2e783e1-c824-4d63-b37a-d8d698862f1d + departure_time: '2024-02-01T10:00:00Z' + arrival_time: '2024-02-01T16:00:00Z' + price: 50 + operator: Deutsche Bahn + bicycles_allowed: true + dogs_allowed: true + - id: 4d67459c-af07-40bb-bb12-178dbb88e09f + origin: b2e783e1-c824-4d63-b37a-d8d698862f1d + destination: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + departure_time: '2024-02-01T12:00:00Z' + arrival_time: '2024-02-01T18:00:00Z' + price: 50 + operator: SNCF + bicycles_allowed: true + dogs_allowed: true + links: + self: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01 + next: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01&page=2 application/xml: schema: allOf: @@ -322,21 +328,24 @@ paths: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - example: - data: - - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - passenger_name: John Doe - has_bicycle: true - has_dog: true - - id: b2e783e1-c824-4d63-b37a-d8d698862f1d - trip_id: b2e783e1-c824-4d63-b37a-d8d698862f1d - passenger_name: Jane Smith - has_bicycle: false - has_dog: false - links: - self: https://api.example.com/bookings - next: https://api.example.com/bookings?page=2 + examples: + Bookings: + summary: A list of bookings + value: + data: + - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + - id: b2e783e1-c824-4d63-b37a-d8d698862f1d + trip_id: b2e783e1-c824-4d63-b37a-d8d698862f1d + passenger_name: Jane Smith + has_bicycle: false + has_dog: false + links: + self: https://api.example.com/bookings + next: https://api.example.com/bookings?page=2 application/xml: schema: allOf: @@ -395,14 +404,21 @@ paths: links: $ref: '#/components/schemas/Links-Self' - example: - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - passenger_name: John Doe - has_bicycle: true - has_dog: true - links: - self: https://api.example.com/bookings/efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + examples: + new_booking: + summary: New Booking + value: |- + { + "id": "{{ uuid() }}", + "trip_id": "{{ request.body/trip_id }}", + "passenger_name": "{{ request.body/passenger_name }}", + "has_bicycle": {{ request.body/has_bicycle }}, + "has_dog": {{ request.body/has_dog }}, + "links": { + "self": "https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb" + } + } + application/xml: schema: allOf: @@ -455,14 +471,17 @@ paths: - properties: links: $ref: '#/components/schemas/Links-Self' - example: - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - passenger_name: John Doe - has_bicycle: true - has_dog: true - links: - self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + examples: + booking_efdbb9d1-02c2-4bc3-afb7-6788d8782b1e: + summary: Booking + value: + id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb application/xml: schema: allOf: @@ -494,6 +513,10 @@ paths: responses: '204': description: Booking deleted + examples: + BookingDeleted: + summary: Booking Deleted + value: '' '400': $ref: '#/components/responses/BadRequest' '401': @@ -642,14 +665,17 @@ webhooks: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - example: - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - passenger_name: John Doe - has_bicycle: true - has_dog: true - links: - self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + examples: + new_booking: + summary: New Booking + value: + id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb responses: '200': description: Return a 200 status to indicate that the data was received successfully. From 5ae670f24fc95c93cebea5bacd661bef581fa17e Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Sun, 30 Jun 2024 13:47:33 +0100 Subject: [PATCH 02/13] chore: add microcks spectral rulset This will help make sure the OpenAPI is working well within Microcks. --- .spectral.yaml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.spectral.yaml b/.spectral.yaml index f3ab72e..423215e 100644 --- a/.spectral.yaml +++ b/.spectral.yaml @@ -1,12 +1,5 @@ extends: -# - "spectral:oas" -- "spectral:arazzo" -# - "@stoplight/spectral-owasp-ruleset" -# - "@apisyouwonthate/style-guide"" - -# rules: -# owasp:api4:2019-array-limit: false -# owasp:api4:2019-string-limit: false -# owasp:api4:2019-string-restricted: false -# owasp:api4:2019-integer-limit: false -# owasp:api4:2019-integer-format: false +- "spectral:oas" +- "@stoplight/spectral-owasp-ruleset" +# - "@apisyouwonthate/style-guide" +- "@microcks/spectral-ruleset" From 74a18cfaf06fb0883b4099b097d4bb843fa5dddb Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Sun, 30 Jun 2024 13:47:56 +0100 Subject: [PATCH 03/13] feat: mark production as not internal --- openapi.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index f3653b7..282f3fe 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -25,7 +25,6 @@ servers: - url: https://api.example.com description: Production x-internal: false - - url: https://mocks.example.com/rest description: Mock Server x-internal: false From 2836d9e292c42bebab68d6bcdc313b903f555485 Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Sun, 30 Jun 2024 13:48:49 +0100 Subject: [PATCH 04/13] feat: name more examples in pairs for microcks --- openapi.yaml | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 282f3fe..415d05c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -122,7 +122,7 @@ paths: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' examples: - Stations: + stations: summary: A list of train stations value: data: @@ -195,7 +195,9 @@ paths: schema: type: string format: uuid - example: b2e783e1-c824-4d63-b37a-d8d698862f1d + examples: + trips: + value: b2e783e1-c824-4d63-b37a-d8d698862f1d - name: date in: query description: The date and time of the trip in ISO 8601 format in origin station's timezone. @@ -203,7 +205,9 @@ paths: schema: type: string format: date-time - example: '2024-02-01T09:00:00Z' + examples: + trips: + value: '2024-02-01T09:00:00Z' - name: bicycles in: query description: Only return trips where bicycles are known to be allowed @@ -242,7 +246,7 @@ paths: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' examples: - Trips: + trips: summary: A list of available train trips value: data: @@ -332,12 +336,12 @@ paths: summary: A list of bookings value: data: - - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + - id: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e passenger_name: John Doe has_bicycle: true has_dog: true - - id: b2e783e1-c824-4d63-b37a-d8d698862f1d + - id: 1725ff48-ab45-4bb5-9d02-88745177dedb trip_id: b2e783e1-c824-4d63-b37a-d8d698862f1d passenger_name: Jane Smith has_bicycle: false @@ -408,13 +412,13 @@ paths: summary: New Booking value: |- { - "id": "{{ uuid() }}", + "id": "{{ uuid() > put(bookingId) }}", "trip_id": "{{ request.body/trip_id }}", "passenger_name": "{{ request.body/passenger_name }}", "has_bicycle": {{ request.body/has_bicycle }}, "has_dog": {{ request.body/has_dog }}, "links": { - "self": "https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb" + "self": "https://api.example.com/bookings/{{ bookingId }}" } } @@ -447,7 +451,11 @@ paths: schema: type: string format: uuid - example: 1725ff48-ab45-4bb5-9d02-88745177dedb + examples: + booking_1725ff48-ab45-4bb5-9d02-88745177dedb: + value: 1725ff48-ab45-4bb5-9d02-88745177dedb + booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: + value: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 get: summary: Get a booking description: Returns the details of a specific booking. @@ -471,16 +479,26 @@ paths: links: $ref: '#/components/schemas/Links-Self' examples: - booking_efdbb9d1-02c2-4bc3-afb7-6788d8782b1e: - summary: Booking + booking_1725ff48-ab45-4bb5-9d02-88745177dedb: + summary: John Doe value: - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + id: 1725ff48-ab45-4bb5-9d02-88745177dedb trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e passenger_name: John Doe has_bicycle: true has_dog: true links: self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: + summary: Billy Bikeless + value: + id: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: Billy Bikeless + has_bicycle: false + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb application/xml: schema: allOf: @@ -512,10 +530,6 @@ paths: responses: '204': description: Booking deleted - examples: - BookingDeleted: - summary: Booking Deleted - value: '' '400': $ref: '#/components/responses/BadRequest' '401': From 45d3e6581052c38959e5ed0beae165840dafce02 Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:22:30 +0100 Subject: [PATCH 05/13] added mock server to servers --- openapi.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index 415d05c..e47d4eb 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -98,6 +98,13 @@ paths: type: string format: iso-country-code example: DE + - in: header + name: If-Modified-Since + description: Request only stations modified since this date + schema: + type: string + format: date-time + example: '2024-01-01T00:00:00Z' responses: '200': description: OK @@ -157,6 +164,8 @@ paths: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' + '304': + description: Not Modified '400': $ref: '#/components/responses/BadRequest' '401': @@ -893,6 +902,10 @@ components: description: Name of the passenger examples: - John Doe + seat_preference: + type: string + enum: [window, aisle, any] + example: "window" has_bicycle: type: boolean description: Indicates whether the passenger has a bicycle. From c81ed10c7b85b418924c37ea0014bc55015933f3 Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:32:05 +0100 Subject: [PATCH 06/13] remove caching stuff for now --- openapi.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index e47d4eb..93671b8 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -98,13 +98,6 @@ paths: type: string format: iso-country-code example: DE - - in: header - name: If-Modified-Since - description: Request only stations modified since this date - schema: - type: string - format: date-time - example: '2024-01-01T00:00:00Z' responses: '200': description: OK @@ -164,8 +157,6 @@ paths: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - '304': - description: Not Modified '400': $ref: '#/components/responses/BadRequest' '401': From 3030f1adc86d92b49c9dadc2433ff50dd74795bb Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:04:15 +0100 Subject: [PATCH 07/13] Silence failing rules for microcks --- .spectral.yaml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.spectral.yaml b/.spectral.yaml index 423215e..11cf878 100644 --- a/.spectral.yaml +++ b/.spectral.yaml @@ -1,5 +1,14 @@ extends: - "spectral:oas" -- "@stoplight/spectral-owasp-ruleset" -# - "@apisyouwonthate/style-guide" -- "@microcks/spectral-ruleset" +# - "@stoplight/spectral-owasp-ruleset" +- "@apisyouwonthate/style-guide" +# - "@microcks/spectral-ruleset" + + +rules: + # The API doesn't have these (yet?) + api-home: off + api-health: off + + # Microcks mocking with dynamic examples causes this rule to fail + oas3-valid-media-example: off From 67526de0d80f7c00b8969f677ebb9fbc2667021b Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:04:24 +0100 Subject: [PATCH 08/13] bring back seat preferences later --- openapi.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 93671b8..415d05c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -893,10 +893,6 @@ components: description: Name of the passenger examples: - John Doe - seat_preference: - type: string - enum: [window, aisle, any] - example: "window" has_bicycle: type: boolean description: Indicates whether the passenger has a bicycle. From 42652257bf29fa9d53a9d662e1fa8a7293b7b404 Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:06:33 +0000 Subject: [PATCH 09/13] allow microcks back in for linting --- .spectral.yaml | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.spectral.yaml b/.spectral.yaml index 11cf878..274c9ef 100644 --- a/.spectral.yaml +++ b/.spectral.yaml @@ -2,7 +2,7 @@ extends: - "spectral:oas" # - "@stoplight/spectral-owasp-ruleset" - "@apisyouwonthate/style-guide" -# - "@microcks/spectral-ruleset" +- "@microcks/spectral-ruleset" rules: diff --git a/package.json b/package.json index 6a1ee9f..0e1c146 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "devDependencies": { "@apisyouwonthate/style-guide": "^1.5.0", - "@stoplight/spectral-owasp-ruleset": "^1.4.3" + "@stoplight/spectral-owasp-ruleset": "^1.4.3", + "@microcks/spectral-ruleset": "^0.0.5" } } From 765262b5c4e7b3ea577af3e2eb60bbf1806090b8 Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Thu, 21 Nov 2024 21:50:28 +0000 Subject: [PATCH 10/13] revert main openapi.yaml and make a overlay / generated openapi.mock.yaml --- .microcks/openapi.mock.yaml | 1216 +++++++++++++++++++++++++++++++++++ .microcks/overlays.yaml | 204 ++++++ openapi.yaml | 203 +++--- 3 files changed, 1502 insertions(+), 121 deletions(-) create mode 100644 .microcks/openapi.mock.yaml create mode 100644 .microcks/overlays.yaml diff --git a/.microcks/openapi.mock.yaml b/.microcks/openapi.mock.yaml new file mode 100644 index 0000000..fdeb438 --- /dev/null +++ b/.microcks/openapi.mock.yaml @@ -0,0 +1,1216 @@ +openapi: 3.1.0 +info: + title: Train Travel API + description: | + API for finding and booking train trips across Europe. + + ## Run in Postman + + Experiment with this API in Postman, using our Postman Collection. + + [Run In Postman](https://app.getpostman.com/run-collection/9265903-7a75a0d0-b108-4436-ba54-c6139698dc08?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D9265903-7a75a0d0-b108-4436-ba54-c6139698dc08%26entityType%3Dcollection%26workspaceId%3Df507f69d-9564-419c-89a2-cb8e4c8c7b8f) + version: 1.0.0 + contact: + name: Train Support + url: https://example.com/support + email: support@example.com + license: + name: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International + identifier: CC-BY-NC-SA-4.0 + x-feedbackLink: + label: Submit Feedback + url: https://github.com/bump-sh-examples/train-travel-api/issues/new +servers: + - url: https://api.example.com + description: Production + x-internal: false + - url: https://mocks.example.com/rest + description: Mock Server + x-internal: false +security: + - OAuth2: + - read +tags: + - name: Stations + description: | + Find and filter train stations across Europe, including their location + and local timezone. + - name: Trips + description: | + Timetables and routes for train trips between stations, including pricing + and availability. + - name: Bookings + description: | + Create and manage bookings for train trips, including passenger details + and optional extras. + - name: Payments + description: "Pay for bookings using a card or bank account, and view payment\nstatus and history.\n\n> warn\n> Bookings usually expire within 1 hour so you'll need to make your payment\n> before the expiry date \n" +paths: + /stations: + get: + summary: Get a list of train stations + description: Returns a paginated and searchable list of all train stations. + operationId: get-stations + tags: + - Stations + parameters: + - $ref: '#/components/parameters/page' + - $ref: '#/components/parameters/limit' + - name: coordinates + in: query + description: > + The latitude and longitude of the user's location, to narrow down the search results to sites within a proximity of this location. + + required: false + schema: + type: string + example: 52.5200,13.4050 + - name: search + in: query + description: > + A search term to filter the list of stations by name or address. + + required: false + schema: + type: string + examples: + - Milano Centrale + - Paris + - name: country + in: query + description: Filter stations by country code + required: false + schema: + type: string + format: iso-country-code + example: DE + responses: + '200': + description: OK + headers: + Cache-Control: + $ref: '#/components/headers/Cache-Control' + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Station' + examples: + stations: + summary: A list of train stations + value: + data: + - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + name: Berlin Hauptbahnhof + address: Invalidenstraße 10557 Berlin, Germany + country_code: DE + timezone: Europe/Berlin + - id: b2e783e1-c824-4d63-b37a-d8d698862f1d + name: Paris Gare du Nord + address: 18 Rue de Dunkerque 75010 Paris, France + country_code: FR + timezone: Europe/Paris + links: + self: https://api.example.com/stations&page=2 + next: https://api.example.com/stations?page=3 + prev: https://api.example.com/stations?page=1 + application/xml: + schema: + allOf: + - $ref: '#/components/schemas/Wrapper-Collection' + - properties: + data: + type: array + xml: + name: stations + wrapped: true + items: + $ref: '#/components/schemas/Station' + - properties: + links: + allOf: + - $ref: '#/components/schemas/Links-Self' + - $ref: '#/components/schemas/Links-Pagination' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequests' + '500': + $ref: '#/components/responses/InternalServerError' + /trips: + get: + summary: Get available train trips + description: > + Returns a list of available train trips between the specified origin and destination stations on the given date, and allows for filtering by bicycle and dog allowances. + + operationId: get-trips + tags: + - Trips + parameters: + - $ref: '#/components/parameters/page' + - $ref: '#/components/parameters/limit' + - name: origin + in: query + description: The ID of the origin station + required: true + schema: + type: string + format: uuid + examples: + trips: + value: b2e783e1-c824-4d63-b37a-d8d698862f1d + - name: destination + in: query + description: The ID of the destination station + required: true + schema: + type: string + format: uuid + examples: + trips: + value: '2024-02-01T09:00:00Z' + - name: date + in: query + description: The date and time of the trip in ISO 8601 format in origin station's timezone. + required: true + schema: + type: string + format: date-time + example: '2024-02-01T09:00:00Z' + - name: bicycles + in: query + description: Only return trips where bicycles are known to be allowed + required: false + schema: + type: boolean + default: false + - name: dogs + in: query + description: Only return trips where dogs are known to be allowed + required: false + schema: + type: boolean + default: false + responses: + '200': + description: A list of available train trips + headers: + Cache-Control: + $ref: '#/components/headers/Cache-Control' + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/Wrapper-Collection' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/Trip' + - properties: + links: + allOf: + - $ref: '#/components/schemas/Links-Self' + - $ref: '#/components/schemas/Links-Pagination' + examples: + trips: + summary: A list of available train trips + value: + data: + - id: ea399ba1-6d95-433f-92d1-83f67b775594 + origin: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + destination: b2e783e1-c824-4d63-b37a-d8d698862f1d + departure_time: '2024-02-01T10:00:00Z' + arrival_time: '2024-02-01T16:00:00Z' + price: 50 + operator: Deutsche Bahn + bicycles_allowed: true + dogs_allowed: true + - id: 4d67459c-af07-40bb-bb12-178dbb88e09f + origin: b2e783e1-c824-4d63-b37a-d8d698862f1d + destination: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + departure_time: '2024-02-01T12:00:00Z' + arrival_time: '2024-02-01T18:00:00Z' + price: 50 + operator: SNCF + bicycles_allowed: true + dogs_allowed: true + links: + self: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01 + next: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01&page=2 + application/xml: + schema: + allOf: + - $ref: '#/components/schemas/Wrapper-Collection' + - properties: + data: + type: array + xml: + name: trips + wrapped: true + items: + $ref: '#/components/schemas/Trip' + - properties: + links: + allOf: + - $ref: '#/components/schemas/Links-Self' + - $ref: '#/components/schemas/Links-Pagination' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequests' + '500': + $ref: '#/components/responses/InternalServerError' + /bookings: + get: + operationId: get-bookings + summary: List existing bookings + description: Returns a list of all trip bookings by the authenticated user. + tags: + - Bookings + parameters: + - $ref: '#/components/parameters/page' + - $ref: '#/components/parameters/limit' + responses: + '200': + description: A list of bookings + headers: + Cache-Control: + $ref: '#/components/headers/Cache-Control' + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/Wrapper-Collection' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/Booking' + - properties: + links: + allOf: + - $ref: '#/components/schemas/Links-Self' + - $ref: '#/components/schemas/Links-Pagination' + examples: + bookings: + summary: A list of bookings + value: + data: + - id: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + - id: 1725ff48-ab45-4bb5-9d02-88745177dedb + trip_id: b2e783e1-c824-4d63-b37a-d8d698862f1d + passenger_name: Jane Smith + has_bicycle: false + has_dog: false + links: + self: https://api.example.com/bookings + next: https://api.example.com/bookings?page=2 + application/xml: + schema: + allOf: + - $ref: '#/components/schemas/Wrapper-Collection' + - properties: + data: + type: array + xml: + name: bookings + wrapped: true + items: + $ref: '#/components/schemas/Booking' + - properties: + links: + allOf: + - $ref: '#/components/schemas/Links-Self' + - $ref: '#/components/schemas/Links-Pagination' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequests' + '500': + $ref: '#/components/responses/InternalServerError' + post: + operationId: create-booking + summary: Create a booking + description: A booking is a temporary hold on a trip. It is not confirmed until the payment is processed. + tags: + - Bookings + security: + - OAuth2: + - write + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Booking' + application/xml: + schema: + $ref: '#/components/schemas/Booking' + responses: + '201': + description: Booking successful + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/Booking' + - properties: + links: + $ref: '#/components/schemas/Links-Self' + examples: + new_booking: + summary: New Booking + value: |- + { + "id": "{{ uuid() > put(bookingId) }}", + "trip_id": "{{ request.body/trip_id }}", + "passenger_name": "{{ request.body/passenger_name }}", + "has_bicycle": {{ request.body/has_bicycle }}, + "has_dog": {{ request.body/has_dog }}, + "links": { + "self": "https://api.example.com/bookings/{{ bookingId }}" + } + } + application/xml: + schema: + allOf: + - $ref: '#/components/schemas/Booking' + - properties: + links: + $ref: '#/components/schemas/Links-Self' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '404': + $ref: '#/components/responses/NotFound' + '409': + $ref: '#/components/responses/Conflict' + '429': + $ref: '#/components/responses/TooManyRequests' + '500': + $ref: '#/components/responses/InternalServerError' + /bookings/{bookingId}: + parameters: + - name: bookingId + in: path + required: true + description: The ID of the booking to retrieve. + schema: + type: string + format: uuid + examples: + booking_1725ff48-ab45-4bb5-9d02-88745177dedb: + value: 1725ff48-ab45-4bb5-9d02-88745177dedb + booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: + value: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + get: + summary: Get a booking + description: Returns the details of a specific booking. + operationId: get-booking + tags: + - Bookings + responses: + '200': + description: The booking details + headers: + Cache-Control: + $ref: '#/components/headers/Cache-Control' + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/Booking' + - properties: + links: + $ref: '#/components/schemas/Links-Self' + examples: + booking_1725ff48-ab45-4bb5-9d02-88745177dedb: + summary: John Doe + value: + id: 1725ff48-ab45-4bb5-9d02-88745177dedb + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: + summary: Billy Bikeless + value: + id: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: Billy Bikeless + has_bicycle: false + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + application/xml: + schema: + allOf: + - $ref: '#/components/schemas/Booking' + - properties: + links: + $ref: '#/components/schemas/Links-Self' + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequests' + '500': + $ref: '#/components/responses/InternalServerError' + delete: + summary: Delete a booking + description: Deletes a booking, cancelling the hold on the trip. + operationId: delete-booking + security: + - OAuth2: + - write + tags: + - Bookings + responses: + '204': + description: Booking deleted + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '429': + $ref: '#/components/responses/TooManyRequests' + '500': + $ref: '#/components/responses/InternalServerError' + /bookings/{bookingId}/payment: + parameters: + - name: bookingId + in: path + required: true + description: The ID of the booking to pay for. + schema: + type: string + format: uuid + examples: + Card: + value: 1725ff48-ab45-4bb5-9d02-88745177dedb + Bank: + value: 1725ff48-ab45-4bb5-9d02-88745177dedb + post: + summary: Pay for a Booking + description: A payment is an attempt to pay for the booking, which will confirm the booking for the user and enable them to get their tickets. + operationId: create-booking-payment + tags: + - Payments + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/BookingPayment' + examples: + Card: + summary: Card Payment + value: + amount: 49.99 + currency: gbp + source: + object: card + name: J. Doe + number: '4242424242424242' + cvc: 123 + exp_month: 12 + exp_year: 2025 + address_line1: 123 Fake Street + address_line2: 4th Floor + address_city: London + address_country: gb + address_post_code: N12 9XX + Bank: + summary: Bank Account Payment + value: + amount: 100.5 + currency: gbp + source: + object: bank_account + name: J. Doe + number: '00012345' + sort_code: '000123' + account_type: individual + bank_name: Starling Bank + country: gb + responses: + '200': + description: Payment successful + headers: + Cache-Control: + $ref: '#/components/headers/Cache-Control' + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/BookingPayment' + - properties: + links: + $ref: '#/components/schemas/Links-Booking' + examples: + Card: + summary: Card Payment + value: + id: 2e3b4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a + amount: 49.99 + currency: gbp + source: + object: card + name: J. Doe + number: '************4242' + cvc: 123 + exp_month: 12 + exp_year: 2025 + address_country: gb + address_post_code: N12 9XX + status: succeeded + links: + booking: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb/payment + Bank: + summary: Bank Account Payment + value: + id: 2e3b4f5a-6b7c-8d9e-0f1a-2b3c4d5e6f7a + amount: 100.5 + currency: gbp + source: + object: bank_account + name: J. Doe + account_type: individual + number: '*********2345' + sort_code: '000123' + bank_name: Starling Bank + country: gb + status: succeeded + links: + booking: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + '400': + $ref: '#/components/responses/BadRequest' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/TooManyRequests' + '500': + $ref: '#/components/responses/InternalServerError' +webhooks: + newBooking: + post: + operationId: new-booking + summary: New Booking + description: | + Subscribe to new bookings being created, to update integrations for your users. Related data is available via the links provided in the request. + tags: + - Bookings + requestBody: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/Booking' + - properties: + links: + allOf: + - $ref: '#/components/schemas/Links-Self' + - $ref: '#/components/schemas/Links-Pagination' + examples: + new_booking: + summary: New Booking + value: + id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + responses: + '200': + description: Return a 200 status to indicate that the data was received successfully. +components: + parameters: + page: + name: page + in: query + description: The page number to return + required: false + schema: + type: integer + minimum: 1 + default: 1 + example: 1 + limit: + name: limit + in: query + description: The number of items to return per page + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 10 + example: 10 + securitySchemes: + OAuth2: + type: oauth2 + description: OAuth 2.0 authorization code following RFC8725 best practices. + flows: + authorizationCode: + authorizationUrl: https://example.com/oauth/authorize + tokenUrl: https://example.com/oauth/token + scopes: + read: Read access + write: Write access + schemas: + Station: + type: object + xml: + name: station + required: + - id + - name + - address + - country_code + properties: + id: + type: string + format: uuid + description: Unique identifier for the station. + examples: + - efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + - b2e783e1-c824-4d63-b37a-d8d698862f1d + name: + type: string + description: The name of the station + examples: + - Berlin Hauptbahnhof + - Paris Gare du Nord + address: + type: string + description: The address of the station. + examples: + - Invalidenstraße 10557 Berlin, Germany + - 18 Rue de Dunkerque 75010 Paris, France + country_code: + type: string + description: The country code of the station. + format: iso-country-code + examples: + - DE + - FR + timezone: + type: string + description: The timezone of the station in the [IANA Time Zone Database format](https://www.iana.org/time-zones). + examples: + - Europe/Berlin + - Europe/Paris + Links-Self: + type: object + properties: + self: + type: string + format: uri + Links-Pagination: + type: object + properties: + next: + type: string + format: uri + prev: + type: string + format: uri + Problem: + type: object + xml: + name: problem + namespace: urn:ietf:rfc:7807 + properties: + type: + type: string + description: A URI reference that identifies the problem type + examples: + - https://example.com/probs/out-of-credit + title: + type: string + description: A short, human-readable summary of the problem type + examples: + - You do not have enough credit. + detail: + type: string + description: A human-readable explanation specific to this occurrence of the problem + examples: + - Your current balance is 30, but that costs 50. + instance: + type: string + description: A URI reference that identifies the specific occurrence of the problem + examples: + - /account/12345/msgs/abc + status: + type: integer + description: The HTTP status code + examples: + - 400 + Trip: + type: object + xml: + name: trip + properties: + id: + type: string + format: uuid + description: Unique identifier for the trip + examples: + - 4f4e4e1-c824-4d63-b37a-d8d698862f1d + origin: + type: string + description: The starting station of the trip + examples: + - Berlin Hauptbahnhof + - Paris Gare du Nord + destination: + type: string + description: The destination station of the trip + examples: + - Paris Gare du Nord + - Berlin Hauptbahnhof + departure_time: + type: string + format: date-time + description: The date and time when the trip departs + examples: + - '2024-02-01T10:00:00Z' + arrival_time: + type: string + format: date-time + description: The date and time when the trip arrives + examples: + - '2024-02-01T16:00:00Z' + operator: + type: string + description: The name of the operator of the trip + examples: + - Deutsche Bahn + - SNCF + price: + type: number + description: The cost of the trip + examples: + - 50 + bicycles_allowed: + type: boolean + description: Indicates whether bicycles are allowed on the trip + dogs_allowed: + type: boolean + description: Indicates whether dogs are allowed on the trip + Booking: + type: object + xml: + name: booking + properties: + id: + type: string + format: uuid + description: Unique identifier for the booking + readOnly: true + examples: + - 3f3e3e1-c824-4d63-b37a-d8d698862f1d + trip_id: + type: string + format: uuid + description: Identifier of the booked trip + examples: + - 4f4e4e1-c824-4d63-b37a-d8d698862f1d + passenger_name: + type: string + description: Name of the passenger + examples: + - John Doe + has_bicycle: + type: boolean + description: Indicates whether the passenger has a bicycle. + has_dog: + type: boolean + description: Indicates whether the passenger has a dog. + Wrapper-Collection: + description: This is a generic request/response wrapper which contains both data and links which serve as hypermedia controls (HATEOAS). + type: object + properties: + data: + description: The wrapper for a collection is an array of objects. + type: array + items: + type: object + links: + description: A set of hypermedia links which serve as controls for the client. + type: object + readOnly: true + xml: + name: data + BookingPayment: + type: object + properties: + id: + description: Unique identifier for the payment. This will be a unique identifier for the payment, and is used to reference the payment in other objects. + type: string + format: uuid + readOnly: true + amount: + description: Amount intended to be collected by this payment. A positive decimal figure describing the amount to be collected. + type: number + exclusiveMinimum: 0 + examples: + - 49.99 + currency: + description: Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. + type: string + enum: + - bam + - bgn + - chf + - eur + - gbp + - nok + - sek + - try + source: + unevaluatedProperties: false + description: The payment source to take the payment from. This can be a card or a bank account. Some of these properties will be hidden on read to protect PII leaking. + oneOf: + - title: Card + description: A card (debit or credit) to take payment from. + type: object + properties: + object: + type: string + const: card + name: + type: string + description: Cardholder's full name as it appears on the card. + examples: + - Francis Bourgeois + number: + type: string + description: The card number, as a string without any separators. On read all but the last four digits will be masked for security. + examples: + - '4242424242424242' + cvc: + type: integer + description: Card security code, 3 or 4 digits usually found on the back of the card. + minLength: 3 + maxLength: 4 + writeOnly: true + example: 123 + exp_month: + type: integer + format: int64 + description: Two-digit number representing the card's expiration month. + examples: + - 12 + exp_year: + type: integer + format: int64 + description: Four-digit number representing the card's expiration year. + examples: + - 2025 + address_line1: + type: string + writeOnly: true + address_line2: + type: string + writeOnly: true + address_city: + type: string + address_country: + type: string + address_post_code: + type: string + required: + - name + - number + - cvc + - exp_month + - exp_year + - address_country + - title: Bank Account + description: A bank account to take payment from. Must be able to make payments in the currency specified in the payment. + type: object + properties: + object: + const: bank_account + type: string + name: + type: string + number: + type: string + description: The account number for the bank account, in string form. Must be a current account. + sort_code: + type: string + description: The sort code for the bank account, in string form. Must be a six-digit number. + account_type: + enum: + - individual + - company + type: string + description: The type of entity that holds the account. This can be either `individual` or `company`. + bank_name: + type: string + description: The name of the bank associated with the routing number. + examples: + - Starling Bank + country: + type: string + description: Two-letter country code (ISO 3166-1 alpha-2). + required: + - name + - number + - account_type + - bank_name + - country + status: + description: The status of the payment, one of `pending`, `succeeded`, or `failed`. + type: string + enum: + - pending + - succeeded + - failed + readOnly: true + Links-Booking: + type: object + properties: + booking: + type: string + format: uri + examples: + - https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + headers: + Cache-Control: + description: "The Cache-Control header communicates directives for caching mechanisms in both requests and responses. \nIt is used to specify the caching directives in responses to prevent caches from storing sensitive information.\n" + schema: + type: string + description: A comma-separated list of directives as defined in [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111.html). + examples: + - max-age=3600 + - max-age=604800, public + - no-store + - no-cache + - private + RateLimit: + description: | + The RateLimit header communicates quota policies. It contains a `limit` to + convey the expiring limit, `remaining` to convey the remaining quota units, + and `reset` to convey the time window reset time. + schema: + type: string + examples: + - limit=10, remaining=0, reset=10 + Retry-After: + description: "The Retry-After header indicates how long the user agent should wait before making a follow-up request. \nThe value is in seconds and can be an integer or a date in the future. \nIf the value is an integer, it indicates the number of seconds to wait. \nIf the value is a date, it indicates the time at which the user agent should make a follow-up request. \n" + schema: + type: string + examples: + integer: + value: '120' + summary: Retry after 120 seconds + date: + value: 'Fri, 31 Dec 2021 23:59:59 GMT' + summary: Retry after the specified date + responses: + BadRequest: + description: Bad Request + headers: + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/bad-request + title: Bad Request + status: 400 + detail: The request is invalid or missing required parameters. + application/problem+xml: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/bad-request + title: Bad Request + status: 400 + detail: The request is invalid or missing required parameters. + Conflict: + description: Conflict + headers: + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/conflict + title: Conflict + status: 409 + detail: There is a conflict with an existing resource. + application/problem+xml: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/conflict + title: Conflict + status: 409 + detail: There is a conflict with an existing resource. + Forbidden: + description: Forbidden + headers: + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/forbidden + title: Forbidden + status: 403 + detail: Access is forbidden with the provided credentials. + application/problem+xml: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/forbidden + title: Forbidden + status: 403 + detail: Access is forbidden with the provided credentials. + InternalServerError: + description: Internal Server Error + headers: + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/internal-server-error + title: Internal Server Error + status: 500 + detail: An unexpected error occurred. + application/problem+xml: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/internal-server-error + title: Internal Server Error + status: 500 + detail: An unexpected error occurred. + NotFound: + description: Not Found + headers: + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/not-found + title: Not Found + status: 404 + detail: The requested resource was not found. + application/problem+xml: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/not-found + title: Not Found + status: 404 + detail: The requested resource was not found. + TooManyRequests: + description: Too Many Requests + headers: + RateLimit: + $ref: '#/components/headers/RateLimit' + Retry-After: + $ref: '#/components/headers/Retry-After' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/too-many-requests + title: Too Many Requests + status: 429 + detail: You have exceeded the rate limit. + application/problem+xml: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/too-many-requests + title: Too Many Requests + status: 429 + detail: You have exceeded the rate limit. + Unauthorized: + description: Unauthorized + headers: + RateLimit: + $ref: '#/components/headers/RateLimit' + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/unauthorized + title: Unauthorized + status: 401 + detail: You do not have the necessary permissions. + application/problem+xml: + schema: + $ref: '#/components/schemas/Problem' + example: + type: https://example.com/errors/unauthorized + title: Unauthorized + status: 401 + detail: You do not have the necessary permissions. diff --git a/.microcks/overlays.yaml b/.microcks/overlays.yaml new file mode 100644 index 0000000..07e9878 --- /dev/null +++ b/.microcks/overlays.yaml @@ -0,0 +1,204 @@ +overlay: 1.0.0 +info: + title: Train Travel API - Microcks + description: | + This API is a mock server for the Train Travel API, which provides a way to + find and book train trips across Europe. It includes endpoints for finding + stations, getting timetables, and managing bookings. + version: 1.0.0 + +actions: + + - target: $["x-topics"] + description: Remove x-topics, not needed for mock server. + remove: true + + - target: $.paths["/stations"].get.responses["200"].content["application/json"]["example"] + description: Remove the example from get /stations. + remove: true + + - target: $.paths["/stations"].get.responses["200"].content["application/json"] + description: Add Microcks examples for get /stations. + update: + examples: + stations: + summary: A list of train stations + value: + data: + - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + name: Berlin Hauptbahnhof + address: Invalidenstraße 10557 Berlin, Germany + country_code: DE + timezone: Europe/Berlin + - id: b2e783e1-c824-4d63-b37a-d8d698862f1d + name: Paris Gare du Nord + address: 18 Rue de Dunkerque 75010 Paris, France + country_code: FR + timezone: Europe/Paris + links: + self: https://api.example.com/stations&page=2 + next: https://api.example.com/stations?page=3 + prev: https://api.example.com/stations?page=1 + + - target: $.paths["/trips"].get.parameters[?(@.name=="origin")].example + description: Remove the example from get /trips - origin parameter. + remove: true + + - target: $.paths["/trips"].get.parameters[?(@.name=="origin")] + description: Add Microcks examples for get /trips - origin parameter + update: + examples: + trips: + value: b2e783e1-c824-4d63-b37a-d8d698862f1d + + - target: $.paths["/trips"].get.parameters[?(@.name=="destination")].example + remove: true + + - target: $.paths["/trips"].get.parameters[?(@.name=="destination")] + update: + examples: + trips: + value: '2024-02-01T09:00:00Z' + + - target: $.paths["/trips"].get.responses["200"].content["application/json"]["example"] + remove: true + + - target: $.paths["/trips"].get.responses["200"].content["application/json"] + update: + examples: + trips: + summary: A list of available train trips + value: + data: + - id: ea399ba1-6d95-433f-92d1-83f67b775594 + origin: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + destination: b2e783e1-c824-4d63-b37a-d8d698862f1d + departure_time: '2024-02-01T10:00:00Z' + arrival_time: '2024-02-01T16:00:00Z' + price: 50 + operator: Deutsche Bahn + bicycles_allowed: true + dogs_allowed: true + - id: 4d67459c-af07-40bb-bb12-178dbb88e09f + origin: b2e783e1-c824-4d63-b37a-d8d698862f1d + destination: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + departure_time: '2024-02-01T12:00:00Z' + arrival_time: '2024-02-01T18:00:00Z' + price: 50 + operator: SNCF + bicycles_allowed: true + dogs_allowed: true + links: + self: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01 + next: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01&page=2 + + - target: $.paths["/bookings"].get.responses["200"].content["application/json"]["example"] + remove: true + + - target: $.paths["/bookings"].get.responses["200"].content["application/json"] + update: + examples: + bookings: + summary: A list of bookings + value: + data: + - id: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + - id: 1725ff48-ab45-4bb5-9d02-88745177dedb + trip_id: b2e783e1-c824-4d63-b37a-d8d698862f1d + passenger_name: Jane Smith + has_bicycle: false + has_dog: false + links: + self: https://api.example.com/bookings + next: https://api.example.com/bookings?page=2 + + - target: $.paths["/bookings"]["post"].responses["201"].content["application/json"]["example"] + remove: true + + - target: $.paths["/bookings"]["post"].responses["201"].content["application/json"] + update: + examples: + new_booking: + summary: New Booking + value: |- + { + "id": "{{ uuid() > put(bookingId) }}", + "trip_id": "{{ request.body/trip_id }}", + "passenger_name": "{{ request.body/passenger_name }}", + "has_bicycle": {{ request.body/has_bicycle }}, + "has_dog": {{ request.body/has_dog }}, + "links": { + "self": "https://api.example.com/bookings/{{ bookingId }}" + } + } + + - target: $.paths["/bookings/{bookingId}"].parameters[?(@.name=="bookingId")].example + remove: true + + - target: $.paths["/bookings/{bookingId}"].parameters[?(@.name=="bookingId")] + update: + examples: + booking_1725ff48-ab45-4bb5-9d02-88745177dedb: + value: 1725ff48-ab45-4bb5-9d02-88745177dedb + booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: + value: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + + - target: $.paths["/bookings/{bookingId}"].get.responses["200"].content["application/json"]["example"] + remove: true + + - target: $.paths["/bookings/{bookingId}"].get.responses["200"].content["application/json"] + update: + examples: + booking_1725ff48-ab45-4bb5-9d02-88745177dedb: + summary: John Doe + value: + id: 1725ff48-ab45-4bb5-9d02-88745177dedb + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: + summary: Billy Bikeless + value: + id: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: Billy Bikeless + has_bicycle: false + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + + + - target: $.paths["/bookings/{bookingId}/payment"].parameters[?(@.name=="bookingId")].example + remove: true + + - target: $.paths["/bookings/{bookingId}/payment"].parameters[?(@.name=="bookingId")] + update: + examples: + Card: + value: 1725ff48-ab45-4bb5-9d02-88745177dedb + Bank: + value: 1725ff48-ab45-4bb5-9d02-88745177dedb + + - target: $.webhooks["newBooking"]["post"]["requestBody"].content["application/json"]["example"] + remove: true + + - target: $.webhooks["newBooking"]["post"]["requestBody"].content["application/json"] + update: + examples: + new_booking: + summary: New Booking + value: + id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb diff --git a/openapi.yaml b/openapi.yaml index 415d05c..95bacc6 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -25,6 +25,7 @@ servers: - url: https://api.example.com description: Production x-internal: false + - url: https://mocks.example.com/rest description: Mock Server x-internal: false @@ -121,25 +122,22 @@ paths: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - examples: - stations: - summary: A list of train stations - value: - data: - - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - name: Berlin Hauptbahnhof - address: Invalidenstraße 10557 Berlin, Germany - country_code: DE - timezone: Europe/Berlin - - id: b2e783e1-c824-4d63-b37a-d8d698862f1d - name: Paris Gare du Nord - address: 18 Rue de Dunkerque 75010 Paris, France - country_code: FR - timezone: Europe/Paris - links: - self: https://api.example.com/stations&page=2 - next: https://api.example.com/stations?page=3 - prev: https://api.example.com/stations?page=1 + example: + data: + - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + name: Berlin Hauptbahnhof + address: Invalidenstraße 10557 Berlin, Germany + country_code: DE + timezone: Europe/Berlin + - id: b2e783e1-c824-4d63-b37a-d8d698862f1d + name: Paris Gare du Nord + address: 18 Rue de Dunkerque 75010 Paris, France + country_code: FR + timezone: Europe/Paris + links: + self: https://api.example.com/stations&page=2 + next: https://api.example.com/stations?page=3 + prev: https://api.example.com/stations?page=1 application/xml: schema: allOf: @@ -195,9 +193,7 @@ paths: schema: type: string format: uuid - examples: - trips: - value: b2e783e1-c824-4d63-b37a-d8d698862f1d + example: b2e783e1-c824-4d63-b37a-d8d698862f1d - name: date in: query description: The date and time of the trip in ISO 8601 format in origin station's timezone. @@ -205,9 +201,7 @@ paths: schema: type: string format: date-time - examples: - trips: - value: '2024-02-01T09:00:00Z' + example: '2024-02-01T09:00:00Z' - name: bicycles in: query description: Only return trips where bicycles are known to be allowed @@ -245,32 +239,29 @@ paths: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - examples: - trips: - summary: A list of available train trips - value: - data: - - id: ea399ba1-6d95-433f-92d1-83f67b775594 - origin: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - destination: b2e783e1-c824-4d63-b37a-d8d698862f1d - departure_time: '2024-02-01T10:00:00Z' - arrival_time: '2024-02-01T16:00:00Z' - price: 50 - operator: Deutsche Bahn - bicycles_allowed: true - dogs_allowed: true - - id: 4d67459c-af07-40bb-bb12-178dbb88e09f - origin: b2e783e1-c824-4d63-b37a-d8d698862f1d - destination: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - departure_time: '2024-02-01T12:00:00Z' - arrival_time: '2024-02-01T18:00:00Z' - price: 50 - operator: SNCF - bicycles_allowed: true - dogs_allowed: true - links: - self: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01 - next: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01&page=2 + example: + data: + - id: ea399ba1-6d95-433f-92d1-83f67b775594 + origin: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + destination: b2e783e1-c824-4d63-b37a-d8d698862f1d + departure_time: '2024-02-01T10:00:00Z' + arrival_time: '2024-02-01T16:00:00Z' + price: 50 + operator: Deutsche Bahn + bicycles_allowed: true + dogs_allowed: true + - id: 4d67459c-af07-40bb-bb12-178dbb88e09f + origin: b2e783e1-c824-4d63-b37a-d8d698862f1d + destination: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + departure_time: '2024-02-01T12:00:00Z' + arrival_time: '2024-02-01T18:00:00Z' + price: 50 + operator: SNCF + bicycles_allowed: true + dogs_allowed: true + links: + self: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01 + next: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01&page=2 application/xml: schema: allOf: @@ -331,24 +322,21 @@ paths: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - examples: - Bookings: - summary: A list of bookings - value: - data: - - id: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 - trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - passenger_name: John Doe - has_bicycle: true - has_dog: true - - id: 1725ff48-ab45-4bb5-9d02-88745177dedb - trip_id: b2e783e1-c824-4d63-b37a-d8d698862f1d - passenger_name: Jane Smith - has_bicycle: false - has_dog: false - links: - self: https://api.example.com/bookings - next: https://api.example.com/bookings?page=2 + example: + data: + - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + - id: b2e783e1-c824-4d63-b37a-d8d698862f1d + trip_id: b2e783e1-c824-4d63-b37a-d8d698862f1d + passenger_name: Jane Smith + has_bicycle: false + has_dog: false + links: + self: https://api.example.com/bookings + next: https://api.example.com/bookings?page=2 application/xml: schema: allOf: @@ -407,21 +395,14 @@ paths: links: $ref: '#/components/schemas/Links-Self' - examples: - new_booking: - summary: New Booking - value: |- - { - "id": "{{ uuid() > put(bookingId) }}", - "trip_id": "{{ request.body/trip_id }}", - "passenger_name": "{{ request.body/passenger_name }}", - "has_bicycle": {{ request.body/has_bicycle }}, - "has_dog": {{ request.body/has_dog }}, - "links": { - "self": "https://api.example.com/bookings/{{ bookingId }}" - } - } - + example: + id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/efdbb9d1-02c2-4bc3-afb7-6788d8782b1e application/xml: schema: allOf: @@ -451,11 +432,7 @@ paths: schema: type: string format: uuid - examples: - booking_1725ff48-ab45-4bb5-9d02-88745177dedb: - value: 1725ff48-ab45-4bb5-9d02-88745177dedb - booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: - value: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + example: 1725ff48-ab45-4bb5-9d02-88745177dedb get: summary: Get a booking description: Returns the details of a specific booking. @@ -478,27 +455,14 @@ paths: - properties: links: $ref: '#/components/schemas/Links-Self' - examples: - booking_1725ff48-ab45-4bb5-9d02-88745177dedb: - summary: John Doe - value: - id: 1725ff48-ab45-4bb5-9d02-88745177dedb - trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - passenger_name: John Doe - has_bicycle: true - has_dog: true - links: - self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb - booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: - summary: Billy Bikeless - value: - id: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 - trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - passenger_name: Billy Bikeless - has_bicycle: false - has_dog: true - links: - self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + example: + id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb application/xml: schema: allOf: @@ -678,17 +642,14 @@ webhooks: allOf: - $ref: '#/components/schemas/Links-Self' - $ref: '#/components/schemas/Links-Pagination' - examples: - new_booking: - summary: New Booking - value: - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e - passenger_name: John Doe - has_bicycle: true - has_dog: true - links: - self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + example: + id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + trip_id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e + passenger_name: John Doe + has_bicycle: true + has_dog: true + links: + self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb responses: '200': description: Return a 200 status to indicate that the data was received successfully. From 25e1c7848c60d4d925ec01e11bf24fddbaeb5807 Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:02:09 +0000 Subject: [PATCH 11/13] add a script to remind me how to do this --- .microcks/{openapi.mock.yaml => openapi.yaml} | 270 ++++++++++++------ package.json | 6 + 2 files changed, 187 insertions(+), 89 deletions(-) rename .microcks/{openapi.mock.yaml => openapi.yaml} (80%) diff --git a/.microcks/openapi.mock.yaml b/.microcks/openapi.yaml similarity index 80% rename from .microcks/openapi.mock.yaml rename to .microcks/openapi.yaml index fdeb438..5f5fbf6 100644 --- a/.microcks/openapi.mock.yaml +++ b/.microcks/openapi.yaml @@ -1,30 +1,35 @@ openapi: 3.1.0 info: title: Train Travel API - description: | + description: > API for finding and booking train trips across Europe. + ## Run in Postman + Experiment with this API in Postman, using our Postman Collection. - [Run In Postman](https://app.getpostman.com/run-collection/9265903-7a75a0d0-b108-4436-ba54-c6139698dc08?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D9265903-7a75a0d0-b108-4436-ba54-c6139698dc08%26entityType%3Dcollection%26workspaceId%3Df507f69d-9564-419c-89a2-cb8e4c8c7b8f) + + [Run In Postman](https://app.getpostman.com/run-collection/9265903-7a75a0d0-b108-4436-ba54-c6139698dc08?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D9265903-7a75a0d0-b108-4436-ba54-c6139698dc08%26entityType%3Dcollection%26workspaceId%3Df507f69d-9564-419c-89a2-cb8e4c8c7b8f) version: 1.0.0 contact: name: Train Support - url: https://example.com/support + url: 'https://example.com/support' email: support@example.com license: name: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International identifier: CC-BY-NC-SA-4.0 x-feedbackLink: label: Submit Feedback - url: https://github.com/bump-sh-examples/train-travel-api/issues/new + url: 'https://github.com/bump-sh-examples/train-travel-api/issues/new' servers: - - url: https://api.example.com + - url: 'https://api.example.com' description: Production x-internal: false - - url: https://mocks.example.com/rest + - url: 'https://mocks.example.com/rest' description: Mock Server x-internal: false security: @@ -44,7 +49,18 @@ tags: Create and manage bookings for train trips, including passenger details and optional extras. - name: Payments - description: "Pay for bookings using a card or bank account, and view payment\nstatus and history.\n\n> warn\n> Bookings usually expire within 1 hour so you'll need to make your payment\n> before the expiry date \n" + description: > + Pay for bookings using a card or bank account, and view payment + + status and history. + + + > warn + + > Bookings usually expire within 1 hour so you'll need to make your + payment + + > before the expiry date paths: /stations: get: @@ -59,17 +75,16 @@ paths: - name: coordinates in: query description: > - The latitude and longitude of the user's location, to narrow down the search results to sites within a proximity of this location. - + The latitude and longitude of the user's location, to narrow down + the search results to sites within a proximity of this location. required: false schema: type: string - example: 52.5200,13.4050 + example: '52.5200,13.4050' - name: search in: query - description: > + description: | A search term to filter the list of stations by name or address. - required: false schema: type: string @@ -95,12 +110,18 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Station' + allOf: + - $ref: '#/components/schemas/Wrapper-Collection' + - properties: + data: + type: array + items: + $ref: '#/components/schemas/Station' + - properties: + links: + allOf: + - $ref: '#/components/schemas/Links-Self' + - $ref: '#/components/schemas/Links-Pagination' examples: stations: summary: A list of train stations @@ -108,18 +129,18 @@ paths: data: - id: efdbb9d1-02c2-4bc3-afb7-6788d8782b1e name: Berlin Hauptbahnhof - address: Invalidenstraße 10557 Berlin, Germany + address: 'Invalidenstraße 10557 Berlin, Germany' country_code: DE timezone: Europe/Berlin - id: b2e783e1-c824-4d63-b37a-d8d698862f1d name: Paris Gare du Nord - address: 18 Rue de Dunkerque 75010 Paris, France + address: '18 Rue de Dunkerque 75010 Paris, France' country_code: FR timezone: Europe/Paris links: - self: https://api.example.com/stations&page=2 - next: https://api.example.com/stations?page=3 - prev: https://api.example.com/stations?page=1 + self: 'https://api.example.com/stations&page=2' + next: 'https://api.example.com/stations?page=3' + prev: 'https://api.example.com/stations?page=1' application/xml: schema: allOf: @@ -151,8 +172,9 @@ paths: get: summary: Get available train trips description: > - Returns a list of available train trips between the specified origin and destination stations on the given date, and allows for filtering by bicycle and dog allowances. - + Returns a list of available train trips between the specified origin and + destination stations on the given date, and allows for filtering by + bicycle and dog allowances. operationId: get-trips tags: - Trips @@ -181,7 +203,9 @@ paths: value: '2024-02-01T09:00:00Z' - name: date in: query - description: The date and time of the trip in ISO 8601 format in origin station's timezone. + description: >- + The date and time of the trip in ISO 8601 format in origin station's + timezone. required: true schema: type: string @@ -248,8 +272,10 @@ paths: bicycles_allowed: true dogs_allowed: true links: - self: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01 - next: https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01&page=2 + self: >- + https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01 + next: >- + https://api.example.com/trips?origin=efdbb9d1-02c2-4bc3-afb7-6788d8782b1e&destination=b2e783e1-c824-4d63-b37a-d8d698862f1d&date=2024-02-01&page=2 application/xml: schema: allOf: @@ -326,8 +352,8 @@ paths: has_bicycle: false has_dog: false links: - self: https://api.example.com/bookings - next: https://api.example.com/bookings?page=2 + self: 'https://api.example.com/bookings' + next: 'https://api.example.com/bookings?page=2' application/xml: schema: allOf: @@ -358,7 +384,9 @@ paths: post: operationId: create-booking summary: Create a booking - description: A booking is a temporary hold on a trip. It is not confirmed until the payment is processed. + description: >- + A booking is a temporary hold on a trip. It is not confirmed until the + payment is processed. tags: - Bookings security: @@ -417,7 +445,7 @@ paths: $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalServerError' - /bookings/{bookingId}: + '/bookings/{bookingId}': parameters: - name: bookingId in: path @@ -463,7 +491,8 @@ paths: has_bicycle: true has_dog: true links: - self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + self: >- + https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: summary: Billy Bikeless value: @@ -473,7 +502,8 @@ paths: has_bicycle: false has_dog: true links: - self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + self: >- + https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb application/xml: schema: allOf: @@ -495,7 +525,7 @@ paths: $ref: '#/components/responses/InternalServerError' delete: summary: Delete a booking - description: Deletes a booking, cancelling the hold on the trip. + description: 'Deletes a booking, cancelling the hold on the trip.' operationId: delete-booking security: - OAuth2: @@ -517,7 +547,7 @@ paths: $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalServerError' - /bookings/{bookingId}/payment: + '/bookings/{bookingId}/payment': parameters: - name: bookingId in: path @@ -533,7 +563,9 @@ paths: value: 1725ff48-ab45-4bb5-9d02-88745177dedb post: summary: Pay for a Booking - description: A payment is an attempt to pay for the booking, which will confirm the booking for the user and enable them to get their tickets. + description: >- + A payment is an attempt to pay for the booking, which will confirm the + booking for the user and enable them to get their tickets. operationId: create-booking-payment tags: - Payments @@ -608,7 +640,8 @@ paths: address_post_code: N12 9XX status: succeeded links: - booking: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb/payment + booking: >- + https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb/payment Bank: summary: Bank Account Payment value: @@ -625,7 +658,8 @@ paths: country: gb status: succeeded links: - booking: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + booking: >- + https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb '400': $ref: '#/components/responses/BadRequest' '401': @@ -641,8 +675,9 @@ webhooks: post: operationId: new-booking summary: New Booking - description: | - Subscribe to new bookings being created, to update integrations for your users. Related data is available via the links provided in the request. + description: > + Subscribe to new bookings being created, to update integrations for your + users. Related data is available via the links provided in the request. tags: - Bookings requestBody: @@ -666,10 +701,13 @@ webhooks: has_bicycle: true has_dog: true links: - self: https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + self: >- + https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb responses: '200': - description: Return a 200 status to indicate that the data was received successfully. + description: >- + Return a 200 status to indicate that the data was received + successfully. components: parameters: page: @@ -699,8 +737,8 @@ components: description: OAuth 2.0 authorization code following RFC8725 best practices. flows: authorizationCode: - authorizationUrl: https://example.com/oauth/authorize - tokenUrl: https://example.com/oauth/token + authorizationUrl: 'https://example.com/oauth/authorize' + tokenUrl: 'https://example.com/oauth/token' scopes: read: Read access write: Write access @@ -732,8 +770,8 @@ components: type: string description: The address of the station. examples: - - Invalidenstraße 10557 Berlin, Germany - - 18 Rue de Dunkerque 75010 Paris, France + - 'Invalidenstraße 10557 Berlin, Germany' + - '18 Rue de Dunkerque 75010 Paris, France' country_code: type: string description: The country code of the station. @@ -743,7 +781,9 @@ components: - FR timezone: type: string - description: The timezone of the station in the [IANA Time Zone Database format](https://www.iana.org/time-zones). + description: >- + The timezone of the station in the [IANA Time Zone Database + format](https://www.iana.org/time-zones). examples: - Europe/Berlin - Europe/Paris @@ -766,26 +806,30 @@ components: type: object xml: name: problem - namespace: urn:ietf:rfc:7807 + namespace: 'urn:ietf:rfc:7807' properties: type: type: string description: A URI reference that identifies the problem type examples: - - https://example.com/probs/out-of-credit + - 'https://example.com/probs/out-of-credit' title: type: string - description: A short, human-readable summary of the problem type + description: 'A short, human-readable summary of the problem type' examples: - You do not have enough credit. detail: type: string - description: A human-readable explanation specific to this occurrence of the problem + description: >- + A human-readable explanation specific to this occurrence of the + problem examples: - - Your current balance is 30, but that costs 50. + - 'Your current balance is 30, but that costs 50.' instance: type: string - description: A URI reference that identifies the specific occurrence of the problem + description: >- + A URI reference that identifies the specific occurrence of the + problem examples: - /account/12345/msgs/abc status: @@ -875,7 +919,9 @@ components: type: boolean description: Indicates whether the passenger has a dog. Wrapper-Collection: - description: This is a generic request/response wrapper which contains both data and links which serve as hypermedia controls (HATEOAS). + description: >- + This is a generic request/response wrapper which contains both data and + links which serve as hypermedia controls (HATEOAS). type: object properties: data: @@ -893,18 +939,26 @@ components: type: object properties: id: - description: Unique identifier for the payment. This will be a unique identifier for the payment, and is used to reference the payment in other objects. + description: >- + Unique identifier for the payment. This will be a unique identifier + for the payment, and is used to reference the payment in other + objects. type: string format: uuid readOnly: true amount: - description: Amount intended to be collected by this payment. A positive decimal figure describing the amount to be collected. + description: >- + Amount intended to be collected by this payment. A positive decimal + figure describing the amount to be collected. type: number exclusiveMinimum: 0 examples: - 49.99 currency: - description: Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. + description: >- + Three-letter [ISO currency + code](https://www.iso.org/iso-4217-currency-codes.html), in + lowercase. type: string enum: - bam @@ -917,7 +971,10 @@ components: - try source: unevaluatedProperties: false - description: The payment source to take the payment from. This can be a card or a bank account. Some of these properties will be hidden on read to protect PII leaking. + description: >- + The payment source to take the payment from. This can be a card or a + bank account. Some of these properties will be hidden on read to + protect PII leaking. oneOf: - title: Card description: A card (debit or credit) to take payment from. @@ -933,12 +990,16 @@ components: - Francis Bourgeois number: type: string - description: The card number, as a string without any separators. On read all but the last four digits will be masked for security. + description: >- + The card number, as a string without any separators. On read + all but the last four digits will be masked for security. examples: - '4242424242424242' cvc: type: integer - description: Card security code, 3 or 4 digits usually found on the back of the card. + description: >- + Card security code, 3 or 4 digits usually found on the back + of the card. minLength: 3 maxLength: 4 writeOnly: true @@ -975,7 +1036,9 @@ components: - exp_year - address_country - title: Bank Account - description: A bank account to take payment from. Must be able to make payments in the currency specified in the payment. + description: >- + A bank account to take payment from. Must be able to make + payments in the currency specified in the payment. type: object properties: object: @@ -985,16 +1048,22 @@ components: type: string number: type: string - description: The account number for the bank account, in string form. Must be a current account. + description: >- + The account number for the bank account, in string form. + Must be a current account. sort_code: type: string - description: The sort code for the bank account, in string form. Must be a six-digit number. + description: >- + The sort code for the bank account, in string form. Must be + a six-digit number. account_type: enum: - individual - company type: string - description: The type of entity that holds the account. This can be either `individual` or `company`. + description: >- + The type of entity that holds the account. This can be + either `individual` or `company`. bank_name: type: string description: The name of the bank associated with the routing number. @@ -1010,7 +1079,9 @@ components: - bank_name - country status: - description: The status of the payment, one of `pending`, `succeeded`, or `failed`. + description: >- + The status of the payment, one of `pending`, `succeeded`, or + `failed`. type: string enum: - pending @@ -1024,30 +1095,51 @@ components: type: string format: uri examples: - - https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb + - >- + https://api.example.com/bookings/1725ff48-ab45-4bb5-9d02-88745177dedb headers: Cache-Control: - description: "The Cache-Control header communicates directives for caching mechanisms in both requests and responses. \nIt is used to specify the caching directives in responses to prevent caches from storing sensitive information.\n" + description: > + The Cache-Control header communicates directives for caching mechanisms + in both requests and responses. + + It is used to specify the caching directives in responses to prevent + caches from storing sensitive information. schema: type: string - description: A comma-separated list of directives as defined in [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111.html). + description: >- + A comma-separated list of directives as defined in [RFC + 9111](https://www.rfc-editor.org/rfc/rfc9111.html). examples: - max-age=3600 - - max-age=604800, public + - 'max-age=604800, public' - no-store - no-cache - private RateLimit: - description: | - The RateLimit header communicates quota policies. It contains a `limit` to - convey the expiring limit, `remaining` to convey the remaining quota units, + description: > + The RateLimit header communicates quota policies. It contains a `limit` + to + + convey the expiring limit, `remaining` to convey the remaining quota + units, + and `reset` to convey the time window reset time. schema: type: string examples: - - limit=10, remaining=0, reset=10 + - 'limit=10, remaining=0, reset=10' Retry-After: - description: "The Retry-After header indicates how long the user agent should wait before making a follow-up request. \nThe value is in seconds and can be an integer or a date in the future. \nIf the value is an integer, it indicates the number of seconds to wait. \nIf the value is a date, it indicates the time at which the user agent should make a follow-up request. \n" + description: > + The Retry-After header indicates how long the user agent should wait + before making a follow-up request. + + The value is in seconds and can be an integer or a date in the future. + + If the value is an integer, it indicates the number of seconds to wait. + + If the value is a date, it indicates the time at which the user agent + should make a follow-up request. schema: type: string examples: @@ -1068,7 +1160,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/bad-request + type: 'https://example.com/errors/bad-request' title: Bad Request status: 400 detail: The request is invalid or missing required parameters. @@ -1076,7 +1168,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/bad-request + type: 'https://example.com/errors/bad-request' title: Bad Request status: 400 detail: The request is invalid or missing required parameters. @@ -1090,7 +1182,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/conflict + type: 'https://example.com/errors/conflict' title: Conflict status: 409 detail: There is a conflict with an existing resource. @@ -1098,7 +1190,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/conflict + type: 'https://example.com/errors/conflict' title: Conflict status: 409 detail: There is a conflict with an existing resource. @@ -1112,7 +1204,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/forbidden + type: 'https://example.com/errors/forbidden' title: Forbidden status: 403 detail: Access is forbidden with the provided credentials. @@ -1120,7 +1212,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/forbidden + type: 'https://example.com/errors/forbidden' title: Forbidden status: 403 detail: Access is forbidden with the provided credentials. @@ -1134,7 +1226,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/internal-server-error + type: 'https://example.com/errors/internal-server-error' title: Internal Server Error status: 500 detail: An unexpected error occurred. @@ -1142,7 +1234,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/internal-server-error + type: 'https://example.com/errors/internal-server-error' title: Internal Server Error status: 500 detail: An unexpected error occurred. @@ -1156,7 +1248,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/not-found + type: 'https://example.com/errors/not-found' title: Not Found status: 404 detail: The requested resource was not found. @@ -1164,7 +1256,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/not-found + type: 'https://example.com/errors/not-found' title: Not Found status: 404 detail: The requested resource was not found. @@ -1180,7 +1272,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/too-many-requests + type: 'https://example.com/errors/too-many-requests' title: Too Many Requests status: 429 detail: You have exceeded the rate limit. @@ -1188,7 +1280,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/too-many-requests + type: 'https://example.com/errors/too-many-requests' title: Too Many Requests status: 429 detail: You have exceeded the rate limit. @@ -1202,7 +1294,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/unauthorized + type: 'https://example.com/errors/unauthorized' title: Unauthorized status: 401 detail: You do not have the necessary permissions. @@ -1210,7 +1302,7 @@ components: schema: $ref: '#/components/schemas/Problem' example: - type: https://example.com/errors/unauthorized + type: 'https://example.com/errors/unauthorized' title: Unauthorized status: 401 detail: You do not have the necessary permissions. diff --git a/package.json b/package.json index 0e1c146..4e6e65a 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,10 @@ { + "dependencies": { + "bump-cli": "^2.8.4" + }, + "scripts": { + "microcks": "bump overlay openapi.yaml .microcks/overlays.yaml > .microcks/openapi.yaml" + }, "devDependencies": { "@apisyouwonthate/style-guide": "^1.5.0", "@stoplight/spectral-owasp-ruleset": "^1.4.3", From 795048a63e46b02ec6b527f8f251826547eb91c9 Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:06:21 +0000 Subject: [PATCH 12/13] disable linting on microcks openapi.yaml as it uses string examples --- .github/workflows/lint.yml | 5 +++-- .microcks/openapi.yaml | 1 + .microcks/overlays.yaml | 10 +++++----- .spectral.yaml | 5 ----- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0635da6..0fc436c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,8 +27,9 @@ jobs: fetch-depth: 0 - name: Super-linter - uses: super-linter/super-linter@v6.0.0 + uses: super-linter/super-linter/slim@v7.2.0 env: DEFAULT_BRANCH: main - VALIDATE_OPENAPI: true + FILTER_REGEX_EXCLUDE: ^.microcks/ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VALIDATE_OPENAPI: true diff --git a/.microcks/openapi.yaml b/.microcks/openapi.yaml index 5f5fbf6..9f21f9d 100644 --- a/.microcks/openapi.yaml +++ b/.microcks/openapi.yaml @@ -1306,3 +1306,4 @@ components: title: Unauthorized status: 401 detail: You do not have the necessary permissions. + diff --git a/.microcks/overlays.yaml b/.microcks/overlays.yaml index 07e9878..a22d114 100644 --- a/.microcks/overlays.yaml +++ b/.microcks/overlays.yaml @@ -49,7 +49,7 @@ actions: update: examples: trips: - value: b2e783e1-c824-4d63-b37a-d8d698862f1d + value: 'b2e783e1-c824-4d63-b37a-d8d698862f1d' - target: $.paths["/trips"].get.parameters[?(@.name=="destination")].example remove: true @@ -143,9 +143,9 @@ actions: update: examples: booking_1725ff48-ab45-4bb5-9d02-88745177dedb: - value: 1725ff48-ab45-4bb5-9d02-88745177dedb + value: '1725ff48-ab45-4bb5-9d02-88745177dedb' booking_bfc5af2c-f477-43c4-8bdf-a00bdb939d65: - value: bfc5af2c-f477-43c4-8bdf-a00bdb939d65 + value: 'bfc5af2c-f477-43c4-8bdf-a00bdb939d65' - target: $.paths["/bookings/{bookingId}"].get.responses["200"].content["application/json"]["example"] remove: true @@ -182,9 +182,9 @@ actions: update: examples: Card: - value: 1725ff48-ab45-4bb5-9d02-88745177dedb + value: '1725ff48-ab45-4bb5-9d02-88745177dedb' Bank: - value: 1725ff48-ab45-4bb5-9d02-88745177dedb + value: '1725ff48-ab45-4bb5-9d02-88745177dedb' - target: $.webhooks["newBooking"]["post"]["requestBody"].content["application/json"]["example"] remove: true diff --git a/.spectral.yaml b/.spectral.yaml index 274c9ef..719461d 100644 --- a/.spectral.yaml +++ b/.spectral.yaml @@ -2,13 +2,8 @@ extends: - "spectral:oas" # - "@stoplight/spectral-owasp-ruleset" - "@apisyouwonthate/style-guide" -- "@microcks/spectral-ruleset" - rules: # The API doesn't have these (yet?) api-home: off api-health: off - - # Microcks mocking with dynamic examples causes this rule to fail - oas3-valid-media-example: off From ea7e90b401413fbb0dda95583328486c24c67697 Mon Sep 17 00:00:00 2001 From: Phil Sturgeon <67381+philsturgeon@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:19:13 +0000 Subject: [PATCH 13/13] Regex... --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0fc436c..ad89126 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,6 +30,6 @@ jobs: uses: super-linter/super-linter/slim@v7.2.0 env: DEFAULT_BRANCH: main - FILTER_REGEX_EXCLUDE: ^.microcks/ + FILTER_REGEX_INCLUDE: ^openapi.yaml GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_OPENAPI: true