diff --git a/.github/workflows/unix.yml b/.github/workflows/unix.yml index d93969e0e..2bc0b699e 100644 --- a/.github/workflows/unix.yml +++ b/.github/workflows/unix.yml @@ -203,13 +203,14 @@ jobs: mkdir motis/web mv ui/web/{external_lib,img,js,style} motis/web/ mv ui/web/{*.html,*.js,*.ico} motis/web/ + mv ui/web/openapi motis/web/ mv ui/rsl/dist motis/web/rsl mv build/motis motis/ cp -r deps/osrm-backend/profiles motis/osrm-profiles cp -r deps/ppr/profiles motis/ppr-profiles cp -r deps/tiles/profile motis/tiles-profiles - cp docs/generated/openapi-3.1/openapi.yaml motis/web/openapi.yaml - cp docs/generated/openapi-3.0/openapi.yaml motis/web/openapi-3.0.yaml + cp docs/generated/openapi-3.1/openapi.yaml motis/web/openapi/openapi.yaml + cp docs/generated/openapi-3.0/openapi.yaml motis/web/openapi/openapi-3.0.yaml tar cjf motis-${{ matrix.config.preset }}.tar.bz2 motis - name: Upload Distribution diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 09269232c..923ce0dfe 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -148,13 +148,14 @@ jobs: mkdir web Copy-Item .\ui\web\external_lib,.\ui\web\img,.\ui\web\js,.\ui\web\style .\web\ -Recurse Copy-Item .\ui\web\*.html,.\ui\web\*.js,.\ui\web\*.ico .\web\ + Copy-Item .\ui\web\openapi .\web\ -Recurse Copy-Item .\ui\rsl\dist .\web\rsl -Recurse - name: Move API Docs if: matrix.config.mode == 'Release' run: | - Copy-Item .\docs\generated\openapi-3.1\openapi.yaml .\web\openapi.yaml - Copy-Item .\docs\generated\openapi-3.0\openapi.yaml .\web\openapi-3.0.yaml + Copy-Item .\docs\generated\openapi-3.1\openapi.yaml .\web\openapi\openapi.yaml + Copy-Item .\docs\generated\openapi-3.0\openapi.yaml .\web\openapi\openapi-3.0.yaml - name: Create Distribution if: matrix.config.mode == 'Release' diff --git a/docs/api/paths.yaml b/docs/api/paths.yaml index 1644d098e..47432105b 100644 --- a/docs/api/paths.yaml +++ b/docs/api/paths.yaml @@ -45,7 +45,7 @@ #/lookup/meta_station_batch: /lookup/ribasis: - summary: Returns a trip in RI Basis format + summary: Retrieve a trip in RI Basis format description: | Returns the current state of a trip in RI Basis format. Also included in the response are all associated trips (merged and through services). @@ -63,7 +63,7 @@ description: Information about the requested trip and associated trips /lookup/schedule_info: - summary: Returns information about the currently loaded schedule + summary: Currently loaded schedule tags: - lookup output: @@ -81,7 +81,7 @@ #/parking/edges: /parking/geo: - summary: Request parking lots in an area + summary: Parking lots in an area tags: - parking input: motis.parking.ParkingGeoRequest @@ -91,7 +91,7 @@ /parking/lookup: - summary: Request information about a parking lot by ID + summary: Lookup a parking lot by ID tags: - parking input: motis.parking.ParkingLookupRequest @@ -136,7 +136,7 @@ description: Universe was destroyed (or marked for destruction if still in use). /paxmon/filter/groups: - summary: Request a list of passenger groups with filter and sort options + summary: List passenger groups with filter and sort options tags: - rsl input: motis.paxmon.PaxMonFilterGroupsRequest @@ -145,7 +145,7 @@ description: A list of passenger groups matching the request /paxmon/filter_trips: - summary: Request a list of trips with filter and sort options + summary: List trips with filter and sort options description: | Returns a list of trips tracked by paxmon (i.e. at least one passenger group uses or used the trip). This API offers various filter and sort @@ -206,7 +206,7 @@ description: Information about the newly created paxmon universe /paxmon/get_groups: - summary: Get information about passenger groups + summary: Information about passenger groups tags: - rsl input: motis.paxmon.PaxMonGetGroupsRequest @@ -215,7 +215,7 @@ description: Information about the requested passenger groups /paxmon/get_interchanges: - summary: Get a list of monitored interchanges at a station + summary: List monitored interchanges at a station tags: - rsl input: motis.paxmon.PaxMonGetInterchangesRequest @@ -224,7 +224,7 @@ description: Information about the requested interchanges /paxmon/group_statistics: - summary: Get various passenger group statistics + summary: Passenger group statistics tags: - rsl input: motis.paxmon.PaxMonGroupStatisticsRequest @@ -233,7 +233,7 @@ description: Statistics for the requested universe /paxmon/groups_in_trip: - summary: Get a list of passenger groups in a trip + summary: List passenger groups in a trip tags: - rsl input: motis.paxmon.PaxMonGetGroupsInTripRequest @@ -280,7 +280,7 @@ description: Information about the changes /paxmon/status: - summary: Request basic information about a paxmon universe + summary: Information about a paxmon universe tags: - rsl input: motis.paxmon.PaxMonStatusRequest @@ -289,7 +289,7 @@ description: Information about the requested paxmon universe /paxmon/trip_load_info: - summary: Get load information for a trip + summary: Load information for a trip tags: - rsl input: motis.paxmon.PaxMonGetTripLoadInfosRequest @@ -298,7 +298,7 @@ description: Load information for the requested trip /paxmon/universes: - summary: Request a list of paxmon universes + summary: List paxmon universes description: > Returns a list of all universes that currently exist on the server, as well as the current multiverse ID. @@ -309,7 +309,7 @@ description: A list of paxmon universes /ppr/profiles: - summary: Get list of available pedestrian routing profiles + summary: List available pedestrian routing profiles tags: - ppr output: @@ -326,7 +326,7 @@ description: Pedestrian routes /railviz/get_station: - summary: Request a list of trips arriving and departing at a station + summary: List trips arriving and departing at a station tags: - lookup input: motis.railviz.RailVizStationRequest @@ -362,7 +362,7 @@ description: Routing response /trip_to_connection: - summary: Get information about the stops of a trip + summary: Retrieve the stops of a trip tags: - lookup input: motis.TripId diff --git a/docs/api/schemas/motis.yaml b/docs/api/schemas/motis.yaml index 16cf17cbd..6c1d8af04 100644 --- a/docs/api/schemas/motis.yaml +++ b/docs/api/schemas/motis.yaml @@ -29,17 +29,17 @@ TripId: description: TODO fields: station_id: - description: TODO + description: The station ID of the first scheduled departure. train_nr: - description: TODO + description: The unique train number at the first scheduled departure. time: - description: TODO + description: The scheduled first departure time (unix timestamp). target_station_id: - description: TODO + description: The scheduled final destination of the trip. target_time: - description: TODO + description: The scheduled arrival time at the destination (unix timestamp). line_id: - description: TODO + description: The line name. TimestampReason: description: TODO ConnectionStatus: @@ -281,6 +281,10 @@ MotisError: description: TODO reason: description: TODO + examples: + - error_code: 3 + category: motis::module + reason: "module: target not found" MotisSuccess: description: TODO fields: {} diff --git a/docs/api/schemas/motis/guesser.yaml b/docs/api/schemas/motis/guesser.yaml index 8c29f3ee2..d977cecd2 100644 --- a/docs/api/schemas/motis/guesser.yaml +++ b/docs/api/schemas/motis/guesser.yaml @@ -3,8 +3,12 @@ StationGuesserRequest: fields: guess_count: description: TODO + examples: + - 10 input: description: TODO + examples: + - Frankfurt StationGuesserResponse: description: TODO fields: diff --git a/docs/api/schemas/motis/lookup.yaml b/docs/api/schemas/motis/lookup.yaml index 72cfa921f..071eac728 100644 --- a/docs/api/schemas/motis/lookup.yaml +++ b/docs/api/schemas/motis/lookup.yaml @@ -65,21 +65,26 @@ LookupRiBasisRequest: description: TODO fields: trip_id: - description: TODO + description: Trip ID of the trip to retrieve. schedule: - description: TODO + description: | + The ID of the schedule to use. + + Use `0` for the default schedule. RiBasisTrip: description: TODO fields: trip_id: - description: TODO + description: The trip ID. fahrt: - description: TODO + description: The full trip in RiBasisFahrt format. LookupRiBasisResponse: description: TODO fields: trips: - description: TODO + description: > + The requested trip and all associated trips (merged and through + services). LookupScheduleInfoResponse: description: Information about the loaded schedule fields: diff --git a/docs/api/schemas/motis/paxforecast.yaml b/docs/api/schemas/motis/paxforecast.yaml index 857229ee8..d2b4c0b81 100644 --- a/docs/api/schemas/motis/paxforecast.yaml +++ b/docs/api/schemas/motis/paxforecast.yaml @@ -42,7 +42,8 @@ MeasureRecipients: LoadLevel: description: TODO TripLoadInfoMeasure: - description: TODO + description: > + This measure type is deprecated. Use `TripLoadRecommendationMeasure` instead. fields: recipients: description: TODO @@ -53,7 +54,8 @@ TripLoadInfoMeasure: level: description: TODO TripRecommendationMeasure: - description: TODO + description: > + This measure type is deprecated. Use `TripLoadRecommendationMeasure` instead. fields: recipients: description: TODO @@ -73,12 +75,23 @@ TripWithLoadLevel: level: description: TODO TripLoadRecommendationMeasure: - description: TODO + description: | + Simulate an announcement that a trip is overcrowded and recommend + alternative trips with load information. + + This simulates an announcement of the form: + - Passengers travelling to `planned_destinations` + - Trip(s) `full_trips` are overcrowded (can't be used) + - Recommended alternatives: `recommended_trips` + + The announcement is made at `time` in the trips/at the stations + specified in `recipients`. fields: recipients: description: TODO time: - description: TODO + description: > + Time at which the announcement is made (unix timestamp). planned_destinations: description: TODO full_trips: @@ -86,16 +99,32 @@ TripLoadRecommendationMeasure: recommended_trips: description: TODO RtUpdateMeasure: - description: TODO + description: | + Simulate a real-time update. + + Real-time updates can be provided in RIBasis or RISML formats. + Set `type` to the format and supply the complete real-time update + in the corresponding format as a string in the `content` field. + + Passengers whose journey breaks because of the real-time update + are automatically rerouted and do not need to be specified via + `recipients`. If additional passengers should be made aware of the + update and be allowed to change their route (e.g. because an + additional train was added), they can be listed in `recipients`, + otherwise `recipients` can be empty for this measure type. fields: recipients: description: TODO time: - description: TODO + description: | + Time at which the measure takes effect (unix timestamp). + + This must match the creation/release time of the given real-time update + (e.g. `meta.created` for RI Basis messages). type: - description: TODO + description: The format of the real-time message given in `content`. content: - description: TODO + description: A real-time message in the format specified by `type`. Measure: description: TODO MeasureWrapper: @@ -109,7 +138,23 @@ PaxForecastApplyMeasuresRequest: universe: description: Paxmon universe ID measures: - description: A list of measures to simulate + description: | + A list of measures to simulate + + All measures have the following shared properties: + + - `time`: Time at which the measure takes effect and passengers are made + aware of it. Note that the time is not forwarded to this point during the + simulation, i.e. no real-time updates (other than those specified as rt + update measures) are applied during the simulation. If a call to + `/paxforecast/apply_measures` contains measures with different timestamps, + a simulation is run for each unique timestamp. All measures with the same + timestamp are applied in one step. + - `recipients`: Select a subset of passengers that is made aware of the + measure (e.g. using an announcement) + - `trips`: Select passengers that are in one of the listed trips at `time` + - `stations`: Select passengers that are at one of the listed stations at + `time` (not in a trip) replace_existing: description: | If set to true, only simulate the measures given in this API call. @@ -158,37 +203,45 @@ PaxForecastApplyMeasuresStatistics: description: TODO fields: measure_time_points: - description: TODO + description: The number of different timestamps in the given measures. total_measures_applied: - description: TODO + description: The number of measures applied. total_affected_groups: - description: TODO + description: | + Number of passenger groups reached via the `recipients` in the + given measures. + + Note: This does not include passenger groups affected by real-time + updates (e.g. because an rt update broke their journey). total_alternative_routings: - description: TODO + description: The number of alternative routing requests performed. total_alternatives_found: - description: TODO + description: The total number of alternative routes found. group_routes_broken: - description: TODO + description: > + The total number of passenger group routes broken by the measures. group_routes_with_major_delay: - description: TODO + description: > + The total number of passenger groups with a major delay that were + affected by the measures. t_rt_updates: - description: TODO + description: Time used to apply real-time updates (ms). t_get_affected_groups: - description: TODO + description: Time used to determine affected groups (ms). t_find_alternatives: - description: TODO + description: Time used to find alternative routes (ms). t_add_alternatives_to_graph: - description: TODO + description: Time used to add alternatives to paxmon (ms). t_behavior_simulation: - description: TODO + description: Time used for passenger behavior simulation (ms). t_update_groups: - description: TODO + description: Time used to update passenger groups (ms). t_update_tracker: - description: TODO + description: Time used for tracking changes (ms). PaxForecastApplyMeasuresResponse: description: TODO fields: stats: - description: TODO + description: Various statistics updates: - description: TODO + description: Changes made to trips and passenger groups diff --git a/docs/api/schemas/motis/paxmon.yaml b/docs/api/schemas/motis/paxmon.yaml index 8d58b4d88..9a1968d55 100644 --- a/docs/api/schemas/motis/paxmon.yaml +++ b/docs/api/schemas/motis/paxmon.yaml @@ -1272,44 +1272,82 @@ PaxMonCriticalTripInfo: description: TODO fields: critical_sections: - description: TODO + description: > + The number of critical sections (number of passengers > capacity). max_excess_pax: - description: TODO + description: > + The number of passengers over capacity for the most overcrowded + section of the trip. cumulative_excess_pax: - description: TODO + description: > + The sum of passengers over capacity over all sections of the trip. + If passengers use multiple sections of the trip, they are counted + multiple times (once for each section they use). PaxMonUpdatedTrip: description: TODO fields: tsi: description: TODO rerouted: - description: TODO + description: | + Whether the stops of the trip have changed. + + If `false`, `before_edges` and `after_edges` have the same number of + entries and can be compared. + + If `true`, no attempt is made to match sections and some of the other + values are either unavailable or guesses. newly_critical_sections: - description: TODO + description: | + The number of critical sections that weren't critical before. + + If the trip was rerouted, this value is a guess based on the + total number of critical sections before and after the changes. no_longer_critical_sections: - description: TODO + description: | + The number of sections that were critical before but are no longer + critical. + + If the trip was rerouted, this value is a guess based on the + total number of critical sections before and after the changes. max_pax_increase: - description: TODO + description: | + The largest increase in the number of passengers among the trip + sections. + + Not available if the trip was rerouted. max_pax_decrease: - description: TODO + description: | + The largest decrease in the number of passengers among the trip + sections. + + Not available if the trip was rerouted. critical_info_before: - description: TODO + description: Information about critical sections before the changes. critical_info_after: - description: TODO + description: Information about critical sections after the changes. updated_group_routes: - description: TODO + description: Passenger group routes using this trip that were updated. before_edges: - description: TODO + description: | + Load information for all trip sections before the changes. + + Only set if `include_before_trip_load_info` is set to `true` + in the request, otherwise an empty array. after_edges: - description: TODO + description: | + Load information for all trip sections after the changes. + + Only set if `include_after_trip_load_info` is set to `true` + in the request, otherwise an empty array. PaxMonTrackedUpdates: description: TODO fields: updated_group_route_count: - description: TODO + description: The number of passenger group routes that were updated. updated_trip_count: - description: TODO + description: The number of trips that were updated. updated_trips: - description: TODO + description: Information about the updated trips updated_group_routes: - description: TODO + description: Information about the updated group routes diff --git a/tools/protocol/README.md b/tools/protocol/README.md index d54dde660..d3aa089c3 100644 --- a/tools/protocol/README.md +++ b/tools/protocol/README.md @@ -90,6 +90,8 @@ Configuration: - `base-uri`: The JSON Schema Base URI - `ids` (boolean): Include `$id` for schema types - `info`: The info block for the OpenAPI file (must include at least `title` and `version`) +- `externalDocs` (optional): The externalDocs block for the OpenAPI file +- `servers` (optional): The servers block for the OpenAPI file #### OpenAPI 3.0 Limitations diff --git a/tools/protocol/protocol.config.yaml b/tools/protocol/protocol.config.yaml index 80126eb09..f78c14610 100644 --- a/tools/protocol/protocol.config.yaml +++ b/tools/protocol/protocol.config.yaml @@ -22,6 +22,18 @@ output: info: &openapi-info title: MOTIS API version: 0.0.0 + contact: + name: MOTIS Project + url: https://github.com/motis-project + license: + name: MIT + url: https://raw.githubusercontent.com/motis-project/motis/master/LICENSE + externalDocs: &openapi-externalDocs + description: MOTIS Project + url: https://motis-project.de/ + servers: &openapi-servers + - url: / + description: Local MOTIS server exclude: *public-exclude openapi-3.0: format: openapi @@ -30,6 +42,8 @@ output: base-uri: *base-uri ids: false info: *openapi-info + externalDocs: *openapi-externalDocs + servers: *openapi-servers exclude: *public-exclude markdown: format: markdown diff --git a/tools/protocol/src/doc/inout.ts b/tools/protocol/src/doc/inout.ts index 293bf89e2..51dbe52c1 100644 --- a/tools/protocol/src/doc/inout.ts +++ b/tools/protocol/src/doc/inout.ts @@ -192,6 +192,7 @@ function readPaths(ctx: DocContext) { tags: props.tags ?? [], input: props.input, output: undefined, + operationId: props.operationId, }; if (dp.input) { if (!ctx.schema.types.has(dp.input)) { diff --git a/tools/protocol/src/doc/types.ts b/tools/protocol/src/doc/types.ts index e535b3bce..cdf06c96d 100644 --- a/tools/protocol/src/doc/types.ts +++ b/tools/protocol/src/doc/types.ts @@ -21,6 +21,7 @@ export interface DocPath { tags: string[]; input?: string | undefined; output?: DocResponse | undefined; + operationId?: string | undefined; } export interface DocResponse { diff --git a/tools/protocol/src/output/json-schema/output.ts b/tools/protocol/src/output/json-schema/output.ts index 28c9613af..9925d86c3 100644 --- a/tools/protocol/src/output/json-schema/output.ts +++ b/tools/protocol/src/output/json-schema/output.ts @@ -202,9 +202,13 @@ function addTableProperties(ctx: JSContext, type: TableType, js: JSONSchema) { const fqtn = value.typeRef.resolvedFqtn; const tag = fqtn[fqtn.length - 1]; unionCases.push({ - if: { properties: { [tagName]: { const: tag } } }, + if: { + properties: { [tagName]: { type: "string", const: tag } }, + }, then: { - properties: { [field.name]: { $ref: ctx.getRefUrl(fqtn) } }, + properties: { + [field.name]: { $ref: ctx.getRefUrl(fqtn) }, + }, }, }); } diff --git a/tools/protocol/src/output/openapi/output.ts b/tools/protocol/src/output/openapi/output.ts index 9a2fb2273..7d4c0aed3 100644 --- a/tools/protocol/src/output/openapi/output.ts +++ b/tools/protocol/src/output/openapi/output.ts @@ -53,7 +53,7 @@ export function writeOpenAPIOutput( getRefUrl, false, true, - openApiVersion === "3.1.0" + false ); const jsonSchema = getJSONSchemaTypes(jsCtx); @@ -73,10 +73,16 @@ export function writeOpenAPIOutput( yd.contents = yd.createNode({}); yd.set("openapi", ctx.openApiVersion); - for (const key in config.info) { - yd.setIn(["info", key], config.info[key]); + function copyBlock(key: string) { + if (config[key]) { + yd.set(key, config[key]); + } } + copyBlock("info"); + copyBlock("externalDocs"); + copyBlock("servers"); + writeTags(ctx); writePaths(ctx); @@ -119,12 +125,56 @@ function writeTags(ctx: OpenApiContext) { } } +function writeResponse( + ctx: OpenApiContext, + oaResponses: YAMLMap, + code: string, + fqtn: string, + description: string +) { + const resType = fqtn.split("."); + const resTypeName = resType[resType.length - 1]; + const oaResponse = createMap(ctx.yd, oaResponses, [code]); + oaResponse.set("description", description); + const oaResponseSchema = createMap(ctx.yd, oaResponse, [ + "content", + "application/json", + "schema", + ]); + oaResponseSchema.set("type", "object"); + oaResponseSchema.set("required", ["content_type", "content"]); + oaResponseSchema.set("properties", { + destination: { + type: "object", + required: ["target"], + properties: { + target: { type: "string", enum: [""] }, + type: { type: "string", enum: ["Module"] }, + }, + }, + content_type: { + type: "string", + enum: [resTypeName], + }, + content: { + $ref: getRefUrl(resType), + }, + id: { type: "integer", format: "int32" }, + }); +} + function writePaths(ctx: OpenApiContext) { const oaPaths = createMap(ctx.yd, ctx.yd, ["paths"]); for (const path of ctx.doc.paths) { const oaPath = createMap(ctx.yd, oaPaths, [path.path]); const post = !!path.input; const oaOperation = createMap(ctx.yd, oaPath, [post ? "post" : "get"]); + const operationId = + path.operationId ?? + path.path + .substring(1) + .replaceAll(/[/_]+(.)/g, (_, p1) => p1.toUpperCase()); + oaOperation.set("operationId", operationId); if (path.summary) { oaOperation.set("summary", path.summary); } @@ -173,37 +223,16 @@ function writePaths(ctx: OpenApiContext) { } const oaResponses = createMap(ctx.yd, oaOperation, ["responses"]); - const oaResponse200 = createMap(ctx.yd, oaResponses, ["200"]); - const resFqtn = path.output?.type ?? "motis.MotisNoMessage"; - const resType = resFqtn.split("."); - const resTypeName = resType[resType.length - 1]; - const resDescription = path.output?.description ?? "Empty response"; - oaResponse200.set("description", resDescription); - const oaResponseSchema = createMap(ctx.yd, oaResponse200, [ - "content", - "application/json", - "schema", - ]); - oaResponseSchema.set("type", "object"); - oaResponseSchema.set("required", ["content_type", "content"]); - oaResponseSchema.set("properties", { - destination: { - type: "object", - required: ["target"], - properties: { - target: { type: "string", enum: [""] }, - type: { type: "string", enum: ["Module"] }, - }, - }, - content_type: { - type: "string", - enum: [resTypeName], - }, - content: { - $ref: getRefUrl(resType), - }, - id: { type: "integer", format: "int32" }, - }); + + writeResponse( + ctx, + oaResponses, + "200", + path.output?.type ?? "motis.MotisNoMessage", + path.output?.description ?? "Empty response" + ); + + writeResponse(ctx, oaResponses, "500", "motis.MotisError", "Error"); } } @@ -274,7 +303,14 @@ function writeSchema( for (const key in jsProps) { const jsProp = jsProps[key]; const oaProp = createMap(ctx.yd, oaProps, [key]); - writeSchema(ctx, oaProp, jsProp, undefined, typeDoc?.fields?.get(key)); + let fieldDoc = typeDoc?.fields?.get(key); + if (!fieldDoc && key.endsWith("_type")) { + fieldDoc = { + name: key, + description: `Type of the \`${key.replace(/_type$/, "")}\` field`, + }; + } + writeSchema(ctx, oaProp, jsProp, undefined, fieldDoc); } } diff --git a/ui/web/openapi/index.html b/ui/web/openapi/index.html new file mode 100644 index 000000000..e8de1421b --- /dev/null +++ b/ui/web/openapi/index.html @@ -0,0 +1,19 @@ + + +
+ + +