diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 58ed0c8..6952f86 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,13 +10,13 @@ jobs: rust: [stable, nightly] steps: - - uses: rui314/setup-mold@v1 - - uses: hecrj/setup-rust-action@v1 - with: - rust-version: ${{ matrix.rust }} - - uses: actions/checkout@master - - name: Run tests - run: cargo check --verbose + - uses: rui314/setup-mold@v1 + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: ${{ matrix.rust }} + - uses: actions/checkout@master + - name: Run tests + run: cargo check --verbose test: if: "!contains(github.event.head_commit.message, '[SKIP CI]')" @@ -27,13 +27,13 @@ jobs: rust: [stable, nightly] steps: - - uses: rui314/setup-mold@v1 - - uses: hecrj/setup-rust-action@v1 - with: - rust-version: ${{ matrix.rust }} - - uses: actions/checkout@master - - name: Run tests - run: cargo test --verbose + - uses: rui314/setup-mold@v1 + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: ${{ matrix.rust }} + - uses: actions/checkout@master + - name: Run tests + run: cargo test --verbose test-fastfloat: if: "!contains(github.event.head_commit.message, '[SKIP CI]')" @@ -44,19 +44,18 @@ jobs: rust: [stable, nightly] steps: - - uses: rui314/setup-mold@v1 - - uses: hecrj/setup-rust-action@v1 - with: - rust-version: ${{ matrix.rust }} - - uses: actions/checkout@master - - name: Run tests - run: cargo test --verbose --features fastfloat + - uses: rui314/setup-mold@v1 + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: ${{ matrix.rust }} + - uses: actions/checkout@master + - name: Run tests + run: cargo test --verbose --features fastfloat rustdoc: runs-on: ubuntu-latest - + needs: [check, test, test-fastfloat] steps: - - uses: rui314/setup-mold@v1 - name: Checkout repository uses: actions/checkout@v2 @@ -65,6 +64,7 @@ jobs: - name: Deploy Docs uses: peaceiris/actions-gh-pages@364c31d33bb99327c77b3a5438a83a357a6729ad # v3.4.0 + if: success() && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[SKIP CI]') with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_branch: gh-pages diff --git a/Cargo.toml b/Cargo.toml index d42b067..47870c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lta_models" -version = "0.5.0" -authors = ["budinverse "] +version = "0.6.0" +authors = ["zeon256 "] edition = "2021" license = "MIT" description = "🚍Models for lta-rs" @@ -13,22 +13,24 @@ exclude = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = { version = "1.0.159", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } serde_repr = "0.1.12" -serde_json = "1.0.95" regex = "1.7.3" -time = { version = "0.3.20", features = ["serde-human-readable"]} +time = { version = "0.3.20", features = ["serde-human-readable", "macros"]} lazy_static = "1.4.0" fast-float = { version = "0.2", optional = true } +serde_json = "1.0.40" [dev-dependencies] -serde_json = "1.0.40" +bincode = "1.3.3" criterion = "0.3" mimalloc = "0.1.25" +rmp-serde = "1.1.2" +flexbuffers = "2.0.0" [features] fastfloat = ["fast-float"] [[bench]] name = "benchmark" -harness = false \ No newline at end of file +harness = false diff --git a/README.md b/README.md index fe22642..13280cc 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ + + +

