Skip to main content

uk_trains/
open_ldbws.rs

1/// Documentation: https://realtime.nationalrail.co.uk/OpenLDBWS/
2/// CRS codes: https://www.nationalrail.co.uk/stations/
3
4use yaserde::*;
5
6/// The endpoint to be posted to
7pub const SOAP_ENDPOINT: &str = "https://lite.realtime.nationalrail.co.uk/OpenLDBWS/ldb12.asmx";
8
9#[derive(Debug, Default, YaDeserialize, YaSerialize)]
10#[yaserde(
11    rename = "trainServices",
12    namespaces = {
13        "lt8" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/types"
14    },
15    prefix = "lt8"
16)]
17pub struct ServiceItemWithCallingPointsListWrapper {
18    #[yaserde(rename = "service", prefix = "lt8")]
19    pub services: Vec<ServiceItemWithCallingPoints>,
20}
21
22#[derive(Debug, Default, YaDeserialize, YaSerialize)]
23#[yaserde(
24    rename = "GetStationBoardResult",
25    namespaces = {
26        "r" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/",
27        "lt4" = "http://thalesgroup.com/RTTI/2015-11-27/ldb/types",
28        "lt8" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/types",
29    },
30    prefix = "r"
31)]
32pub struct StationBoardWithDetails {
33    #[yaserde(rename = "generatedAt", prefix = "lt4")]
34    pub generated_at: String,
35    #[yaserde(rename = "locationName", prefix = "lt4")]
36    pub location_name: String,
37    #[yaserde(rename = "trainServices", prefix = "lt8")]
38    pub train_services: ServiceItemWithCallingPointsListWrapper,
39}
40
41#[derive(Debug, Default, YaDeserialize, YaSerialize)]
42#[yaserde(
43    rename = "GetDepartureBoardResponse",
44    namespaces = {
45        "r" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/",
46    },
47    prefix = "r"
48)]
49pub struct GetDepBoardWithDetailsResponse {
50    #[yaserde(rename = "GetStationBoardResult", prefix = "r")]
51    pub station_board_result: StationBoardWithDetails,
52}
53
54#[derive(Debug, Default, YaDeserialize, YaSerialize)]
55#[yaserde(rename = "Body", namespaces = {"r" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/"})]
56pub struct SoapBodyGetDepBoardWithDetailsResponse {
57    #[yaserde(rename = "GetDepBoardWithDetailsResponse", prefix = "r")]
58    pub dep_board_with_details_response: GetDepBoardWithDetailsResponse,
59}
60
61#[derive(Debug, Default, YaDeserialize, YaSerialize)]
62#[yaserde(
63    rename = "Envelope",
64    namespaces = {
65      "s" = "http://schemas.xmlsoap.org/soap/envelope/",
66    },
67    prefix = "s"
68)]
69pub struct SoapEnvelopeGetDepBoardWithDetailsResponse {
70    #[yaserde(rename = "Body", prefix = "s")]
71    pub body: SoapBodyGetDepBoardWithDetailsResponse,
72}
73
74#[derive(Debug, Default, YaDeserialize, YaSerialize)]
75#[yaserde(
76    rename = "callingPoint",
77    namespaces = {
78        "lt8" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/types",
79    },
80    prefix = "lt8"
81)]
82pub struct CallingPoint {
83    #[yaserde(rename = "crs", prefix = "lt8")]
84    pub crs: String, // Can be ???
85    #[yaserde(rename = "st", prefix = "lt8")]
86    pub st: String,
87    #[yaserde(rename = "et", prefix = "lt8")]
88    pub et: Option<String>,
89    #[yaserde(rename = "at", prefix = "lt8")]
90    pub at: Option<String>,
91}
92
93#[derive(Debug, Default, YaDeserialize, YaSerialize)]
94#[yaserde(
95    rename = "callingPointList",
96    namespaces = {
97        "lt8" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/types",
98    },
99    prefix = "lt8"
100)]
101pub struct CallingPointGroup {
102    #[yaserde(rename = "callingPoint", prefix = "lt8")]
103    pub calling_point: Vec<CallingPoint>,
104}
105
106#[derive(Debug, Default, YaDeserialize, YaSerialize)]
107#[yaserde(
108    rename = "subsequentCallingPoints",
109    namespaces = {
110        "lt8" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/types",
111    },
112    prefix = "lt8",
113)]
114pub struct SubsequentCallingPointsListWrapper {
115    #[yaserde(rename = "callingPointList", prefix = "lt8")]
116    pub calling_points_list: Vec<CallingPointGroup>, // There can be multiple branches of calling points
117}
118
119#[derive(Debug, Default, YaDeserialize, YaSerialize)]
120#[yaserde(
121    rename = "service",
122    namespaces = {
123        "lt4" = "http://thalesgroup.com/RTTI/2015-11-27/ldb/types",
124        "lt5" = "http://thalesgroup.com/RTTI/2016-02-16/ldb/types",
125        "lt8" = "http://thalesgroup.com/RTTI/2021-11-01/ldb/types",
126    },
127    prefix = "lt8"
128)]
129pub struct ServiceItemWithCallingPoints {
130    #[yaserde(rename = "sta", prefix = "lt4")]
131    pub sta: Option<String>,
132    #[yaserde(rename = "eta", prefix = "lt4")]
133    pub eta: Option<String>,
134    #[yaserde(rename = "std", prefix = "lt4")]
135    pub std: Option<String>,
136    #[yaserde(rename = "etd", prefix = "lt4")]
137    pub etd: Option<String>,
138    #[yaserde(rename = "platform", prefix = "lt4")]
139    pub platform: Option<String>,
140    #[yaserde(rename = "operator", prefix = "lt4")]
141    pub operator: String,
142    #[yaserde(rename = "operatorCode", prefix = "lt4")]
143    pub operator_code: String,
144    #[yaserde(rename = "serviceType", prefix = "lt4")]
145    pub service_type: String,
146    #[yaserde(rename = "length", prefix = "lt4")]
147    pub length: Option<u32>,
148    #[yaserde(rename = "isCancelled", prefix = "lt4")]
149    pub is_cancelled: Option<bool>,
150    #[yaserde(rename = "cancelReason", prefix = "lt4")]
151    pub cancel_reason: Option<String>,
152    #[yaserde(rename = "delayReason", prefix = "lt4")]
153    pub delay_reason: Option<String>,
154    #[yaserde(rename = "serviceID", prefix = "lt4")]
155    pub service_id: String,
156    // #[yaserde(rename = "origin", prefix = "lt5")]
157    // pub origin: Origin,
158    #[yaserde(rename = "destination", prefix = "lt5")]
159    pub destination: Destination,
160    #[yaserde(rename = "subsequentCallingPoints", prefix = "lt8")]
161    pub subsequent_calling_points: SubsequentCallingPointsListWrapper,
162}
163
164#[derive(Debug, Default, YaDeserialize, YaSerialize)]
165#[yaserde(
166    rename = "location",
167    namespaces = {
168        "lt4" = "http://thalesgroup.com/RTTI/2015-11-27/ldb/types"
169    },
170    prefix = "lt4"
171)]
172pub struct Location {
173    #[yaserde(rename = "locationName", prefix = "lt4")]
174    pub location_name: String,
175}
176
177#[derive(Debug, Default, YaDeserialize, YaSerialize)]
178#[yaserde(
179    rename = "destination",
180    namespaces = {
181        "lt4" = "http://thalesgroup.com/RTTI/2015-11-27/ldb/types",
182        "lt5" = "http://thalesgroup.com/RTTI/2016-02-16/ldb/types"
183    },
184    prefix = "lt5"
185)]
186pub struct Destination {
187    #[yaserde(rename = "location", prefix = "lt4")]
188    pub locations: Vec<Location>,
189}
190
191pub trait RequestType {
192    fn id() -> &'static str;
193    fn year_version() -> &'static str;
194    fn values(&self) -> Vec<(&'static str, String)>;
195
196    /// Generate the body to be used for the HTTP post request, get a token from https://realtime.nationalrail.co.uk/OpenLDBWSRegistration/
197    fn generate_request_body(&self, ldb_token: &str) -> String {
198        let mut request_tag = format!("        <ldb:{}Request>\n", Self::id());
199        for value in self.values() {
200            let value_name = value.0;
201            let value_val = value.1;
202            request_tag += &format!("            <ldb:{value_name}>{value_val}</ldb:{value_name}>\n");
203        }
204        request_tag += &format!("        </ldb:{}Request>", Self::id());
205
206        return format!(
207            r#"
208<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:typ="http://thalesgroup.com/RTTI/2013-11-28/Token/types" xmlns:ldb="http://thalesgroup.com/RTTI/2021-11-01/ldb/">
209    <soap:Header>
210        <typ:AccessToken>
211            <typ:TokenValue>{}</typ:TokenValue>
212        </typ:AccessToken>
213    </soap:Header>
214    <soap:Body>
215{}
216    </soap:Body>
217</soap:Envelope>"#,
218            ldb_token, request_tag
219        );
220    }
221
222    /// Generate header for HTTP request with key "SOAPAction"
223    fn generate_soap_action_header(&self) -> String {
224        format!("http://thalesgroup.com/RTTI/{}/ldb/{}", Self::year_version(), Self::id())
225    }
226}
227
228pub struct GetDepBoardWithDetailsRequest {
229    pub num_rows: i32,
230    pub crs: String,
231    pub filter_crs: Option<String>,
232    pub filter_type: Option<String>,
233    pub time_offset: Option<i32>,
234    pub time_window: Option<i32>,
235}
236
237impl GetDepBoardWithDetailsRequest {
238    /// Fails if yaserde fails to parse raw response
239    pub fn get_station_board_result(response_raw: &str) -> Result<StationBoardWithDetails, String> {
240        let envelope: SoapEnvelopeGetDepBoardWithDetailsResponse = de::from_str(response_raw)?;
241        let station_board_result = envelope
242            .body
243            .dep_board_with_details_response
244            .station_board_result;
245        Ok(station_board_result)
246    }
247}
248
249impl RequestType for GetDepBoardWithDetailsRequest {
250    fn id() -> &'static str {
251        "GetDepBoardWithDetails"
252    }
253
254    fn year_version() -> &'static str {
255        "2015-05-14"
256    }
257
258    fn values(&self) -> Vec<(&'static str, String)> {
259        let mut v = vec![
260            ("numRows", self.num_rows.to_string()),
261            ("crs", self.crs.clone()),
262        ];
263
264        if let Some(ref filter_crs) = self.filter_crs {
265            v.push(("filterCrs", filter_crs.clone()));
266        }
267
268        if let Some(ref filter_type) = self.filter_type {
269            v.push(("filterType", filter_type.clone()));
270        }
271
272        if let Some(time_offset) = self.time_offset {
273            v.push(("timeOffset", time_offset.to_string()));
274        }
275
276        if let Some(time_window) = self.time_window {
277            v.push(("timeWindow", time_window.to_string()));
278        }
279
280        v
281    }
282}