diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0635da6..ad89126 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_INCLUDE: ^openapi.yaml GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VALIDATE_OPENAPI: true diff --git a/.microcks/openapi.yaml b/.microcks/openapi.yaml new file mode 100644 index 0000000..9f21f9d --- /dev/null +++ b/.microcks/openapi.yaml @@ -0,0 +1,1309 @@ +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 + + 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: + 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: + 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 + 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. + + 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). + 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. + + 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: + 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..a22d114 --- /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/.spectral.yaml b/.spectral.yaml index f3ab72e..719461d 100644 --- a/.spectral.yaml +++ b/.spectral.yaml @@ -1,12 +1,9 @@ extends: -# - "spectral:oas" -- "spectral:arazzo" +- "spectral:oas" # - "@stoplight/spectral-owasp-ruleset" -# - "@apisyouwonthate/style-guide"" +- "@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 +rules: + # The API doesn't have these (yet?) + api-home: off + api-health: off diff --git a/package.json b/package.json index 6a1ee9f..4e6e65a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,13 @@ { + "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" + "@stoplight/spectral-owasp-ruleset": "^1.4.3", + "@microcks/spectral-ruleset": "^0.0.5" } }