This repository contains the data structures required to interact with LTA's datamall APIs. All data structures implements `Serialize` and `Deserialize`. @@ -23,9 +26,19 @@ This repository contains the data structures required to interact with LTA's dat ## `Cargo.toml` setup ```toml # extra features available: fastfloat -lta-models = { version = "0.5.0" } +lta-models = { version = "0.6.0" } ``` +## Supported formats + +| Format | Supported? | Tested? | +| ----------- | ---------- | ------- | +| JSON | βœ… | βœ… | +| Bincode | βœ… | βœ… | +| Flexbuffer | βœ… | βœ… | +| MessagePack | βœ… | βœ… | + + ## Performance & `fast-float` implementation Some of the deserialization code _may_ benefit from using the `fastfloat` feature, but during testing the biggest performance improvement can be seen when you swap out the system allocator to something faster like [`mimalloc`](https://github.com/microsoft/mimalloc) or [`jemalloc`](https://github.com/jemalloc/jemalloc) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 3aca0a4..9d08309 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,20 +1,25 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use lta_models::{bus::bus_arrival::NextBusRaw, prelude::*}; use mimalloc::MiMalloc; +use time::macros::datetime; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; #[rustfmt::skip] mod de { - use lta_models::{prelude::*, crowd::crowd_density::{CrowdDensityForecast, CrowdDensityForecastRawResp}}; + use std::fmt::Debug; + + use lta_models::prelude::*; + use lta_models::bus::bus_arrival::NextBusRaw; use serde::{Deserialize, Serialize}; pub fn generate_bench<'de, I, S, F>(input_fn: F) -> S where F: FnOnce() -> &'de str, I: Deserialize<'de> + Into, - S: Serialize, + S: Serialize + Debug { let str_data = input_fn(); serde_json::from_str(str_data).map(|v: I| v.into()).unwrap() @@ -31,7 +36,7 @@ mod de { } pub fn bus_arrival() -> BusArrivalResp { - gen!(RawBusArrivalResp, _, "../dumped_data/bus_arrival.json") + gen!(BusArrivalRespRaw, _, "../dumped_data/bus_arrival.json") } pub fn bus_routes() -> Vec { @@ -94,69 +99,90 @@ mod de { gen!(StationCrowdLevelRawResp, _, "../dumped_data/crowd_density_rt.json") } - pub fn crowd_density_forecast() -> CrowdDensityForecast { + pub fn crowd_density_forecast() -> CrowdDensityForecast { gen!(CrowdDensityForecastRawResp, _, "../dumped_data/crowd_density_forecast.json") } + + pub fn transmute_nextbus(data: NextBusRaw) -> NextBus { + let nx = unsafe { + std::mem::transmute::<_, NextBus>(data) + }; + nx + } } #[rustfmt::skip] pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("de::bike_parking.json", |b| b.iter(|| de::bike_parking())); - c.bench_function("de::bus_arrival.json", |b| b.iter(|| de::bus_arrival())); - c.bench_function("de::bus_route.json", |b| b.iter(|| de::bus_routes())); - c.bench_function("de::bus_services.json", |b| b.iter(|| de::bus_service())); - c.bench_function("de::bus_stops.json", |b| b.iter(|| de::bus_stops())); - c.bench_function("de::carpark_avail.json", |b| b.iter(|| de::carpark_avail())); - c.bench_function("de::erp_rates.json", |b| b.iter(|| de::erp_rates())); - c.bench_function("de::est_travel_time.json", |b| b.iter(|| de::est_travel_time())); - c.bench_function("de::faulty_traffic_lights.json", |b| b.iter(|| de::faulty_traffic_lights())); - c.bench_function("de::passenger_vol_bus_stops.json", |b| b.iter(|| de::passenger_vol_bus_stops())); - c.bench_function("de::passenger_od_bus_stops.json", |b| b.iter(|| de::passenger_vol_od_bus_stops())); - c.bench_function("de::passenger_vol_od_train.json", |b| b.iter(|| de::passenger_vol_od_train())); - c.bench_function("de::passenger_vol_train.json", |b| b.iter(|| de::passenger_vol_train())); - c.bench_function("de::taxi_avail.json", |b| b.iter(|| de::taxi_avail())); - c.bench_function("de::taxi_stands.json", |b| b.iter(|| de::taxi_stands())); - c.bench_function("de::train_service_alert.json", |b| b.iter(|| de::train_service_alert())); - c.bench_function("de::crowd_density_rt.json", |b| b.iter(|| de::crowd_density_rt())); - c.bench_function("de::crowd_density_forecast.json", |b| b.iter(|| de::crowd_density_forecast())); - - let bike_parking = de::bike_parking(); - let bus_arrival = de::bus_arrival(); - let bus_routes = de::bus_routes(); - let bus_service = de::bus_service(); - let bus_stops = de::bus_stops(); - let carpark_avail = de::carpark_avail(); - let erp_rates = de::erp_rates(); - let est_travel_time = de::est_travel_time(); - let faulty_traffic_lights = de::faulty_traffic_lights(); - let passenger_vol_bus_stops = de::passenger_vol_bus_stops(); - let passenger_vol_od_bus_stops = de::passenger_vol_od_bus_stops(); - let passenger_vol_train = de::passenger_vol_train(); - let passenger_vol_od_train = de::passenger_vol_od_train(); - let taxi_avail = de::taxi_avail(); - let taxi_stands = de::taxi_stands(); - let train_service_alert = de::train_service_alert(); - let crowd_density_rt = de::crowd_density_rt(); - let crowd_density_forecast = de::crowd_density_forecast(); - - c.bench_function("ser::bike_parking.json", |b| b.iter(|| serde_json::to_string(black_box(&bike_parking)))); - c.bench_function("ser::bus_arrival.json", |b| b.iter(|| serde_json::to_string(black_box(&bus_arrival)))); - c.bench_function("ser::bus_route.json", |b| b.iter(|| serde_json::to_string(black_box(&bus_routes)))); - c.bench_function("ser::bus_services.json", |b| b.iter(|| serde_json::to_string(black_box(&bus_service)))); - c.bench_function("ser::bus_stops.json", |b| b.iter(|| serde_json::to_string(black_box(&bus_stops)))); - c.bench_function("ser::carpark_avail.json", |b| b.iter(|| serde_json::to_string(black_box(&carpark_avail)))); - c.bench_function("ser::erp_rates.json", |b| b.iter(|| serde_json::to_string(black_box(&erp_rates)))); - c.bench_function("ser::est_travel_time.json", |b| b.iter(|| serde_json::to_string(black_box(&est_travel_time)))); - c.bench_function("ser::faulty_traffic_lights.json", |b| b.iter(|| serde_json::to_string(black_box(&faulty_traffic_lights)))); - c.bench_function("ser::passenger_vol_bus_stops.json", |b| b.iter(|| serde_json::to_string(black_box(&passenger_vol_bus_stops)))); - c.bench_function("ser::passenger_od_bus_stops.json", |b| b.iter(|| serde_json::to_string(black_box(&passenger_vol_od_bus_stops)))); - c.bench_function("ser::passenger_vol_od_train.json", |b| b.iter(|| serde_json::to_string(black_box(&passenger_vol_od_train)))); - c.bench_function("ser::passenger_vol_train.json", |b| b.iter(|| serde_json::to_string(black_box(&passenger_vol_train)))); - c.bench_function("ser::taxi_avail.json", |b| b.iter(|| serde_json::to_string(black_box(&taxi_avail)))); - c.bench_function("ser::taxi_stands.json", |b| b.iter(|| serde_json::to_string(black_box(&taxi_stands)))); - c.bench_function("ser::train_service_alert.json", |b| b.iter(|| serde_json::to_string(black_box(&train_service_alert)))); - c.bench_function("ser::crowd_density_rt.json", |b| b.iter(|| serde_json::to_string(black_box(&crowd_density_rt)))); - c.bench_function("ser::crowd_density_forecast.json", |b| b.iter(|| serde_json::to_string(black_box(&crowd_density_forecast)))); + let sample_data = NextBusRaw { + origin_code: 77009, + dest_code: 77009, + est_arrival: datetime!(2023-04-06 14:47:57 +8), + lat: 1.314452, + long: 103.910009, + visit_no: 1, + load: BusLoad::SeatsAvailable, + feature: BusFeature::WheelChairAccessible, + bus_type: BusType::SingleDecker, + }; + + c.bench_function("transmute_nextbus", |b| b.iter(|| de::transmute_nextbus(black_box(sample_data.clone())))); + c.bench_function("into_nextbus", |b| b.iter(|| NextBus::from(black_box(sample_data.clone())))); + c.bench_function("de::bike_parking.json", |b| b.iter(|| de::bike_parking())); + c.bench_function("de::bus_arrival.json", |b| b.iter(|| de::bus_arrival())); + c.bench_function("de::bus_route.json", |b| b.iter(|| de::bus_routes())); + c.bench_function("de::bus_services.json", |b| b.iter(|| de::bus_service())); + c.bench_function("de::bus_stops.json", |b| b.iter(|| de::bus_stops())); + c.bench_function("de::carpark_avail.json", |b| b.iter(|| de::carpark_avail())); + c.bench_function("de::erp_rates.json", |b| b.iter(|| de::erp_rates())); + c.bench_function("de::est_travel_time.json", |b| b.iter(|| de::est_travel_time())); + c.bench_function("de::faulty_traffic_lights.json", |b| b.iter(|| de::faulty_traffic_lights())); + c.bench_function("de::passenger_vol_bus_stops.json", |b| b.iter(|| de::passenger_vol_bus_stops())); + c.bench_function("de::passenger_od_bus_stops.json", |b| b.iter(|| de::passenger_vol_od_bus_stops())); + c.bench_function("de::passenger_vol_od_train.json", |b| b.iter(|| de::passenger_vol_od_train())); + c.bench_function("de::passenger_vol_train.json", |b| b.iter(|| de::passenger_vol_train())); + c.bench_function("de::taxi_avail.json", |b| b.iter(|| de::taxi_avail())); + c.bench_function("de::taxi_stands.json", |b| b.iter(|| de::taxi_stands())); + c.bench_function("de::train_service_alert.json", |b| b.iter(|| de::train_service_alert())); + c.bench_function("de::crowd_density_rt.json", |b| b.iter(|| de::crowd_density_rt())); + c.bench_function("de::crowd_density_forecast.json", |b| b.iter(|| de::crowd_density_forecast())); + + let bike_parking = de::bike_parking(); + let bus_arrival = de::bus_arrival(); + let bus_routes = de::bus_routes(); + let bus_service = de::bus_service(); + let bus_stops = de::bus_stops(); + let carpark_avail = de::carpark_avail(); + let erp_rates = de::erp_rates(); + let est_travel_time = de::est_travel_time(); + let faulty_traffic_lights = de::faulty_traffic_lights(); + let passenger_vol_bus_stops = de::passenger_vol_bus_stops(); + let passenger_vol_od_bus_stops = de::passenger_vol_od_bus_stops(); + let passenger_vol_train = de::passenger_vol_train(); + let passenger_vol_od_train = de::passenger_vol_od_train(); + let taxi_avail = de::taxi_avail(); + let taxi_stands = de::taxi_stands(); + let train_service_alert = de::train_service_alert(); + let crowd_density_rt = de::crowd_density_rt(); + let crowd_density_forecast = de::crowd_density_forecast(); + + c.bench_function("ser::bike_parking.json", |b| b.iter(|| serde_json::to_string(black_box(&bike_parking)))); + c.bench_function("ser::bus_arrival.json", |b| b.iter(|| serde_json::to_string(black_box(&bus_arrival)))); + c.bench_function("ser::bus_route.json", |b| b.iter(|| serde_json::to_string(black_box(&bus_routes)))); + c.bench_function("ser::bus_services.json", |b| b.iter(|| serde_json::to_string(black_box(&bus_service)))); + c.bench_function("ser::bus_stops.json", |b| b.iter(|| serde_json::to_string(black_box(&bus_stops)))); + c.bench_function("ser::carpark_avail.json", |b| b.iter(|| serde_json::to_string(black_box(&carpark_avail)))); + c.bench_function("ser::erp_rates.json", |b| b.iter(|| serde_json::to_string(black_box(&erp_rates)))); + c.bench_function("ser::est_travel_time.json", |b| b.iter(|| serde_json::to_string(black_box(&est_travel_time)))); + c.bench_function("ser::faulty_traffic_lights.json", |b| b.iter(|| serde_json::to_string(black_box(&faulty_traffic_lights)))); + c.bench_function("ser::passenger_vol_bus_stops.json", |b| b.iter(|| serde_json::to_string(black_box(&passenger_vol_bus_stops)))); + c.bench_function("ser::passenger_od_bus_stops.json", |b| b.iter(|| serde_json::to_string(black_box(&passenger_vol_od_bus_stops)))); + c.bench_function("ser::passenger_vol_od_train.json", |b| b.iter(|| serde_json::to_string(black_box(&passenger_vol_od_train)))); + c.bench_function("ser::passenger_vol_train.json", |b| b.iter(|| serde_json::to_string(black_box(&passenger_vol_train)))); + c.bench_function("ser::taxi_avail.json", |b| b.iter(|| serde_json::to_string(black_box(&taxi_avail)))); + c.bench_function("ser::taxi_stands.json", |b| b.iter(|| serde_json::to_string(black_box(&taxi_stands)))); + c.bench_function("ser::train_service_alert.json", |b| b.iter(|| serde_json::to_string(black_box(&train_service_alert)))); + c.bench_function("ser::crowd_density_rt.json", |b| b.iter(|| serde_json::to_string(black_box(&crowd_density_rt)))); + c.bench_function("ser::crowd_density_forecast.json", |b| b.iter(|| serde_json::to_string(black_box(&crowd_density_forecast)))); } criterion_group!(benches, criterion_benchmark); diff --git a/dumped_data/bus_arrival.json b/dumped_data/bus_arrival.json index a6bbba3..4ecaf2d 100644 --- a/dumped_data/bus_arrival.json +++ b/dumped_data/bus_arrival.json @@ -1,117 +1,413 @@ { "odata.metadata": "http://datamall2.mytransport.sg/ltaodataservice/$metadata#BusArrivalv2/@Element", - "BusStopCode": "83139", + "BusStopCode": "82009", "Services": [ - { - "ServiceNo": "15", - "Operator": "GAS", - "NextBus": { - "OriginCode": "77009", - "DestinationCode": "77009", - "EstimatedArrival": "2020-09-30T19:25:55+08:00", - "Latitude": "1.3319648333333334", - "Longitude": "103.9025555", - "VisitNumber": "1", - "Load": "SEA", - "Feature": "WAB", - "Type": "SD" + { + "ServiceNo": "150", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:00:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:15:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:30:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + } }, - "NextBus2": { - "OriginCode": "77009", - "DestinationCode": "77009", - "EstimatedArrival": "2020-09-30T19:40:55+08:00", - "Latitude": "1.3449385", - "Longitude": "103.9371435", - "VisitNumber": "1", - "Load": "SEA", - "Feature": "WAB", - "Type": "SD" + { + "ServiceNo": "154", + "Operator": "GAS", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "22009", + "EstimatedArrival": "2024-01-17T18:01:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "22009", + "EstimatedArrival": "2024-01-17T18:09:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "22009", + "EstimatedArrival": "2024-01-17T18:18:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "DD" + } }, - "NextBus3": { - "OriginCode": "77009", - "DestinationCode": "77009", - "EstimatedArrival": "2020-09-30T19:57:37+08:00", - "Latitude": "1.3692186666666668", - "Longitude": "103.94679866666667", - "VisitNumber": "1", - "Load": "SEA", - "Feature": "WAB", - "Type": "SD" - } - }, - { - "ServiceNo": "150", - "Operator": "SBST", - "NextBus": { - "OriginCode": "82009", - "DestinationCode": "82009", - "EstimatedArrival": "2020-09-30T19:23:44+08:00", - "Latitude": "1.3184525", - "Longitude": "103.90032683333334", - "VisitNumber": "1", - "Load": "SEA", - "Feature": "WAB", - "Type": "SD" + { + "ServiceNo": "22", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "54009", + "EstimatedArrival": "2024-01-17T17:59:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "54009", + "EstimatedArrival": "2024-01-17T18:12:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "54009", + "EstimatedArrival": "2024-01-17T18:19:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + } }, - "NextBus2": { - "OriginCode": "82009", - "DestinationCode": "82009", - "EstimatedArrival": "2020-09-30T19:33:48+08:00", - "Latitude": "0", - "Longitude": "0", - "VisitNumber": "1", - "Load": "SEA", - "Feature": "WAB", - "Type": "SD" + { + "ServiceNo": "60", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T17:56:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:08:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "DD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:18:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + } }, - "NextBus3": { - "OriginCode": "82009", - "DestinationCode": "82009", - "EstimatedArrival": "2020-09-30T19:48:48+08:00", - "Latitude": "0", - "Longitude": "0", - "VisitNumber": "1", - "Load": "SEA", - "Feature": "WAB", - "Type": "SD" - } - }, - { - "ServiceNo": "155", - "Operator": "SBST", - "NextBus": { - "OriginCode": "52009", - "DestinationCode": "84009", - "EstimatedArrival": "2020-09-30T19:21:28+08:00", - "Latitude": "1.3187676666666666", - "Longitude": "103.902475", - "VisitNumber": "1", - "Load": "SDA", - "Feature": "WAB", - "Type": "SD" + { + "ServiceNo": "60A", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "84511", + "EstimatedArrival": "2024-01-17T18:02:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "DD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "84511", + "EstimatedArrival": "2024-01-17T18:23:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "", + "DestinationCode": "", + "EstimatedArrival": "", + "Latitude": "", + "Longitude": "", + "VisitNumber": "", + "Load": "", + "Feature": "", + "Type": "" + } + }, + { + "ServiceNo": "61", + "Operator": "SMRT", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "43009", + "EstimatedArrival": "2024-01-17T17:58:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "43009", + "EstimatedArrival": "2024-01-17T18:10:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "43009", + "EstimatedArrival": "2024-01-17T18:22:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + } + }, + { + "ServiceNo": "63", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:01:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:11:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:21:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + } + }, + { + "ServiceNo": "63M", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:02:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:17:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:32:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + } + }, + { + "ServiceNo": "76", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "55509", + "EstimatedArrival": "2024-01-17T18:10:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "DD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "55509", + "EstimatedArrival": "2024-01-17T18:21:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "DD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "55509", + "EstimatedArrival": "2024-01-17T18:32:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + } }, - "NextBus2": { - "OriginCode": "52009", - "DestinationCode": "84009", - "EstimatedArrival": "2020-09-30T19:33:55+08:00", - "Latitude": "1.3196455", - "Longitude": "103.88187283333333", - "VisitNumber": "1", - "Load": "SEA", - "Feature": "WAB", - "Type": "SD" + { + "ServiceNo": "93", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "14009", + "EstimatedArrival": "2024-01-17T18:03:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "14009", + "EstimatedArrival": "2024-01-17T18:18:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "14009", + "EstimatedArrival": "2024-01-17T18:33:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + } }, - "NextBus3": { - "OriginCode": "52009", - "DestinationCode": "84009", - "EstimatedArrival": "2020-09-30T19:50:46+08:00", - "Latitude": "1.3332161666666666", - "Longitude": "103.87857", - "VisitNumber": "1", - "Load": "SEA", - "Feature": "WAB", - "Type": "SD" + { + "ServiceNo": "94", + "Operator": "SBST", + "NextBus": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:05:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus2": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:14:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "SD" + }, + "NextBus3": { + "OriginCode": "82009", + "DestinationCode": "82009", + "EstimatedArrival": "2024-01-17T18:22:00+08:00", + "Latitude": "0.0", + "Longitude": "0.0", + "VisitNumber": "1", + "Load": "SEA", + "Feature": "WAB", + "Type": "DD" + } } - } ] } \ No newline at end of file diff --git a/src/bus.rs b/src/bus.rs index ac37f48..955e25d 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -2,16 +2,16 @@ pub mod prelude { pub use { - crate::bus::bus_arrival::{BusArrivalResp, RawBusArrivalResp}, + crate::bus::bus_arrival::{BusArrivalResp, NextBus, BusArrivalRespRaw}, crate::bus::bus_routes::{BusRoute, BusRouteResp}, - crate::bus::bus_services::{BusService, BusServiceResp}, + crate::bus::bus_services::{BusFreq, BusService, BusServiceResp}, crate::bus::bus_stops::{BusStop, BusStopsResp}, }; } pub mod bus_arrival { use serde::{Deserialize, Serialize}; - use time::{OffsetDateTime, serde::iso8601}; + use time::{serde::iso8601, serde::timestamp, OffsetDateTime}; use crate::bus_enums::{BusFeature, BusLoad, BusType, Operator}; use crate::utils::de::{from_str, treat_error_as_none}; @@ -19,33 +19,27 @@ pub mod bus_arrival { #[cfg(feature = "fastfloat")] use crate::utils::de::from_str_fast_float; - #[deprecated(since = "0.5", note = "Will be removed in future versions")] - pub const URL: &str = "http://datamall2.mytransport.sg/ltaodataservice/BusArrivalv2"; - #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] - #[serde(rename_all(deserialize = "PascalCase"))] + #[serde(rename_all = "PascalCase")] pub struct RawArrivalBusService { pub service_no: String, pub operator: Operator, #[serde(deserialize_with = "treat_error_as_none")] - pub next_bus: Option, + pub next_bus: Option, #[serde(deserialize_with = "treat_error_as_none")] - pub next_bus_2: Option, + pub next_bus_2: Option, #[serde(deserialize_with = "treat_error_as_none")] - pub next_bus_3: Option, + pub next_bus_3: Option, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] - #[serde(rename_all(deserialize = "PascalCase"))] pub struct ArrivalBusService { pub service_no: String, - pub operator: Operator, - pub next_bus: [Option; 3], } @@ -54,68 +48,101 @@ pub mod bus_arrival { Self { service_no: data.service_no, operator: data.operator, - next_bus: [data.next_bus, data.next_bus_2, data.next_bus_3], + next_bus: [ + data.next_bus.map(Into::into), + data.next_bus_2.map(Into::into), + data.next_bus_3.map(Into::into), + ], } } } #[derive(Debug, Clone, PartialEq, PartialOrd, Deserialize, Serialize)] - #[serde(rename_all(deserialize = "PascalCase"))] - pub struct NextBus { + #[serde(rename_all = "PascalCase")] + pub struct NextBusRaw { #[serde(deserialize_with = "from_str")] pub origin_code: u32, - #[serde(deserialize_with = "from_str", alias = "DestinationCode")] + #[serde(deserialize_with = "from_str", rename = "DestinationCode")] pub dest_code: u32, - + /// Time in GMT+8 - #[serde(alias = "EstimatedArrival", deserialize_with = "iso8601::deserialize")] + #[serde( + rename = "EstimatedArrival", + deserialize_with = "iso8601::deserialize", + serialize_with = "iso8601::serialize" + )] pub est_arrival: OffsetDateTime, - #[cfg(feature = "fastfloat")] - #[serde(deserialize_with = "from_str_fast_float", alias = "Latitude")] - pub lat: f64, - - #[cfg(feature = "fastfloat")] - #[serde(deserialize_with = "from_str_fast_float", alias = "Longitude")] - pub long: f64, - - #[cfg(not(feature = "fastfloat"))] - #[serde(deserialize_with = "from_str", alias = "Latitude")] + #[serde(rename = "Latitude")] + #[cfg_attr(not(feature = "fastfloat"), serde(deserialize_with = "from_str"))] + #[cfg_attr(feature = "fastfloat", serde(deserialize_with = "from_str_fast_float"))] pub lat: f64, - #[cfg(not(feature = "fastfloat"))] - #[serde(deserialize_with = "from_str", alias = "Longitude")] + #[serde(rename = "Longitude")] + #[cfg_attr(not(feature = "fastfloat"), serde(deserialize_with = "from_str"))] + #[cfg_attr(feature = "fastfloat", serde(deserialize_with = "from_str_fast_float"))] pub long: f64, - #[serde(deserialize_with = "from_str", alias = "VisitNumber")] - pub visit_no: u32, + #[serde(deserialize_with = "from_str", rename = "VisitNumber")] + pub visit_no: u8, pub load: BusLoad, - pub feature: Option, + pub feature: BusFeature, #[serde(alias = "Type")] pub bus_type: BusType, } + #[derive(Debug, Clone, PartialEq, PartialOrd, Deserialize, Serialize)] + pub struct NextBus { + pub origin_code: u32, + pub dest_code: u32, + /// Time in GMT+8 + #[serde(with = "timestamp")] + pub est_arrival: OffsetDateTime, + pub lat: f64, + pub long: f64, + pub visit_no: u8, + pub load: BusLoad, + pub feature: BusFeature, + pub bus_type: BusType, + } + + impl From for NextBus { + #[inline(always)] + fn from(r: NextBusRaw) -> Self { + Self { + origin_code: r.origin_code, + dest_code: r.dest_code, + est_arrival: r.est_arrival, + lat: r.lat, + long: r.long, + visit_no: r.visit_no, + load: r.load, + feature: r.feature, + bus_type: r.bus_type, + } + } + } + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] - #[serde(rename_all(deserialize = "PascalCase"))] - pub struct RawBusArrivalResp { + #[serde(rename_all = "PascalCase")] + pub struct BusArrivalRespRaw { #[serde(deserialize_with = "from_str")] pub bus_stop_code: u32, pub services: Vec, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] - #[serde(rename_all(deserialize = "PascalCase"))] pub struct BusArrivalResp { pub bus_stop_code: u32, pub services: Vec, } - impl From for BusArrivalResp { - fn from(data: RawBusArrivalResp) -> Self { + impl From for BusArrivalResp { + fn from(data: BusArrivalRespRaw) -> Self { Self { bus_stop_code: data.bus_stop_code, services: data.services.into_iter().map(|v| v.into()).collect(), @@ -125,32 +152,31 @@ pub mod bus_arrival { } pub mod bus_services { + use std::num::NonZeroU8; + use crate::bus_enums::{BusCategory, Operator}; use crate::utils::de::from_str_error_as_none; use crate::utils::regex::BUS_FREQ_RE; use serde::{Deserialize, Deserializer, Serialize}; - - #[deprecated(since = "0.5", note = "Will be removed in future versions")] - pub const URL: &str = "http://datamall2.mytransport.sg/ltaodataservice/BusServices"; /// Both min and max are in terms of minutes - #[derive(Debug, Clone, PartialEq, Serialize)] + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct BusFreq { - pub min: Option, - pub max: Option, + pub min: Option, + pub max: Option, } impl BusFreq { - pub fn new(min: u32, max: u32) -> Self { + pub fn new(min: u8, max: u8) -> Self { BusFreq { - min: Some(min), - max: Some(max), + min: Some(NonZeroU8::new(min).unwrap()), + max: Some(NonZeroU8::new(max).unwrap()), } } - pub fn no_max(min: u32) -> Self { + pub fn no_max(min: u8) -> Self { BusFreq { - min: Some(min), + min: Some(NonZeroU8::new(min).unwrap()), max: None, } } @@ -170,22 +196,22 @@ pub mod bus_services { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] - #[serde(rename_all(deserialize = "PascalCase"))] - pub struct BusService { + #[serde(rename_all = "PascalCase")] + pub struct BusServiceRaw { pub service_no: String, pub operator: Operator, #[serde(alias = "Direction")] - pub no_direction: u32, + pub no_direction: u8, pub category: BusCategory, #[serde(deserialize_with = "from_str_error_as_none")] - pub origin_code: Option, + pub origin_code: Option, #[serde(deserialize_with = "from_str_error_as_none", alias = "DestinationCode")] - pub dest_code: Option, + pub dest_code: Option, #[serde(alias = "AM_Peak_Freq", deserialize_with = "from_str_to_bus_freq")] pub am_peak_freq: BusFreq, @@ -199,7 +225,22 @@ pub mod bus_services { #[serde(alias = "PM_Offpeak_Freq", deserialize_with = "from_str_to_bus_freq")] pub pm_offpeak_freq: BusFreq, - pub loop_desc: Option, + pub loop_desc: String, + } + + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] + pub struct BusService { + pub service_no: String, + pub operator: Operator, + pub no_direction: u8, + pub category: BusCategory, + pub origin_code: Option, + pub dest_code: Option, + pub am_peak_freq: BusFreq, + pub am_offpeak_freq: BusFreq, + pub pm_peak_freq: BusFreq, + pub pm_offpeak_freq: BusFreq, + pub loop_desc: String, } fn from_str_to_bus_freq<'de, D>(deserializer: D) -> Result @@ -223,14 +264,32 @@ pub mod bus_services { Ok(bus_freq) } + impl From for BusService { + fn from(r: BusServiceRaw) -> Self { + Self { + service_no: r.service_no, + operator: r.operator, + no_direction: r.no_direction, + category: r.category, + origin_code: r.origin_code, + dest_code: r.dest_code, + am_offpeak_freq: r.am_offpeak_freq, + am_peak_freq: r.am_peak_freq, + pm_offpeak_freq: r.pm_offpeak_freq, + pm_peak_freq: r.pm_peak_freq, + loop_desc: r.loop_desc, + } + } + } + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct BusServiceResp { - pub value: Vec, + pub value: Vec, } impl From for Vec { fn from(data: BusServiceResp) -> Self { - data.value + data.value.into_iter().map(Into::into).collect() } } } @@ -242,20 +301,17 @@ pub mod bus_routes { use crate::utils::de::from_str; use crate::utils::serde_date::str_time_option::{de_str_time_opt_br, ser_str_time_opt}; - #[deprecated(since = "0.5", note = "Will be removed in future versions")] - pub const URL: &str = "http://datamall2.mytransport.sg/ltaodataservice/BusRoutes"; - #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] - #[serde(rename_all(deserialize = "PascalCase"))] - pub struct BusRoute { + #[serde(rename_all = "PascalCase")] + pub struct BusRouteRaw { pub service_no: String, pub operator: Operator, - pub direction: u32, + pub direction: u8, #[serde(alias = "StopSequence")] - pub stop_seq: u32, + pub stop_seq: u8, #[serde(deserialize_with = "from_str")] pub bus_stop_code: u32, @@ -306,14 +362,50 @@ pub mod bus_routes { pub sun_last: Option