diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..cc5c18b --- /dev/null +++ b/.envrc @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +export DIRENV_WARN_TIMEOUT=20s + +eval "$(devenv direnvrc)" + +# `use devenv` supports the same options as the `devenv shell` command. +# +# To silence all output, use `--quiet`. +# +# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true +use devenv diff --git a/.gitignore b/.gitignore index 0b745e2..25a5dce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,13 @@ /target -.env \ No newline at end of file +**/target +.env +# Devenv +.devenv* +devenv.local.nix +devenv.local.yaml + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/.vscode/settings.json b/.vscode/settings.json index 9b7f0c6..7deb7b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,6 @@ "rust-analyzer.server.extraEnv": { "CARGO_TARGET_DIR": "target/analyzer", "RUSTFLAGS": "-C link-arg=-fuse-ld=lld" - } + }, + "nixEnvSelector.nixFile": "${workspaceFolder}/devenv.nix" } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5d8b0a9..d7cafa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,9 +45,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arrayvec" @@ -135,7 +135,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -208,6 +208,36 @@ dependencies = [ "windows-link", ] +[[package]] +name = "code_generator" +version = "0.1.0" +dependencies = [ + "anyhow", + "codegen", + "heck", + "itertools", + "lazy_static", + "num-to-words", + "numerics", + "quote", + "serde", + "serde_json", + "serde_path_to_error", + "serde_repr", + "serde_with", + "strum", + "syn 2.0.111", +] + +[[package]] +name = "codegen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff61280aed771c3070e7dcc9e050c66f1eb1e3b96431ba66f9f74641d02fc41d" +dependencies = [ + "indexmap 1.9.3", +] + [[package]] name = "colored" version = "3.0.0" @@ -252,6 +282,51 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.111", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -269,7 +344,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", "unicode-xid", ] @@ -292,7 +367,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -301,6 +376,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -430,7 +511,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -514,7 +595,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.9.0", "slab", "tokio", "tokio-util", @@ -785,6 +866,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -806,6 +893,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.9.0" @@ -814,6 +912,7 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.3", + "serde", ] [[package]] @@ -857,6 +956,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.172" @@ -940,6 +1045,133 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.6", + "num-complex 0.4.6", + "num-integer", + "num-iter", + "num-rational 0.4.2", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-to-words" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70c8c96e8d8ce07367ba8bf71271b7ab33891cc5f44063b3a109773640553c54" +dependencies = [ + "num 0.4.3", + "thiserror", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -949,6 +1181,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "numerics" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e490ec1cba5ae6b5c4af47b2c0a09957098bdad25f23f28284de56f4e6c3d18" +dependencies = [ + "num 0.2.1", +] + [[package]] name = "object" version = "0.36.7" @@ -987,7 +1228,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -1064,6 +1305,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1113,10 +1360,11 @@ dependencies = [ [[package]] name = "ptv" -version = "0.3.0" +version = "1.3.1" dependencies = [ "anyhow", "chrono", + "code_generator", "colored", "derive_more", "dotenv", @@ -1130,6 +1378,7 @@ dependencies = [ "rust_decimal", "serde", "serde_json", + "serde_path_to_error", "sha1", "to_and_fro", "tokio", @@ -1142,14 +1391,14 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1205,6 +1454,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "rend" version = "0.4.2" @@ -1388,6 +1657,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1425,34 +1718,67 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -1467,6 +1793,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "schemars 0.9.0", + "schemars 1.0.4", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1530,6 +1887,33 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1549,9 +1933,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -1575,7 +1959,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -1618,6 +2002,57 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -1656,7 +2091,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -1685,7 +2120,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -1733,7 +2168,7 @@ version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ - "indexmap", + "indexmap 2.9.0", "toml_datetime", "winnow", ] @@ -1926,7 +2361,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", "wasm-bindgen-shared", ] @@ -1961,7 +2396,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2006,7 +2441,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -2017,7 +2452,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -2190,7 +2625,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", "synstructure", ] @@ -2211,7 +2646,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] [[package]] @@ -2231,7 +2666,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", "synstructure", ] @@ -2271,5 +2706,5 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.111", ] diff --git a/Cargo.toml b/Cargo.toml index 0500261..334e24f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,39 +1,5 @@ -[package] -name = "ptv" -edition = "2024" -version = "0.3.1" -license = "MIT" -description = "A Rust library for the Public Transport Victoria (PTV) API" -repository = "https://github.com/tascord/ptvrs" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[target.'cfg(target_arch = "x86_64")'.dependencies] -colored = "3.0.0" -dotenv = "0.15.0" -hex = "0.4.3" -hmac = "0.12.1" -once_cell = "1.19.0" -reqwest = { version = "0.12.0", features = ["json"] } -sha1 = "0.10.6" -tokio = { version = "1.36.0", features = ["full"] } - -[dependencies] -anyhow = "1.0.81" -chrono = { version = "0.4.35", features = ["serde"] } -derive_more = { version = "2", features = ["display", "debug", "from"] } -itertools = "0.14.0" -rust_decimal = "1.37.1" -serde = { version = "1.0.197", features = ["derive"] } -serde_json = "1.0.114" -to_and_fro = "0.7.1" -url-escape = "0.1.1" - -[dev-dependencies] -futures = "0.3.30" -ptvrs-macros = { path = "ptvrs-macros" } - [workspace] -members = ["ptvrs-macros"] - -[features] +resolver = "3" +members = [ + "crates/*" +] diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml new file mode 100644 index 0000000..b3bf03d --- /dev/null +++ b/crates/api/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "ptv" +edition = "2024" +version = "1.3.1" +license = "MIT" +description = "A Rust library for the Public Transport Victoria (PTV) API" +repository = "https://github.com/tascord/ptvrs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[target.'cfg(any (target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] +colored = "3.0.0" +dotenv = "0.15.0" +hex = "0.4.3" +hmac = "0.12.1" +once_cell = "1.19.0" +reqwest = { version = "0.12.0", features = ["json"] } +sha1 = "0.10.6" +tokio = { version = "1.36.0", features = ["full"] } + +[dependencies] +anyhow = "1.0.81" +chrono = { version = "0.4.35", features = ["serde"] } +derive_more = { version = "2", features = ["display", "debug", "from"] } +itertools = "0.14.0" +rust_decimal = "1.37.1" +serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.114" +to_and_fro = "0.7.1" +url-escape = "0.1.1" +code_generator = { path = "../code_generator" } +serde_path_to_error = "0.1.20" + +[dev-dependencies] +futures = "0.3.30" +ptvrs-macros = { path = "../ptvrs-macros" } + + +[features] diff --git a/crates/api/src/core.rs b/crates/api/src/core.rs new file mode 100644 index 0000000..78b8c73 --- /dev/null +++ b/crates/api/src/core.rs @@ -0,0 +1,93 @@ +#![cfg(not(target_arch = "wasm32"))] +use std::fmt::Debug; + +use { + crate::*, + anyhow::Result, + hmac::{Hmac, Mac}, + serde::de::DeserializeOwned, + sha1::Sha1, +}; + +use serde::{Deserialize, Serialize}; + +use code_generator::SwaggerClient; + +pub const API_URL: &str = "https://timetableapi.ptv.vic.gov.au"; + +type PtvHmac = Hmac; + +use anyhow::Error; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Modes(#[serde(serialize_with = "ser_disruption_query")] pub DisruptionMode); + +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum DateTime { + Naive(chrono::NaiveDateTime), + WithTz(chrono::DateTime), +} + +#[derive(SwaggerClient)] +#[swagger( + path = "v3", + strip_prefix = "V3.", + extra_names = [("RouteType", "crate::ty::RouteType"), ("Status", "crate::ty::Status"), ("Expand", "Vec"), ("ServiceOperator", "crate::ty::ServiceOperator"), ("DisruptionStatus", "crate::ty::DisruptionStatus"), ("Geopath", "Option"),("RouteId", "crate::ty::RouteId"),("StopId", "crate::ty::StopId"),("RunId", "crate::ty::RunId"),("DirectionId", "crate::ty::DirectionId"),("DisruptionId", "crate::ty::DisruptionId"), ("DisruptionMode", "crate::ty::DisruptionMode"), ("DisruptionModes", "crate::core::Modes"), ("DateTime", "crate::core::DateTime")], + skip = ["signature"] +)] +pub struct Client { + #[swagger(static)] + devid: String, + #[swagger(static)] + token: String, +} + +use helpers::to_query; + +impl Client { + pub fn new(devid: String, token: String) -> Self { + Self { devid, token } + } + pub async fn rq(&self, path: String) -> Result { + let path = format!( + "{path}{}devid={}", + { + if !path.contains('?') { + "?" + } else if path.ends_with('?') { + "" + } else { + "&" + } + }, + self.devid + ); + + let mut hasher: PtvHmac = Hmac::new_from_slice(self.token.as_bytes()).unwrap(); + hasher.update(path.as_bytes()); + + let hash = hex::encode(hasher.finalize().into_bytes()).to_uppercase(); + let url = format!("{API_URL}{}&signature={}", path, hash); + + if std::env::var("DEBUG").is_ok() { + println!("Requesting: |{}|", url); + } + + let res = reqwest::get(&url).await?; + if !res.status().is_success() { + let status = res.status(); + if let Ok(ApiError { message, .. }) = res.json().await { + return Err(anyhow::anyhow!("Request failed: {} - {}", status, message)); + } + return Err(anyhow::anyhow!("Request failed: {}", status)); + } + // println!("{}", res.text().await?); + let res = res.text().await?; + let mut deserializer = serde_json::Deserializer::from_str(&res); + + let res: T = serde_path_to_error::deserialize(&mut deserializer) + .map_err(|e| anyhow::anyhow!("Error at path: {} - response: {}", e.path(), res))?; + Ok(res) + } +} diff --git a/src/helpers.rs b/crates/api/src/helpers.rs similarity index 97% rename from src/helpers.rs rename to crates/api/src/helpers.rs index 79797b7..8a05255 100644 --- a/src/helpers.rs +++ b/crates/api/src/helpers.rs @@ -3,7 +3,7 @@ use itertools::Itertools; use rust_decimal::Decimal; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::SeqAccess, ser}; -use crate::DisruptionModes; +use crate::DisruptionMode; pub fn clean(s: String) -> String { let mut s = s; @@ -51,14 +51,11 @@ where .map_err(|e| serde::de::Error::custom(format!("Error deser iso_8601 '{s}': {e:?}"))) } -pub fn ser_iso_8601(date: &Option, serializer: S) -> Result +pub fn ser_iso_8601(date: &NaiveDateTime, serializer: S) -> Result where S: serde::Serializer, { - match date { - Some(date) => serializer.serialize_str(&date.format("%Y-%m-%dT%H:%M:%S").to_string()), - None => serializer.serialize_none(), - } + serializer.serialize_str(&date.format("%Y-%m-%dT%H:%M:%S").to_string()) } /// 24 hour clock format (HH:MM:SS) AEDT/AEST @@ -206,16 +203,13 @@ where } pub fn ser_disruption_query( - disruption: &Option>, + disruption: &DisruptionMode, serializer: S, ) -> Result where S: ser::Serializer, { - match disruption { - Some(disruption) => serializer.collect_seq(disruption.iter().map(|d| d.as_number())), - None => serializer.serialize_none(), - } + disruption.as_number().serialize(serializer) } pub fn de_string_as_i32<'de, D>(deserializer: D) -> Result diff --git a/src/lib.rs b/crates/api/src/lib.rs similarity index 100% rename from src/lib.rs rename to crates/api/src/lib.rs diff --git a/crates/api/src/ty.rs b/crates/api/src/ty.rs new file mode 100644 index 0000000..d5b7480 --- /dev/null +++ b/crates/api/src/ty.rs @@ -0,0 +1,214 @@ +//! Some types are marked as 'Value' and have a note, TODO: T. +//! This is because the API documentation does not specify the type of the value. +//! I'll do my best to fill in what appears to be correct, but it's not guaranteed to be correct. +//! +//! I appreciate any work done to fill in the TODO: T types. + +use chrono::NaiveDate; +use derive_more::{Display, From}; +use rust_decimal::Decimal; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use to_and_fro::ToAndFro; + +use crate::helpers::deserialize_path; + +pub struct I32ButSilly(pub i32); +impl<'de> Deserialize<'de> for I32ButSilly { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Ok(I32ButSilly( + i32::from_str(&value).map_err(|e| serde::de::Error::custom(format!("{e:?}")))?, + )) + } +} + +macro_rules! newtype_i32 { + ($name:ident) => { + #[derive(Debug, Copy, Clone, Deserialize, Serialize, Display, PartialEq, Eq, PartialOrd, Ord)] + #[serde(transparent)] + pub struct $name(pub i32); + }; + ($name:ident, $($extra:tt)*) => { + #[derive(Debug, Copy, Clone, Deserialize, Serialize, Display, PartialEq, Eq, PartialOrd, Ord, $($extra)*)] + #[serde(transparent)] + pub struct $name(pub i32); + }; +} +newtype_i32!(DisruptionId); + +newtype_i32!(RunId); + +newtype_i32!(StopId); + +newtype_i32!(RouteId); + +newtype_i32!(DirectionId); + +/// Routepath (TODO) +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Geopath { + pub direction_id: DirectionId, + pub valid_from: NaiveDate, + pub valid_to: NaiveDate, + #[serde(deserialize_with = "deserialize_path")] + pub paths: Vec>, +} // TODO: T + +/// Types of routes +#[derive(Debug, Copy, Clone, Display, From, PartialEq, Eq, PartialOrd, Ord)] +#[repr(i8)] +pub enum RouteType { + /// Metropolitan train service + Train = 0, + /// Metropolitan tram service + Tram = 1, + /// Bus Service + Bus = 2, + /// V/Line regional train service + VLine = 3, + /// Night Bus service + NightBus = 4, + /// Other Route Type + Other(i8), +} + +impl Serialize for RouteType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + i8::from(*self).serialize(serializer) + } +} + +//imp deserialize +impl<'de> Deserialize<'de> for RouteType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(match i8::deserialize(deserializer)? { + 0 => RouteType::Train, + 1 => RouteType::Tram, + 2 => RouteType::Bus, + 3 => RouteType::VLine, + 4 => RouteType::NightBus, + x => RouteType::Other(x), + }) + } +} + +impl From for i8 { + fn from(value: RouteType) -> Self { + match value { + RouteType::Train => 0, + RouteType::Tram => 1, + RouteType::Bus => 2, + RouteType::VLine => 3, + RouteType::NightBus => 4, + RouteType::Other(x) => x, + } + } +} + +/// Modes of disruption +#[derive(Debug, Serialize, Deserialize, Clone, From, Copy)] +#[serde(tag = "disruption_mode_name", content = "disruption_mode")] +#[repr(i8)] +pub enum DisruptionMode { + #[serde(rename = "metro_train")] + MetroTrain = 1, // { + #[serde(rename = "metro_bus")] + MetroBus = 2, + #[serde(rename = "metro_tram")] + MetroTram = 3, + #[serde(rename = "regional_coach")] + RegionalCoach = 4, + #[serde(rename = "regional_train")] + RegionalTrain = 5, + #[serde(rename = "regional_bus")] + RegionalBus = 7, + #[serde(rename = "school_bus")] + SchoolBus = 8, + #[serde(rename = "telebus")] + Telebus = 9, + #[serde(rename = "night_bus")] + NightBus = 10, + #[serde(rename = "ferry")] + Ferry = 11, + #[serde(rename = "interstate_train")] + InterstateTrain = 12, + #[serde(rename = "skybus")] + Skybus = 13, + #[serde(rename = "taxi")] + Taxi = 14, + #[serde(rename = "general")] + General = 100, +} + +impl DisruptionMode { + pub fn as_number(&self) -> i8 { + *self as i8 + } +} + +// + +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Status { + /// API Version number + pub version: String, + /// API system health status (0=offline, 1=online) + pub health: i8, +} + +// +#[derive(ToAndFro, PartialOrd, Ord, Serialize, Deserialize)] +#[input_case("lower")] +#[output_case("lower")] +pub enum DisruptionStatus { + Current, + Planned, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct ApiError { + pub message: String, + pub status: Status, +} + +#[derive(ToAndFro, Serialize, Deserialize)] +pub enum ExpandOptions { + All, + Stop, + Route, + Run, + Direction, + Disruption, + VehiclePosition, + VehicleDescriptor, + None, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub enum PassengerType { + Senior, + Concession, + FullFare, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum ServiceOperator { + #[serde(rename = "Metro Trains Melbourne")] + MetroTrainsMelbourne, + #[serde(rename = "Yarra Trams")] + YarraTrams, + #[serde(rename = "Ventura Bus Line")] + VenturaBusLine, + Other(String), +} diff --git a/tests/main.rs b/crates/api/tests/main.rs similarity index 54% rename from tests/main.rs rename to crates/api/tests/main.rs index 50906a8..e76ef3a 100644 --- a/tests/main.rs +++ b/crates/api/tests/main.rs @@ -8,6 +8,7 @@ pub mod test { use futures::{StreamExt, stream::FuturesUnordered}; use once_cell::sync::Lazy; + use ptv::core::generated_types::*; use ptv::*; use ptvrs_macros::make_test; @@ -35,82 +36,37 @@ pub mod test { Arc Pin>>> + Send + Sync>; pub static TASKS: Lazy> = Lazy::new(|| { let mut map = BTreeMap::<&str, Task>::new(); - - // > Departures - make_test!( - map, - departures_stop, - DeparturesStopOpts => [gtfs,include_cancelled], - ROUTE_TYPE, - STOP_ID - ); - - make_test!( - map, - departures_stop_route, - DeparturesStopRouteOpts => [[max_results: 10, look_backwards: true],gtfs,include_cancelled], - ROUTE_TYPE, - ROUTE_ID, - STOP_ID - ); - // > Routes - make_test!( - map, - routes, - RouteOpts => [route_types: vec![RouteType::Train]] - ); - - make_test!(map, routes_id, RouteIdOpts => [include_geopath], ROUTE_ID); - - // > Patterns - make_test!(map, patterns_run_route, PatternsRunRouteOpts => [stop_id: STOP_ID, expand: vec![ExpandOptions::All], include_skipped, include_geopath], RUN_REF, ROUTE_TYPE); - - // > Directions - - make_test!(map, directions_id, DIRECTION_ID); - - make_test!(map, directions_route, ROUTE_ID); - - make_test!(map, directions_id_route, DIRECTION_ID, ROUTE_TYPE); - - // > Disruptions - - make_test!(map, disruptions, DisruptionsOpts => [modes: vec![DisruptionModes::MetroTrain], modes: vec![DisruptionModes::MetroBus]]); - + make_test!(map, get_departures_by_route_type_and_stop_id, GetDeparturesByRouteTypeAndStopIdParams => [ gtfs, include_cancelled], ROUTE_TYPE, STOP_ID ); + make_test!(map, get_departures_by_route_type_and_stop_id_and_route_id, GetDeparturesByRouteTypeAndStopIdAndRouteIdParams => [ gtfs, include_cancelled], ROUTE_TYPE, STOP_ID, ROUTE_ID ); + make_test!(map, get_directions_by_direction_id, DIRECTION_ID); make_test!( map, - disruptions_route, - DisruptionsSpecificOpts => [ - status: DisruptionStatus::Current, - status: DisruptionStatus::Planned - ], - ROUTE_ID + get_directions_by_direction_id_and_route_type, + DIRECTION_ID, + ROUTE_TYPE ); - - make_test!( - map, - disruptions_route_stop, - DisruptionsSpecificOpts => [ - status: DisruptionStatus::Current, - status: DisruptionStatus::Planned - ], - ROUTE_ID, - STOP_ID - ); - - make_test!( - map, - disruptions_stop, - DisruptionsSpecificOpts => [ - status: DisruptionStatus::Current, - status: DisruptionStatus::Planned - ], - STOP_ID - ); - - // > Search - make_test!(map, search, SearchOpts => [include_outlets, include_addresses],"Flinders Street Station"); - + make_test!(map, get_directions_by_route_id, ROUTE_ID); + make_test!(map, get_disruption_by_disruption_id, DisruptionId(1)); + make_test!(map, get_disruptions, GetDisruptionsParams => [ route_types: vec![RouteType::Train] ]); + make_test!(map, get_disruptions_by_route_id, GetDisruptionsByRouteIdParams => [ disruption_status: DisruptionStatus::Planned ], ROUTE_ID); + make_test!(map, get_disruptions_by_route_id_and_stop_id, GetDisruptionsByRouteIdAndStopIdParams => [ disruption_status: DisruptionStatus::Planned ], ROUTE_ID, STOP_ID); + make_test!(map, get_disruptions_by_stop_id, GetDisruptionsByStopIdParams => [ disruption_status: DisruptionStatus::Planned ], STOP_ID); + // make_test!(map, get_disruptions_modes); + make_test!(map, get_fare_estimate_by_min_zone_and_max_zone,GetFareEstimateByMinZoneAndMaxZoneParams => [ is_journey_in_free_tram_zone ], 1, 2); + make_test!(map, get_outlet_geolocation_by_latitude_and_longitude, GetOutletGeolocationByLatitudeAndLongitudeParams => [ max_results: 20, max_distance: 30.0 ], -37.8100, 144.9620); + make_test!(map, get_outlets, GetOutletsParams => [ max_results: 20 ]); + make_test!(map, get_route_by_route_id, GetRouteByRouteIdParams => [include_geopath], ROUTE_ID); + // make_test!(map, get_route_types); + make_test!(map, get_routes, GetRoutesParams => [ route_types: vec![RouteType::Train], route_types: vec![RouteType::Bus], route_types: vec![RouteType::Tram] ]); + make_test!(map, get_run_by_run_ref_and_route_type, GetRunByRunRefAndRouteTypeParams => [expand: vec![ExpandOptions::All]], RUN_REF, ROUTE_TYPE); + //make_test!(map, get_runs_by_route_id, GetRunsByRouteIdParams => [ expand: vec![ExpandOptions::Direction] ], ROUTE_ID); + //make_test!(map, get_runs_by_route_id_and_route_type, GetRunsByRouteIdAndRouteTypeParams => [ expand: vec![ExpandOptions::Direction] ], ROUTE_ID, ROUTE_TYPE); + make_test!(map, get_runs_by_run_ref, GetRunsByRunRefParams => [ expand: vec![ExpandOptions::All] ], RUN_REF); + make_test!(map, get_search_result_by_search_term, GetSearchResultBySearchTermParams => [ route_types: vec![RouteType::Train] ], "Flinders Street"); + make_test!(map, get_stop_by_stop_id_and_route_type, GetStopByStopIdAndRouteTypeParams => [ stop_location, stop_amenities ], STOP_ID, ROUTE_TYPE); + make_test!(map, get_stopping_pattern_by_run_ref_and_route_type, GetStoppingPatternByRunRefAndRouteTypeParams => [ expand: vec![ExpandOptions::All] ], RUN_REF, ROUTE_TYPE); + make_test!(map, get_stops_by_distance_by_latitude_and_longitude, GetStopsByDistanceByLatitudeAndLongitudeParams => [ max_results: 20, max_distance: 30.0 ], -37.8100, 144.9620); + make_test!(map, get_stops_on_route_by_route_id_and_route_type, GetStopsOnRouteByRouteIdAndRouteTypeParams => [ include_geopath ], ROUTE_ID, ROUTE_TYPE); map }); diff --git a/crates/code_generator/Cargo.toml b/crates/code_generator/Cargo.toml new file mode 100644 index 0000000..a242f84 --- /dev/null +++ b/crates/code_generator/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "code_generator" +version = "0.1.0" +edition = "2024" + + +[lib] +proc-macro = true + +[dependencies] +anyhow = "1.0.100" +codegen = "0.2.0" +heck = "0.5.0" +itertools = "0.14.0" +lazy_static = "1.5.0" +num-to-words = "0.1.1" +numerics = "0.0.1" +quote = "1.0.42" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +serde_path_to_error = "0.1.20" +serde_repr = "0.1.20" +serde_with = "3.15.1" +strum = { version = "0.27.2", features = ["derive"] } +syn = "2.0.111" diff --git a/crates/code_generator/src/lib.rs b/crates/code_generator/src/lib.rs new file mode 100644 index 0000000..95319dd --- /dev/null +++ b/crates/code_generator/src/lib.rs @@ -0,0 +1,386 @@ +use anyhow::Context as AnyhowContext; +use std::{collections::HashMap, rc::Rc, str::FromStr}; + +use heck::{ToSnakeCase, ToUpperCamelCase}; +use itertools::Itertools; +use syn::{DeriveInput, parse::Parse, spanned::Spanned}; + +use crate::types::{Context, SwaggerFile, ToRustTypeName, TypePath}; + +struct SwaggerClientArgs { + path: String, + strip_prefix: Option, + skipped: Vec, + extra_names: HashMap, +} + +impl Parse for SwaggerClientArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut path = None; + let mut strip_prefix = None; + let mut extra_names = None; + let mut skipped = Vec::new(); + + while !input.is_empty() { + let ident: syn::Ident = input.parse()?; + input.parse::()?; + + match ident.to_string().as_str() { + "path" => { + let lit: syn::LitStr = input.parse()?; + path = Some(lit.value()); + } + "strip_prefix" => { + let lit: syn::LitStr = input.parse()?; + strip_prefix = Some(lit.value()); + } + "skip" => { + let skips: syn::ExprArray = input.parse()?; + for expr in skips.elems.iter() { + if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }) = expr + { + skipped.push(lit.value()); + } else { + return Err(syn::Error::new( + expr.span(), + "Expected string literals in skip array", + )); + } + } + + // Ignore the value for now + } + "extra_names" => { + let map: syn::ExprArray = input.parse()?; + let mut names_map = HashMap::new(); + for expr in map.elems.iter() { + if let syn::Expr::Tuple(tuple) = expr { + if tuple.elems.len() == 2 { + if let ( + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit1), + .. + }), + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit2), + .. + }), + ) = (&tuple.elems[0], &tuple.elems[1]) + { + names_map.insert(lit1.value(), lit2.value()); + } else { + return Err(syn::Error::new( + tuple.span(), + "Expected string literals in extra_names tuples", + )); + } + } else { + return Err(syn::Error::new( + tuple.span(), + "Expected tuples of length 2 in extra_names", + )); + } + } else { + return Err(syn::Error::new( + expr.span(), + "Expected tuples in extra_names array", + )); + } + } + extra_names = Some(names_map); + } + _ => { + return Err(syn::Error::new( + ident.span(), + format!("Unknown argument: {}", ident), + )); + } + } + + if input.peek(syn::Token![,]) { + input.parse::()?; + } + } + + let path = path.ok_or_else(|| syn::Error::new(input.span(), "Missing 'path' argument"))?; + + Ok(SwaggerClientArgs { + path, + strip_prefix, + skipped, + extra_names: extra_names.unwrap_or_default(), + }) + } +} + +#[macro_use] +mod types; + +fn derive_actual( + input: DeriveInput, + args: SwaggerClientArgs, +) -> anyhow::Result { + let mut deserializer = + serde_json::Deserializer::from_reader(std::fs::File::open(&args.path).unwrap()); + let mut constant_parameters = Vec::new(); + constant_parameters.extend(args.skipped); + + if let syn::Data::Struct(strukt) = &input.data { + strukt.fields.iter().for_each(|field| { + let field_name = field.ident.as_ref().unwrap().to_string(); + field + .attrs + .iter() + .find(|attr| attr.path().is_ident("swagger")); + constant_parameters.push(field_name); + }); + } + println!("Constant parameters: {:?}", &constant_parameters); + let result: SwaggerFile = match serde_path_to_error::deserialize(&mut deserializer) { + Ok(v) => v, + Err(e) => { + eprintln!("Error at path: {}", e.path()); + return Err(e.into()); + } + }; + + let names = result + .definitions + .keys() + .map(|name| { + ( + TypePath(format!("#/definitions/{}", name)), + // TODO: make names properly configured, not just strip V3. + if let Some(ref prefix) = args.strip_prefix { + name.replace(prefix, "") + } else { + name.clone() + }, + ) + }) + .collect::>(); + + let context = Rc::new(Context { + scope: std::cell::RefCell::new(codegen::Scope::new()), + constant_parameters, + strip_prefix: args.strip_prefix.clone(), + types: names, + name_stack: Default::default(), + extra_types: args.extra_names, + }); + let mut module = codegen::Module::new("generated_types"); + { + let mut scope = context.scope.borrow_mut(); + module.import("serde", "Serialize"); + module.import("serde", "Deserialize"); + module.import("derive_more", "Display"); + module.vis("pub"); + scope.push_module(module.clone()); + } + for (name, ty) in &result.definitions { + let context = context.clone(); + let name = if let Some(ref prefix) = args.strip_prefix { + name.replace(prefix, "") + } else { + name.clone() + }; + let _handle = context.handle_with_name(name.clone()); + ty.schema_object.to_rust_type_name(context.clone())?; + } + let paths = result + .paths + .into_iter() + .map(|(mut k, v)| { + k.elements.pop_front(); + (k, v) + }) + .collect::>(); + + for (path_name, path_item) in paths { + // println!("path_name: {:?}", path_name); + for (method, operation) in &path_item.methods { + let ret_type = operation + .responses + .get("200") + .unwrap() + .schema + .as_ref() + .unwrap() + .schema_object + .to_rust_type_name(context.clone())?; + + let name = if !operation.parameters.path.is_empty() { + let rust_type = ret_type.to_snake_case().replace("_response", ""); + format!( + "{}_{}_by_{}", + method, + rust_type, + operation + .parameters + .path + .iter() + .map(|x| &x.name) + .join("_and_") + ) + } else { + format!("{}_{}", method, path_name.elements.iter().join("_")) + }; + let _name = context.handle_with_name(name.clone()); + let path_params = operation + .parameters + .path + .iter() + .filter(|param| !context.constant_parameters.contains(¶m.name)) + .map(|param| { + let param_name = param.name.to_snake_case(); + let _name_handle = context.handle_with_name(param_name.clone()); + let rust_type = if let Some(ty) = + context.extra_types.get(¶m_name.to_upper_camel_case()) + { + ty.clone() + } else { + param + .r#type + .schema_object + .to_rust_type_name(context.clone()) + .unwrap() + }; + + (param_name, rust_type, param.name.clone()) + }) + .collect_vec(); + let obj_params_name = format!("{}Params", name.to_upper_camel_case()); + let _handle = context.handle_with_name(obj_params_name.clone()); + let obj_params = operation + .parameters + .query + .iter() + .filter(|param| !context.constant_parameters.contains(¶m.name)) + .map(|param| { + let context = context.clone(); + let param_name = param.name.to_snake_case(); + let _handle = context.handle_with_name(param_name.clone()); + let rust_type = if let Some(ty) = + context.extra_types.get(¶m_name.to_upper_camel_case()) + { + ty.clone() + } else { + param + .r#type + .schema_object + .to_rust_type_name(context.clone()) + .unwrap() + }; + let mut field = + codegen::Field::new(¶m_name, format!("Option<{}>", rust_type)); + field.vis("pub"); + if let Some(ref docs) = param.description { + field.doc(docs); + } + field.annotation("#[serde(skip_serializing_if = \"Option::is_none\")]"); + field + }) + .collect_vec(); + let func_param_name = { + if obj_params.is_empty() { + None + } else { + context!(context, scope); + + let func_params = scope + .new_struct(&obj_params_name) + .vis("pub") + .derive("Default"); + struc_opts!(func_params); + for field in obj_params { + func_params.push_field(field); + } + Some(obj_params_name) + } + }; + + let ret_type = format!( + "{}::{}", + { + context!(context, scope); + scope.name.clone() + }, + ret_type.to_upper_camel_case() + ); + + let mut scope = context.scope.borrow_mut(); + let scope = scope.new_impl(&input.ident.to_string()); + let mut func = scope + .new_fn(&name.to_snake_case()) + .vis("pub") + .ret(format!("Result<{},Error>", ret_type)); + if let Some(ref docs) = operation.summary { + func.doc(docs); + } + func.set_async(true); + func.arg_ref_self(); + for (param_name, rust_type, _) in path_params.iter() { + func = func.arg( + ¶m_name, + if rust_type == "String" { + "impl AsRef" + } else { + &rust_type + }, + ); + } + if let Some(func_param_name) = &func_param_name { + func.arg( + "params", + format!("{}::{}", "generated_types", func_param_name), + ); + } + let mut path_name = path_name.clone(); + for (param_name, ty, original_name) in path_params { + let to_replace = format!("{{{}}}", original_name); + let replacement = if ty == "String" { + func.line(format!( + "let {0} = url_escape::encode_path(&clean({0}.as_ref().to_string())).into_owned();", + ¶m_name + )); + format!("{{{}}}", param_name) + } else { + format!("{{{}}}", param_name) + }; + path_name.internal = path_name.internal.replace(&to_replace, &replacement); + } + // println!("Generating function a: {}", path_name.internal); + + func.line(format!("let path = format!(\"{}\");", &path_name.internal)); + if let Some(_params) = func_param_name { + func.line("self.rq(format!(\"{}?{}\", path, to_query(params))).await"); + } else { + func.line("self.rq(path).await"); + } + } + } + let _ident = &input.ident; + let scope = context.scope.borrow_mut(); + let generated = scope.to_string(); + // std::fs::write("output.rs", &generated).unwrap(); + + Ok(proc_macro::TokenStream::from_str(&generated).unwrap()) + + // Further code generation logic would go here... +} + +#[proc_macro_derive(SwaggerClient, attributes(swagger))] +pub fn swagger_client_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + let args = input + .attrs + .iter() + .find(|attr| attr.path().is_ident("swagger")) + .expect("Expected a #[swagger(...)] attribute") + .parse_args::() + .unwrap(); + derive_actual(input, args).unwrap() +} diff --git a/crates/code_generator/src/types.rs b/crates/code_generator/src/types.rs new file mode 100644 index 0000000..003a841 --- /dev/null +++ b/crates/code_generator/src/types.rs @@ -0,0 +1,757 @@ +use anyhow::Context as AnyhowContext; +use heck::ToUpperCamelCase; +use heck::*; +use itertools::Itertools; +use numerics::ToPrimitive; +use serde::{Deserialize, Serialize}; + +use std::{cell::RefCell, collections::VecDeque, rc::Rc}; + +macro_rules! context { + ($e:expr,$id:ident) => { + let mut scopea = $e.scope.borrow_mut(); + let $id = scopea + .get_module_mut("generated_types") + .context("Expected generated_types module")?; + }; +} + +macro_rules! enm_opts { + ($e:expr) => { + struc_opts!($e); + $e.derive("Display"); + }; +} +macro_rules! struc_opts { + ($e:expr) => { + $e.vis("pub"); + $e.derive("Debug"); + $e.derive("Serialize"); + $e.derive("Deserialize"); + }; +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +pub struct TypePath(pub String); + +#[derive(Debug)] +pub struct Context { + pub types: std::collections::HashMap, + pub extra_types: std::collections::HashMap, + pub scope: RefCell, + pub constant_parameters: Vec, + // probably not the best way, but it makes sense + pub name_stack: RefCell>, + pub strip_prefix: Option, +} + +impl Context { + pub fn handle_with_name<'a>(&'a self, name: String) -> ContextHandle<'a> { + // println!("Pushing name: {}", &name); + self.name_stack.borrow_mut().push_front(name); + + // println!("Current stack: {:?}", &self.extra_name.borrow()); + ContextHandle { context: self } + } + + pub fn get_name(&self) -> String { + let name = self + .name_stack + .borrow() + .iter() + .map(|s| s.to_upper_camel_case()) + .join(""); + //println!("Name: {}", &name); + name + } + + pub fn get_top_name(&self) -> Option { + self.name_stack.borrow().front().cloned() + } +} + +pub struct ContextHandle<'a> { + pub context: &'a Context, +} + +impl Drop for ContextHandle<'_> { + fn drop(&mut self) { + let mut names = self.context.name_stack.borrow_mut(); + names.pop_front(); + } +} + +pub trait ToRustTypeName { + fn to_rust_type_name(&self, context: Rc) -> anyhow::Result; +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Info { + pub title: String, + pub version: String, + pub description: Option, + pub terms_of_service: Option, + pub contact: Option, + pub license: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Contact { + pub name: Option, + pub url: Option, + pub email: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct License { + pub name: String, + pub url: Option, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, strum::Display)] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum Method { + Get, + Post, + Put, + Delete, +} + +//TODO: probably change to an enum +#[derive(Serialize, Deserialize, Debug)] +pub struct PathItem { + #[serde(flatten)] + pub methods: std::collections::BTreeMap, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "lowercase")] +pub enum NumberFormat { + Int32, + Int64, + Float, + Double, +} + +impl ToRustTypeName for NumberFormat { + fn to_rust_type_name(&self, _context: Rc) -> anyhow::Result { + Ok(match self { + NumberFormat::Int32 => "i32".to_string(), + NumberFormat::Int64 => "i64".to_string(), + NumberFormat::Float => "f32".to_string(), + NumberFormat::Double => "f64".to_string(), + }) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "type")] +pub enum TypeTagged { + Number { + format: Option, + r#enum: Option>, + }, + Integer { + format: Option, + r#enum: Option>, + }, + + String { + r#enum: Option>, + format: Option, + }, + Boolean, + Array { + items: Box, + }, + Object { + properties: Option>, + #[serde(rename = "additionalProperties")] + additional_properties: Option>, + required: Option>, + }, +} + +impl ToRustTypeName for TypeTagged { + fn to_rust_type_name(&self, context: Rc) -> anyhow::Result { + match self { + TypeTagged::Number { format, r#enum } => { + let format = format + .as_ref() + .map(|x| x.to_rust_type_name(context.clone())) + .unwrap_or(Ok("f64".to_string()))?; + if let Some(en) = r#enum { + let enum_name = context + .get_top_name() + .context("Expected extra name for enum")? + .to_upper_camel_case(); + if let Some(enuma) = context.extra_types.get(&enum_name) { + Ok(enuma.clone()) + } else { + let enum_name = context.get_name(); + context!(context, scope); + let enm = scope.new_enum(&enum_name); + enm_opts!(enm); + enm.repr(&format); + // FIXME: make enums not crazy + for variant in en { + enm.new_variant(format!( + "{} = {}", + num_to_words::integer_to_en_us(variant.floor().to_i64().unwrap())? + .to_upper_camel_case(), + variant + )); + } + Ok(enum_name) + } + } else { + Ok(format) + } + } + // TODO: Handle enums properly + TypeTagged::Integer { format, r#enum } => { + let format = format + .as_ref() + .map(|x| x.to_rust_type_name(context.clone())) + .unwrap_or(Ok("i64".to_string()))?; + if let Some(en) = r#enum { + let enum_name = context + .get_top_name() + .context("Expected extra name for enum")? + .to_upper_camel_case(); + + //println!("Generating enum: {}", &enum_name); + if let Some(enuma) = context.extra_types.get(&enum_name) { + return Ok(enuma.clone()); + } else { + let enum_name = context.get_name(); + context!(context, scope); + let new_enum_name = format!("{}Enum", enum_name); + // println!("Generating enum: {}", &new_enum_name); + let enm = scope.new_enum(&new_enum_name); + enm_opts!(enm); + enm.repr(&format); + + for variant in en { + enm.new_variant(format!( + "{} = {}", + num_to_words::integer_to_en_us(*variant)?.to_upper_camel_case(), + variant + )); + } + Ok(new_enum_name) + } + } else { + Ok(format) + } + } + TypeTagged::String { + format: Some(x), .. + } => { + if let "date-time" = x.as_str() { + Ok(context + .extra_types + .get("DateTime") + .cloned() + .unwrap_or("chrono::NaiveDateTime".to_string())) + } else if let "date" = x.as_str() { + Ok("chrono::NaiveDate".to_string()) + } else { + Ok("String".to_string()) + } + } + // TODO: Handle enums properly + TypeTagged::String { r#enum, .. } => { + if let Some(en) = r#enum { + let enum_name = context + .get_top_name() + .context("Expected extra name for enum")? + .to_upper_camel_case(); + if let Some(enuma) = context.extra_types.get(&enum_name) { + return Ok(enuma.clone()); + } else { + let enum_name = context.get_name(); + context!(context, scope); + // println!("Generating enum: {}", &enum_name); + let enm = scope.new_enum(&enum_name); + enm_opts!(enm); + + for variant in en { + let variant_name = if variant.chars().next().unwrap().is_numeric() { + format!("_{}", variant) + } else { + variant.to_upper_camel_case() + }; + enm.new_variant(format!( + r#"{} = "{}""#, + variant_name, + variant.replace('"', r#"\""#) + )); + } + Ok(enum_name) + } + } else { + Ok("String".to_string()) + } + } + TypeTagged::Boolean => Ok("bool".to_string()), + TypeTagged::Array { items } => { + { + let mut borrowed = context.name_stack.borrow_mut(); + let front_mut = borrowed.front_mut(); + if let Some(name) = front_mut { + *name = name.strip_suffix("s").unwrap_or(name).to_string(); + } + } + Ok(format!( + "Vec<{}>", + items.schema_object.to_rust_type_name(context.clone())? + )) + } + TypeTagged::Object { + properties: None, + additional_properties: None, + .. + } => Ok("std::collections::HashMap".to_string()), + TypeTagged::Object { + properties: None, + additional_properties: Some(prop), + .. + } => Ok(format!( + "std::collections::HashMap", + prop.schema_object.to_rust_type_name(context.clone())? + )), + // TODO: Implement proper object handling + TypeTagged::Object { + properties, + additional_properties, + required, + } => { + let struct_name = context + .get_top_name() + .context("Expected extra name for object")? + .to_upper_camel_case(); + if let Some(structa) = context.extra_types.get(&struct_name) { + return Ok(structa.clone()); + } else { + let struct_name = context.get_name(); + // println!("Generating struct: {}", struct_name); + let mut strukt = codegen::Struct::new(&struct_name); + struc_opts!(strukt); + + if let Some(props) = properties { + for (prop_name, prop_type) in props { + let rename_to = prop_name; + let mut field_name = prop_name.to_snake_case(); + if field_name == "type" { + field_name = "type_".to_string(); + } + let _handle = context.handle_with_name(field_name.clone()); + let rust_type = + prop_type.schema_object.to_rust_type_name(context.clone())?; + let mut field = if required + .as_ref() + .map(|r| r.contains(prop_name)) + .unwrap_or(false) + { + codegen::Field::new(&field_name, rust_type) + } else { + codegen::Field::new(&field_name, format!("Option<{}>", rust_type)) + }; + field.vis("pub"); + if let Some(description) = &prop_type.description { + field.doc(description); + } + + if rename_to != &field_name { + field.annotation(format!(r#"#[serde(rename = "{}")]"#, rename_to)); + } + strukt.push_field(field); + } + } + if let Some(add_props) = additional_properties { + let _handle = context.handle_with_name("additional_properties".to_string()); + let typea = add_props.schema_object.to_rust_type_name(context.clone())?; + let mut field = codegen::Field::new( + "additional_properties", + format!("std::collections::HashMap", typea), + ); + field.vis("pub"); + field.annotation(r#"#[serde(flatten)]"#); + strukt.push_field(field); + } // else { + // let mut field = codegen::Field::new( + // "additional_properties", + // "std::collections::HashMap".to_string(), + // ); + // field.vis("pub"); + // field.annotation(r#"#[serde(flatten)]"#); + // strukt.push_field(field); + + { + context!(context, scope); + scope.push_struct(strukt); + } + Ok(struct_name) + } + } + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum TypeUntagged { + Tagged(TypeTagged), + Ref { + #[serde(rename = "$ref")] + r#ref: TypePath, + }, + // Extra(serde_json::Value), +} + +impl ToRustTypeName for TypeUntagged { + fn to_rust_type_name(&self, context: Rc) -> anyhow::Result { + match self { + TypeUntagged::Tagged(tagged) => tagged.to_rust_type_name(context), + TypeUntagged::Ref { r#ref } => { + let type_name = context.types.get(r#ref).cloned(); + if let Some(type_name) = type_name { + Ok(context + .extra_types + .get(&type_name) + .cloned() + .unwrap_or(type_name)) + } else { + Ok("serde_json::Value".to_string()) + } + } + } + } +} + +mod locations { + use std::fmt::Display; + + use super::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize)] + pub struct Query; + #[derive(Debug, Serialize, Deserialize)] + pub struct Header; + #[derive(Debug, Serialize, Deserialize)] + pub struct Path; + + impl Display for Query { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "query") + } + } + + impl Display for Header { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "header") + } + } + + impl Display for Path { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "path") + } + } + + #[derive(Serialize, Deserialize, Debug, Clone, Copy)] + #[serde(rename_all = "lowercase")] + pub enum InLocation { + Query, + Header, + Path, + Cookie, + } + + impl AsInLocation for InLocation { + fn from_enum(_loc: &InLocation) -> Option { + Some(*_loc) + } + fn to_enum(&self) -> InLocation { + *self + } + } + pub trait AsInLocation: Serialize + for<'de> Deserialize<'de> { + fn from_enum(loc: &InLocation) -> Option; + fn to_enum(&self) -> InLocation; + } + + impl AsInLocation for Query { + fn from_enum(loc: &InLocation) -> Option { + match loc { + InLocation::Query => Some(Query), + _ => None, + } + } + fn to_enum(&self) -> InLocation { + InLocation::Query + } + } + + impl AsInLocation for Header { + fn from_enum(loc: &InLocation) -> Option { + match loc { + InLocation::Header => Some(Header), + _ => None, + } + } + fn to_enum(&self) -> InLocation { + InLocation::Header + } + } + + impl AsInLocation for Path { + fn from_enum(loc: &InLocation) -> Option { + match loc { + InLocation::Path => Some(Path), + _ => None, + } + } + fn to_enum(&self) -> InLocation { + InLocation::Path + } + } +} + +pub use locations::{AsInLocation, InLocation}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Parameter +where + T: AsInLocation, +{ + pub name: String, + #[serde(rename = "in")] + #[serde(bound = "T: AsInLocation + std::fmt::Debug")] + pub in_: T, + pub required: bool, + #[serde(flatten)] + pub r#type: Type, + pub description: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Response { + pub description: Option, + pub schema: Option, +} + +#[derive(Debug)] +pub struct ParameterLocations { + pub query: Vec>, + pub header: Vec>, + pub path: Vec>, +} + +impl Default for ParameterLocations { + fn default() -> Self { + ParameterLocations { + query: Vec::new(), + header: Vec::new(), + path: Vec::new(), + } + } +} + +impl<'de> Deserialize<'de> for ParameterLocations { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let params: Vec> = Deserialize::deserialize(deserializer)?; + let query = params + .iter() + .filter_map(|x| { + if let Some(q) = locations::Query::from_enum(&x.in_) { + Some(Parameter { + name: x.name.clone(), + in_: q, + required: x.required, + r#type: Type { + description: x.r#type.description.clone(), + schema_object: x.r#type.schema_object.clone(), + }, + description: x.description.clone(), + }) + } else { + None + } + }) + .collect::>(); + let header = params + .iter() + .filter_map(|x| { + if let Some(h) = locations::Header::from_enum(&x.in_) { + Some(Parameter { + name: x.name.clone(), + in_: h, + required: x.required, + r#type: Type { + description: x.r#type.description.clone(), + schema_object: x.r#type.schema_object.clone(), + }, + description: x.description.clone(), + }) + } else { + None + } + }) + .collect::>(); + let path = params + .iter() + .filter_map(|x| { + if let Some(p) = locations::Path::from_enum(&x.in_) { + Some(Parameter { + name: x.name.clone(), + in_: p, + required: x.required, + r#type: Type { + description: x.r#type.description.clone(), + schema_object: x.r#type.schema_object.clone(), + }, + description: x.description.clone(), + }) + } else { + None + } + }) + .collect::>(); + Ok(ParameterLocations { + query, + header, + path, + }) + } +} + +impl Serialize for ParameterLocations { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut params = Vec::new(); + for param in &self.query { + params.push(serde_json::to_value(param).map_err(serde::ser::Error::custom)?); + } + for param in &self.header { + params.push(serde_json::to_value(param).map_err(serde::ser::Error::custom)?); + } + for param in &self.path { + params.push(serde_json::to_value(param).map_err(serde::ser::Error::custom)?); + } + params.serialize(serializer) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Operation { + pub tags: Option>, + pub operation_id: Option, + pub consumes: Option>, + pub produces: Option>, + pub summary: Option, + pub description: Option, + #[serde(default)] + pub parameters: ParameterLocations, + pub responses: std::collections::HashMap, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum SwaggerVersion { + #[serde(rename = "2.0")] + V2, + #[serde(rename = "3.0")] + V3, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum Scheme { + Http, + Https, + Ws, + Wss, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, strum::Display)] +pub enum PathElement { + #[strum(to_string = "{0}")] + Static(String), + Parameter(String), +} + +impl AsRef for PathElement { + fn as_ref(&self) -> &str { + match self { + PathElement::Static(s) => s.as_ref(), + PathElement::Parameter(s) => s.as_ref(), + } + } +} + +impl<'de> Deserialize<'de> for PathName { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + let elements = s + .split('/') + .filter(|part| !part.is_empty()) + .map(|part| { + if part.starts_with('{') && part.ends_with('}') { + PathElement::Parameter(part[1..part.len() - 1].to_string()) + } else { + PathElement::Static(part.to_string()) + } + }) + .collect(); + Ok(PathName { + internal: s, + elements, + }) + } +} + +#[derive(Debug, Clone)] +pub struct PathName { + pub internal: String, + pub elements: std::collections::VecDeque, +} + +impl PartialEq for PathName { + fn eq(&self, other: &Self) -> bool { + self.internal == other.internal + } +} +impl Eq for PathName {} + +impl std::hash::Hash for PathName { + fn hash(&self, state: &mut H) { + self.internal.hash(state); + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Type { + pub description: Option, + #[serde(flatten)] + pub schema_object: TypeUntagged, +} + +#[derive(Deserialize, Debug)] +pub struct SwaggerFile { + pub swagger: SwaggerVersion, + pub info: Info, + pub host: String, + pub schemes: Vec, + pub paths: std::collections::HashMap, + pub definitions: std::collections::HashMap, +} diff --git a/ptvrs-macros/Cargo.toml b/crates/ptvrs-macros/Cargo.toml similarity index 100% rename from ptvrs-macros/Cargo.toml rename to crates/ptvrs-macros/Cargo.toml diff --git a/ptvrs-macros/src/lib.rs b/crates/ptvrs-macros/src/lib.rs similarity index 100% rename from ptvrs-macros/src/lib.rs rename to crates/ptvrs-macros/src/lib.rs diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..f347f45 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,123 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1761156818, + "owner": "cachix", + "repo": "devenv", + "rev": "949fc6dc8f36f38e1cceb1bf1673c4e995a6a766", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1760663237, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1758532697, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "207a4cb0e1253c7658c6736becc6eb9cace1f25f", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ], + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1761273263, + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "28405834d4fdd458d28e123fae4db148daecec6f", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..d420b4a --- /dev/null +++ b/devenv.nix @@ -0,0 +1,33 @@ +{ + pkgs, + lib, + config, + ... +}: +{ + # https://devenv.sh/languages/ + languages.rust = { + enable = true; + channel = "nightly"; + components = [ + "rustc" + "cargo" + "clippy" + "rustfmt" + "rust-analyzer" + ]; + }; + + # https://devenv.sh/packages/ + packages = [ + # Packages used for flake-parts modules: + pkgs.nil + pkgs.mold + pkgs.lld + pkgs.statix + pkgs.deadnix + pkgs.alejandra + ]; + + # See full reference at https://devenv.sh/reference/options/ +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..37e8589 --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,8 @@ +inputs: + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + rust-overlay: + url: github:oxalica/rust-overlay + inputs: + nixpkgs: + follows: nixpkgs diff --git a/src/core.rs b/src/core.rs deleted file mode 100644 index 4e99fbe..0000000 --- a/src/core.rs +++ /dev/null @@ -1,326 +0,0 @@ -#![cfg(not(target_arch = "wasm32"))] -use { - crate::*, - anyhow::Result, - hmac::{Hmac, Mac}, - serde::de::DeserializeOwned, - sha1::Sha1, -}; - -pub const API_URL: &str = "https://timetableapi.ptv.vic.gov.au"; - -type PtvHmac = Hmac; - -pub struct Client { - devid: String, - key: String, -} - -impl Client { - pub fn new(devid: String, key: String) -> Client { - Client { devid, key } - } - - pub async fn rq(&self, path: String) -> Result { - let path = format!( - "/{path}{}devid={}", - { - if !path.contains('?') { - "?" - } else if path.ends_with('?') { - "" - } else { - "&" - } - }, - self.devid - ); - - let mut hasher: PtvHmac = Hmac::new_from_slice(self.key.as_bytes()).unwrap(); - hasher.update(path.as_bytes()); - - let hash = hex::encode(hasher.finalize().into_bytes()); - let url = format!("{API_URL}{}&signature={}", path, hash); - - if std::env::var("DEBUG").is_ok() { - println!("Requesting: {}", url); - } - - let res = reqwest::get(&url).await?; - if !res.status().is_success() { - let status = res.status(); - if let Ok(ApiError { message, .. }) = res.json().await { - return Err(anyhow::anyhow!("Request failed: {} - {}", status, message)); - } - return Err(anyhow::anyhow!("Request failed: {}", status)); - } - - Ok(res.json().await?) - } - - /* > Departures */ - - /// View departures for all routes from a specific stop - pub async fn departures_stop( - &self, - route_type: RouteType, - stop_id: StopId, - options: DeparturesStopOpts, - ) -> Result { - self.rq(format!( - "v3/departures/route_type/{}/stop/{}?{}", - route_type, - stop_id, - to_query(options) - )) - .await - } - - /// View departures for a specific route from a stop - pub async fn departures_stop_route( - &self, - route_type: RouteType, - route_id: RouteId, - stop_id: StopId, - options: DeparturesStopRouteOpts, - ) -> Result { - self.rq(format!( - "v3/departures/route_type/{}/stop/{}/route/{}?{}", - route_type, - route_id, - stop_id, - to_query(options) - )) - .await - } - - /* > Directions */ - - /// View all routes for a direction of travel - pub async fn directions_id(&self, direction_id: DirectionId) -> Result { - self.rq(format!("v3/directions/{}", direction_id)).await - } - - /// View directions that a route travels in - pub async fn directions_route(&self, route_id: RouteId) -> Result { - self.rq(format!("v3/directions/route/{}", route_id)).await - } - - /// View all routes of a particular type for a direction of travel - pub async fn directions_id_route( - &self, - direction_id: DirectionId, - route_type: RouteType, - ) -> Result { - self.rq(format!( - "v3/directions/{}/route_type/{}", - direction_id, route_type - )) - .await - } - - /* > Disruptions */ - - /// View all disruptions for all route types - pub async fn disruptions(&self, options: DisruptionsOpts) -> Result { - self.rq(format!("v3/disruptions?{}", to_query(options))) - .await - } - - /// View all disruptions for a particular route - pub async fn disruptions_route( - &self, - route_id: RouteId, - options: DisruptionsSpecificOpts, - ) -> Result { - self.rq(format!( - "v3/disruptions/route/{}?{}", - route_id, - to_query(options) - )) - .await - } - - /// View all disruptions for a particular route and stop - pub async fn disruptions_route_stop( - &self, - route_id: RouteId, - stop_id: StopId, - options: DisruptionsSpecificOpts, - ) -> Result { - self.rq(format!( - "v3/disruptions/route/{}/stop/{}?{}", - route_id, - stop_id, - to_query(options) - )) - .await - } - - /// View all disruptions for a particular stop - pub async fn disruptions_stop( - &self, - stop_id: StopId, - options: DisruptionsSpecificOpts, - ) -> Result { - self.rq(format!( - "v3/disruptions/stop/{}?{}", - stop_id, - to_query(options) - )) - .await - } - - /// View a specific disruption - pub async fn disruptions_id(&self, disruption_id: DisruptionId) -> Result { - // TODO: Technically this has Status too but I dont want to - // dupe the struct 17 times - self.rq(format!("v3/disruptions/{}", disruption_id)).await - } - - /* > Fare Estimate */ - - /// Estimate a fare by zone - pub async fn fare_estimate( - &self, - min_zone: u8, - max_zone: u8, - options: FareEstimateOpts, - ) -> Result { - self.rq(format!( - "v3/fare_estimate/min_zone/{}/max_zone/{}?{}", - min_zone, - max_zone, - to_query(options) - )) - .await - } - - /* > Outlets */ - - /// Last all ticket outlets - pub async fn outlets(&self, options: OutletsOpts) -> Result { - self.rq(format!("v3/outlets?{}", to_query(options))).await - } - - /// List outlets near a specific location - pub async fn outlets_lat_long( - &self, - latitude: f64, - longitude: f64, - options: OutletsLatLongOpts, - ) -> Result { - self.rq(format!( - "v3/outlets/location/{}/{}?{}", - latitude, - longitude, - to_query(options) - )) - .await - } - - /* > Patterns */ - - /// View the stopping pattern for a specific tip / service run - pub async fn patterns_run_route( - &self, - run_ref: &str, - route_type: RouteType, - options: PatternsRunRouteOpts, - ) -> Result { - self.rq(format!( - "v3/pattern/run/{}/route_type/{}?{}", - run_ref, - route_type, - to_query(options) - )) - .await - } - - /* > Routes */ - - /// View route names and numbers for all routes - pub async fn routes(&self, options: RouteOpts) -> Result { - self.rq(format!("v3/routes?{}", to_query(options))).await - } - - // View route name and number for a specific route ID - pub async fn routes_id( - &self, - route_id: RouteId, - options: RouteIdOpts, - ) -> Result { - self.rq(format!("v3/routes/{}?{}", route_id, to_query(options))) - .await - } - - /* > Runs */ - - /// View all trip/service runs for a specific run_ref - pub async fn runs_ref(&self, run_ref: &str, options: RunsRefOpts) -> Result { - self.rq(format!("v3/runs/{}?{}", run_ref, to_query(options))) - .await - } - - /// View all trip/service runs for a specific route ID - pub async fn runs_id(&self, run_id: RouteId, options: RunsIdOpts) -> Result { - self.rq(format!("v3/runs/route/{}?{}", run_id, to_query(options))) - .await - } - - /// View all trip/service runs for a specific run_ref and route type - pub async fn runs_ref_type( - &self, - run_ref: &str, - route_type: RouteType, - options: RunsRefOpts, - ) -> Result { - self.rq(format!( - "v3/runs/{}/route_type/{}?{}", - run_ref, - route_type, - to_query(options) - )) - .await - } - - /// View all trip/service runs for a specific run ID and route type - pub async fn runs_id_type( - &self, - run_id: RunId, - route_type: RouteType, - options: RunsIdOpts, - ) -> Result { - self.rq(format!( - "v3/runs/route/{}/route_type/{}?{}", - run_id, - route_type, - to_query(options) - )) - .await - } - // Search for stops, routes and myki outlets that match the input search term - pub async fn search(&self, search_term: &str, options: SearchOpts) -> Result { - self.rq(format!( - "v3/search/{}?{}", - url_escape::encode_path(&clean(search_term.to_owned())).into_owned(), - to_query(options) - )) - .await - } - // View facilities at a specific stop (Metro and VLine stations only) - pub async fn stops_id_route_type( - &self, - stop_id: StopId, - route_type: RouteType, - options: StopsIdRouteTypeOpts, - ) -> Result { - self.rq(format!( - "v3/stops/{}/route_type/{}?{}", - stop_id, - route_type, - to_query(options) - )) - .await - } -} diff --git a/src/ty.rs b/src/ty.rs deleted file mode 100644 index a61bd50..0000000 --- a/src/ty.rs +++ /dev/null @@ -1,1127 +0,0 @@ -//! Some types are marked as 'Value' and have a note, TODO: T. -//! This is because the API documentation does not specify the type of the value. -//! I'll do my best to fill in what appears to be correct, but it's not guaranteed to be correct. -//! -//! I appreciate any work done to fill in the TODO: T types. - -use chrono::{NaiveDate, NaiveDateTime}; -use derive_more::{Display, From}; -use rust_decimal::Decimal; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::{collections::BTreeMap, str::FromStr}; -use to_and_fro::{output_case, ToAndFro}; - -use crate::helpers::{ - de_iso_8601, de_rfc3339, de_service_time, deserialize_path, opt_de_rfc3339, opt_ser_rfc3339, - ser_disruption_query, ser_iso_8601, ser_rfc3339, ser_touch_utc, -}; - -pub struct I32ButSilly(pub i32); -impl<'de> Deserialize<'de> for I32ButSilly { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = String::deserialize(deserializer)?; - Ok(I32ButSilly( - i32::from_str(&value).map_err(|e| serde::de::Error::custom(format!("{e:?}")))?, - )) - } -} - -macro_rules! newtype_i32 { - ($name:ident) => { - #[derive(Debug, Copy, Clone, Deserialize, Serialize, Display, PartialEq, Eq, PartialOrd, Ord)] - #[serde(transparent)] - pub struct $name(pub i32); - }; - ($name:ident, $($extra:tt)*) => { - #[derive(Debug, Copy, Clone, Deserialize, Serialize, Display, PartialEq, Eq, PartialOrd, Ord, $($extra)*)] - #[serde(transparent)] - pub struct $name(pub i32); - }; -} -newtype_i32!(DisruptionId); - -newtype_i32!(RunId); - -newtype_i32!(StopId); - -newtype_i32!(RouteId); - -newtype_i32!(DirectionId); - -/// Routepath (TODO) -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Geopath { - pub direction_id: DirectionId, - pub valid_from: NaiveDate, - pub valid_to: NaiveDate, - #[serde(deserialize_with = "deserialize_path")] - pub paths: Vec>, -} // TODO: T - -/// Types of routes -#[derive(Debug, Copy, Clone, Display, From, PartialEq, Eq, PartialOrd, Ord)] -#[repr(i8)] -pub enum RouteType { - /// Metropolitan train service - Train = 0, - /// Metropolitan tram service - Tram = 1, - /// Bus Service - Bus = 2, - /// V/Line regional train service - VLine = 3, - /// Night Bus service - NightBus = 4, - /// Other Route Type - Other(i8), -} - -impl Serialize for RouteType { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - i8::from(*self).serialize(serializer) - } -} - -//imp deserialize -impl<'de> Deserialize<'de> for RouteType { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ok(match i8::deserialize(deserializer)? { - 0 => RouteType::Train, - 1 => RouteType::Tram, - 2 => RouteType::Bus, - 3 => RouteType::VLine, - 4 => RouteType::NightBus, - x => RouteType::Other(x), - }) - } -} - -impl From for i8 { - fn from(value: RouteType) -> Self { - match value { - RouteType::Train => 0, - RouteType::Tram => 1, - RouteType::Bus => 2, - RouteType::VLine => 3, - RouteType::NightBus => 4, - RouteType::Other(x) => x, - } - } -} -/// Modes of disruption -#[derive(Debug, Serialize, Deserialize, Clone, From, Copy)] -#[serde(tag = "disruption_mode_name", content = "disruption_mode")] -#[repr(i8)] -pub enum DisruptionModes { - #[serde(rename = "metro_train")] - MetroTrain = 1, // { - #[serde(rename = "metro_bus")] - MetroBus = 2, - #[serde(rename = "metro_tram")] - MetroTram = 3, - #[serde(rename = "regional_coach")] - RegionalCoach = 4, - #[serde(rename = "regional_train")] - RegionalTrain = 5, - #[serde(rename = "regional_bus")] - RegionalBus = 7, - #[serde(rename = "school_bus")] - SchoolBus = 8, - #[serde(rename = "telebus")] - Telebus = 9, - #[serde(rename = "night_bus")] - NightBus = 10, - #[serde(rename = "ferry")] - Ferry = 11, - #[serde(rename = "interstate_train")] - InterstateTrain = 12, - #[serde(rename = "skybus")] - Skybus = 13, - #[serde(rename = "taxi")] - Taxi = 14, - #[serde(rename = "general")] - General = 100, -} - -impl DisruptionModes { - pub fn as_number(&self) -> i8 { - *self as i8 - } -} - -// - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Status { - /// API Version number - pub version: String, - /// API system health status (0=offline, 1=online) - pub health: i8, -} - -// - -#[derive(Serialize, Default)] -pub struct DeparturesStopOpts { - /// Filter by platform number at stop - #[serde(skip_serializing_if = "Option::is_none")] - pub platform_numbers: Option>, - /// Filter by identifier of direction of travel - #[serde(skip_serializing_if = "Option::is_none")] - pub direction_id: Option, - /// Indicates that stop_id parameter will accept "GTFS stop_id" data - #[serde(skip_serializing_if = "Option::is_none")] - pub gtfs: Option, - /// Filter by the date and time of the request (default = current date and time) - #[serde(serialize_with = "ser_iso_8601")] - #[serde(rename = "date_utc")] - #[serde(skip_serializing_if = "Option::is_none")] - pub date: Option, - /// Maximum number of results returned - #[serde(skip_serializing_if = "Option::is_none")] - pub max_results: Option, - /// Indicates if cancelled services are included in results. - /// Metro Trains only - #[serde(skip_serializing_if = "Option::is_none")] - pub include_cancelled: Option, - /// Indicates if filtering runs to those that arrive at destination before date_urc - /// (default = false) - #[serde(skip_serializing_if = "Option::is_none")] - pub look_backwards: Option, - /// Last of objects to be returned in full - #[serde(skip_serializing_if = "Option::is_none")] - pub expand: Option>, - ///Indicates if the route geopath should be returned - #[serde(skip_serializing_if = "Option::is_none")] - pub include_geopath: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct ApiError { - pub message: String, - pub status: Status, -} - -#[derive(Serialize, Default)] -pub struct DeparturesStopRouteOpts { - /// Filter by identifier of direction of travel - #[serde(skip_serializing_if = "Option::is_none")] - pub direction_id: Option, - /// Indicates that stop_id parameter will accept "GTFS stop_id" data - #[serde(skip_serializing_if = "Option::is_none")] - pub gtfs: Option, - /// Filter by the date and time of the request (default = current date and time) - #[serde(serialize_with = "ser_iso_8601")] - #[serde(rename = "date_utc")] - #[serde(skip_serializing_if = "Option::is_none")] - pub date: Option, - /// Maximum number of results returned - #[serde(skip_serializing_if = "Option::is_none")] - pub max_results: Option, - /// Indicates if cancelled services are included in results. - /// Metro Trains only - #[serde(skip_serializing_if = "Option::is_none")] - pub include_cancelled: Option, - /// Indicates if filtering runs to those that arrive at destination before date_urc - /// (default = false) - #[serde(skip_serializing_if = "Option::is_none")] - pub look_backwards: Option, - /// Last of objects to be returned in full - #[serde(skip_serializing_if = "Option::is_none")] - pub expand: Option>, - ///Indicates if the route geopaath should be returned - #[serde(skip_serializing_if = "Option::is_none")] - pub include_geopath: Option, -} - -#[derive(ToAndFro, Serialize)] -pub enum ExpandOptions { - All, - Stop, - Route, - Run, - Direction, - Disruption, - VehiclePosition, - VehicleDescriptor, - None, -} - -impl<'de> Deserialize<'de> for ExpandOptions { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = String::deserialize(deserializer)?; - Self::from_str(&value).map_err(serde::de::Error::custom) - } -} -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DeparturesResponse { - /// Timetabled and real-time service departures - pub departures: Vec, - /// A train station, tram stop, bus stop, regional coach stop or Night Bus stop - pub stops: BTreeMap, - /// Train lines, tram routes, bus routes, regional coach routes, Night Bus routes - pub routes: BTreeMap, - /// Individual trips/services of a route - pub runs: BTreeMap, - /// Directions of travel of route - pub directions: BTreeMap, - /// Disruption information applicable to relevant routes or stops - pub disruptions: BTreeMap, - // API Status / Metadata - pub status: Status, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StoppingPatternsStop { - #[serde(flatten)] - pub stop: Stop, - pub stop_ticket: StopTicket, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Stop { - #[serde(rename = "stop_distance")] - pub distance: Decimal, - #[serde(rename = "stop_suburb")] - pub suburb: String, - #[serde(rename = "stop_name")] - pub name: String, - #[serde(rename = "stop_id")] - pub id: StopId, - pub route_type: RouteType, - #[serde(rename = "stop_latitude")] - pub latitude: Decimal, - #[serde(rename = "stop_longitude")] - pub longitude: Decimal, - #[serde(rename = "stop_landmark")] - pub landmark: String, - #[serde(rename = "stop_sequence")] - pub sequence: i32, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Departure { - /// Stop identifier - pub stop_id: StopId, - /// Route identifier - pub route_id: RouteId, - /// Numeric trip/service run identifier. Defaults to -1 when run identifier is Alphanumeric - pub run_id: RunId, - /// Alphanumeric trip/service run identifier - pub run_ref: String, - /// Direction of travel identifier - pub direction_id: DirectionId, - /// Disruption information identifier(s) - pub disruption_ids: Vec, - /// Scheduled (i.e. timetabled) departure time and date - #[serde(deserialize_with = "opt_de_rfc3339")] - #[serde(serialize_with = "opt_ser_rfc3339")] - #[serde(rename = "scheduled_departure_utc")] - pub scheduled_departure: Option, // TODO: Seems to always be Some - /// Real-time estimate of departure time and date - #[serde(deserialize_with = "opt_de_rfc3339")] - #[serde(serialize_with = "opt_ser_rfc3339")] - #[serde(rename = "estimated_departure_utc")] - pub estimated_departure: Option, - /// Indicates if the metropolitan train service is at the platform at the time of query. - /// false for other modes - pub at_platform: bool, - /// Platform number at stop (metropolitan train only. - /// None for other modes - pub platform_number: Option, - /// Flag indicating special condition for run - pub flags: String, - /// Chronological sequence for the departures in a run. - pub departure_sequence: i32, - - pub skipped_stops: Option>, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopTicket { - pub ticket_type: String, - pub zone: String, - pub is_free_fare_zone: bool, - pub ticket_machine: bool, - pub ticket_checks: bool, - pub vline_reservation: bool, - pub ticket_zones: Vec, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Route { - pub route_type: RouteType, - #[serde(rename = "route_id")] - pub id: RouteId, - #[serde(rename = "route_name")] - pub name: String, - #[serde(rename = "route_number")] - pub number: String, - #[serde(rename = "route_gtfs_id")] - pub gtfs_id: String, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct RouteWithGeoPath { - #[serde(flatten)] - pub route: Route, - /// TODO: T - pub geopath: Option>, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Direction { - #[serde(rename = "direction_id")] - pub id: DirectionId, - #[serde(rename = "direction_name")] - pub name: String, - pub route_id: RouteId, - pub route_type: RouteType, -} - -// - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DirectionsResponse { - /// Directions of travel of route - pub directions: Vec, - /// API Status / Metadata - pub status: Status, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DirectionWithDescription { - // Direction of travel identifier - #[serde(flatten)] - pub direction: Direction, - /// Description - #[serde(rename = "route_direction_description")] - pub description: String, -} - -// - -#[derive(Serialize, Default)] -pub struct DisruptionsOpts { - /// Filter by route type - #[serde(skip_serializing_if = "Option::is_none")] - pub route_types: Option>, - /// Filter by disruption_modes - #[serde(rename = "disruption_modes")] - #[serde(skip_serializing_if = "Option::is_none")] - #[serde(serialize_with = "ser_disruption_query")] - pub modes: Option>, - /// Filter by status of disruption - #[serde(rename = "disruption_status")] - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, -} - -#[derive(Serialize, Default)] -pub struct DisruptionsSpecificOpts { - /// Filter by status of disruption - #[serde(rename = "disruption_status")] - #[serde(skip_serializing_if = "Option::is_none")] - pub status: Option, -} - -#[derive(ToAndFro, PartialOrd, Ord)] -#[input_case("lower")] -#[output_case("lower")] -pub enum DisruptionStatus { - Current, - Planned, -} - -impl Serialize for DisruptionStatus { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for DisruptionStatus { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = String::deserialize(deserializer)?.to_lowercase(); - Self::from_str(&value).map_err(serde::de::Error::custom) - } -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DisruptionsResponse { - /// Disruption information applicable to relavenet route, run, stop, direction - pub disruptions: Disruptions, - /// API Status / Metadata - pub status: Status, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Disruptions { - /// Subset of disruption information applicable to multiple route_types - pub general: Vec, - /// Subset of disruption information applicable to metropolitan train - pub metro_train: Vec, - /// Subset of disruption information applicable to metropolitan tram - pub metro_tram: Vec, - /// Subset of disruption information applicable to metropolitan bus - pub metro_bus: Vec, - /// Subset of disruption information applicable to V/Line train - pub regional_train: Vec, - /// Subset of disruption information applicable to V/Line coach - pub regional_coach: Vec, - /// Subset of disruption information applicable to regional bus - pub regional_bus: Vec, - /// Subset of disruption information applicable to school bus - pub school_bus: Vec, - /// Subset of disruption information applicable to telebus services - pub telebus: Vec, - /// Subset of disruption information applicable to night bus - pub night_bus: Vec, - /// Subset of disruption information applicable to ferry - pub ferry: Vec, - /// Subset of disruption information applicable to interstate train - pub interstate_train: Vec, - /// Subset of disruption information applicable to skybus - pub skybus: Vec, - /// Subset of disruption information applicable to taxi - pub taxi: Vec, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Disruption { - /// Disruption information identifier - pub disruption_id: DisruptionId, - /// Headline title summarising disruption information - pub title: String, - /// URL of relevant article on PTV website - pub url: String, - /// Description of the disruption - pub description: String, - /// Status of the disruption (e.g. "Planned", "Current") - pub disruption_status: DisruptionStatus, // TODO: This might want to be a String - /// Type of disruption - pub disruption_type: String, - /// Date and time disruption information is published on PTV website - #[serde(deserialize_with = "de_rfc3339")] - #[serde(serialize_with = "ser_rfc3339")] - pub published_on: NaiveDateTime, - /// Date and time disruption information was last updated by PTV - #[serde(deserialize_with = "de_rfc3339")] - #[serde(serialize_with = "ser_rfc3339")] - pub last_updated: NaiveDateTime, - /// Date and time at which disruption begins - #[serde(deserialize_with = "de_rfc3339")] - #[serde(serialize_with = "ser_rfc3339")] - pub from_date: NaiveDateTime, - /// Date and time at which disruption ends (returns None if unknown) - #[serde(deserialize_with = "opt_de_rfc3339")] - #[serde(serialize_with = "opt_ser_rfc3339")] - pub to_date: Option, - /// Route relevant to a disruption (if applicable) - pub routes: Vec, - /// Stop relevant to a disruption (if applicable) - pub stops: Vec, - pub colour: String, - pub display_on_board: bool, - pub display_status: bool, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DisruptionStop { - #[serde(rename = "stop_id")] - pub id: StopId, - #[serde(rename = "stop_name")] - pub name: String, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DisruptionRoute { - #[serde(flatten)] - pub route: Route, - /// Direction of travel relevant to disruption - pub direction: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct DisruptionDirection { - /// Route and direction of travel combination identifier - #[serde(rename = "route_direction_id")] - pub combination_id: i32, - /// Direction of travel identifier - #[serde(rename = "direction_id")] - pub id: DirectionId, - /// Name of direction of travel - #[serde(rename = "direction_name")] - pub name: String, - /// Time of service to which disruption applies. Returns None if disruption applies to multiple, or no services - /// - /// This doesn't use null, it uses a blank string. I hate it here. - #[serde(deserialize_with = "de_service_time")] - pub service_time: Option, -} - -// - -#[derive(Serialize, Default)] -pub struct FareEstimateOpts { - /// Journey touch on - #[serde(serialize_with = "ser_touch_utc")] - #[serde(rename = "journey_touch_on_utc")] - #[serde(skip_serializing_if = "Option::is_none")] - pub touch_on: Option, - /// Journey touch off - #[serde(serialize_with = "ser_touch_utc")] - #[serde(rename = "journey_touch_off_utc")] - #[serde(skip_serializing_if = "Option::is_none")] - pub touch_off: Option, - #[serde(rename = "is_journey_in_free_tram_zone")] - #[serde(skip_serializing_if = "Option::is_none")] - pub free_tram_zone: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub traveled_route_types: Option>, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "PascalCase")] -pub struct FareEstimateResponse { - pub fare_estimate_result: FareEstimate, - // API Status / Metadata - pub fare_estimate_status: Status, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "PascalCase")] -pub struct ZoneInfo { - pub min_zone: i32, - pub max_zone: i32, - pub unique_zones: Vec, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "camelCase")] -pub enum PassengerType { - Senior, - Concession, - FullFare, -} -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "PascalCase")] -pub struct PassengerFare { - pub passenger_type: PassengerType, - pub fare2_hour_off_peak: Decimal, - pub fare2_hour_peak: Decimal, - pub fare_daily_peak: Decimal, - pub fare_daily_off_peak: Decimal, - pub pass7_days: Decimal, - pub pass28_to69_day_per_day: Decimal, - pub pass70_plus_day_per_day: Decimal, - pub weekend_cap: Decimal, - pub holiday_cap: Decimal, -} - -// TODO: This is undefined on the API documentation -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -#[serde(rename_all = "PascalCase")] -pub struct FareEstimate { - pub is_early_bird: bool, - pub is_journey_in_free_tram_zone: Option, - pub is_this_weekend_journey: Option, - pub zone_info: ZoneInfo, - pub passenger_fares: Vec, -} - -#[derive(Serialize, Default)] -pub struct OutletsOpts { - /// Maximum number of results returned - /// (default = 30) - #[serde(skip_serializing_if = "Option::is_none")] - pub max_results: Option, -} - -#[derive(Serialize, Default)] -pub struct OutletsLatLongOpts { - /// Maximum number of results returned - /// (default = 30) - #[serde(skip_serializing_if = "Option::is_none")] - pub max_results: Option, - - /// Maximum distance (in metres) from the specified location - /// (default = 300) - #[serde(skip_serializing_if = "Option::is_none")] - pub max_distance: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct OutletsResponse { - /// Myki ticket outlets - pub outlets: Vec, - /// API Status / Metadata - pub status: Status, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Outlet { - /// The SLID / SPID - #[serde(rename = "outlet_slid_spid")] - pub id: String, - /// The location name of the outlet - #[serde(rename = "outlet_name")] - pub name: String, - /// The buisness name of the outlet - #[serde(rename = "outlet_business")] - pub business: String, - /// Geographic coordinate of the latitude at outlet - #[serde(rename = "outlet_latitude")] - pub latitude: Decimal, - /// Geographic coordinate of the longitude at outlet - #[serde(rename = "outlet_longitude")] - pub longitude: Decimal, - /// The city/municipality of the outlet - #[serde(rename = "outlet_suburb")] - pub suburb: String, - /// The postcode of the outlet - #[serde(rename = "outlet_postcode")] - pub postcode: usize, - /// The business hours on Monday - #[serde(rename = "outlet_business_hour_mon")] - pub hours_monday: Option, - /// The business hours on Tuesday - #[serde(rename = "outlet_business_hour_tue")] - pub hours_tuesday: Option, - /// The business hours on Wednesday - #[serde(rename = "outlet_business_hour_wed")] - pub hours_wednesday: Option, - /// The business hours on Thursday - #[serde(rename = "outlet_business_hour_thu")] - pub hours_thursday: Option, - /// The business hours on Friday - #[serde(rename = "outlet_business_hour_fri")] - pub hours_friday: Option, - /// The business hours on Saturday - #[serde(rename = "outlet_business_hour_sat")] - pub hours_saturday: Option, - /// The business hours on Sunday - #[serde(rename = "outlet_business_hour_sun")] - pub hours_sunday: Option, - /// Any additional notes for the outlet such as - /// 'Buy pre-loaded myki cards only' - #[serde(rename = "outlet_notes")] - pub note: Option, -} - -// - -#[derive(Serialize, Default)] -pub struct PatternsRunRouteOpts { - /// List of objects to be returned in full - #[serde(skip_serializing_if = "Option::is_none")] - pub expand: Option>, - /// Filter by stop_id - #[serde(skip_serializing_if = "Option::is_none")] - pub stop_id: Option, - /// Filter by the date and time of the request - #[serde(serialize_with = "ser_iso_8601")] - #[serde(rename = "date_utc")] - #[serde(skip_serializing_if = "Option::is_none")] - pub date: Option, - /// Include any skipped stops in a stopping pattern - /// (default = false) - #[serde(rename = "include_skipped_stops")] - #[serde(skip_serializing_if = "Option::is_none")] - pub include_skipped: Option, - /// Incidates if the route geopath should be returned - /// (default = false) - #[serde(skip_serializing_if = "Option::is_none")] - pub include_geopath: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] // PartialOrd, Ord can be added once Value has a strong type -pub struct PatternResponse { - /// Disruption information applicable to relevant routes or stops - pub disruptions: Vec, - /// Timetabled and real-time service departures - pub departures: Vec, - /// A train station, tram stop, bus stop, regional coach stop or Night Bus stop - pub stops: BTreeMap, - /// Train lines, tram routes, bus routes, regional coach routes, Night Bus routes - pub routes: BTreeMap, // TODO needs to be more specific - /// Individual trips/services of a route - pub runs: BTreeMap, - /// Directions of travel of route - pub directions: BTreeMap, - /// API Status / Metadata - pub status: Status, -} - -#[derive(Serialize, Default)] -pub struct RouteOpts { - /// Filterered by route_types - #[serde(skip_serializing_if = "Option::is_none")] - pub route_types: Option>, - /// Filter by name of route. - /// Accepts partial route name matches - #[serde(skip_serializing_if = "Option::is_none")] - pub route_name: Option, -} - -#[derive(Serialize, Default)] -pub struct RouteIdOpts { - /// Indicates kif geopath will be returned (default = false) - #[serde(skip_serializing_if = "Option::is_none")] - pub include_geopath: Option, - // Filter geopath by date (default = current date) - #[serde(serialize_with = "ser_iso_8601")] - #[serde(skip_serializing_if = "Option::is_none")] - pub date: Option, -} - -/// This is just documented wrong? -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct RoutesResponse { - /// Train lines, tram routes, bus routes, regional coach routes, Night Bus routes - pub routes: Vec, - /// Documented as route: RouteWithStatus? - /// API Status / Metadata - pub status: Status, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct RoutesIdResponse { - /// Train lines, tram routes, bus routes, regional coach routes, Night Bus routes - pub route: Option, - /// API Status / Metadata - pub status: Status, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct RouteWithStatus { - /// Service status for the route (indicates disruptions) - #[serde(rename = "route_service_status")] - pub service_status: RouteServiceStatus, - #[serde(flatten)] - pub route: RouteWithGeoPath, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct RouteServiceStatus { - pub description: String, - pub timestamp: String, // TODO: Add a deser. No information in docs. -} - -#[derive(Serialize, Default)] -pub struct RunsIdOpts { - /// List of objects to be returned in full - #[serde(skip_serializing_if = "Option::is_none")] - pub expand: Option>, - /// Filter by the date and time of the request - #[serde(serialize_with = "ser_iso_8601")] - #[serde(rename = "date_utc")] - #[serde(skip_serializing_if = "Option::is_none")] - pub date: Option, -} - -#[derive(Serialize, Default)] -pub struct RunsRefOpts { - /// List of objects to be returned in full - #[serde(skip_serializing_if = "Option::is_none")] - pub expand: Option>, - /// Filter by the date and time of the request - #[serde(serialize_with = "ser_iso_8601")] - #[serde(rename = "date_utc")] - #[serde(skip_serializing_if = "Option::is_none")] - pub date: Option, - /// Indicates if the route geopath should be returned - #[serde(skip_serializing_if = "Option::is_none")] - pub include_geopath: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct RunsResponse { - /// Individual trips/services of a route - pub runs: Vec, - /// API Status / Metadata - pub status: Status, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Run { - /// Numeric trip/service run identifier. - /// Defaults to -1 when run identifier is Alphanumeric - pub run_id: RunId, - /// Alphanumeric trip/service run identifier - pub run_ref: String, - /// Route identifier - pub route_id: RouteId, - /// Transport mode identifier - pub route_type: RouteType, - /// stop_id of final stop of run - pub final_stop_id: StopId, - /// Name of destination of run - pub destination_name: String, - /// Status of metropolitan train run; returns "scheduled" for other modes - pub status: String, - /// Direction of travel identifier - pub direction_id: DirectionId, - /// Chronological sequence of the trip/service run on the route in direction - /// Order ascendingly by this field to get chronological order (earliest first) of runs with the same route_id and direction_id - /// - /// What a mouthful - pub run_sequence: i32, - // The number of remaining skipped/express stations for the run/service from a stop - pub express_stop_count: i32, - // Position of the trip/service run. Available for some Bus, Nightrider and Train runs. - pub vehicle_position: Option, - // Descriptor of the trip/service run. Only available for some runs. - pub vehicle_descriptor: Option, - /// Geopath of the route - pub geopath: Vec, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct VehiclePosition { - /// Geographic coordinate of latitude of the vehicle when known. - pub latitude: Option, - /// Geographic coordinate of longitude of the vehicle when known. - pub longitude: Option, - /// CIS - Metro Train Vehicle Location Easting coordinate - pub easting: Option, - /// CIS - Metro Train Vehicle Location Northing coordinate - pub northing: Option, - /// CIS - Metro Train Vehicle Location Direction - pub direction: Option, - /// Compass bearing of the vehicle when known, clockwise from True North. - /// ie. 0 is North and 90 is East - pub bearing: Option, - /// Supplier of the vehicle position data - pub supplier: String, - /// Date and time that the vehicle position data was supplied - #[serde(deserialize_with = "de_iso_8601")] - #[serde(rename = "datetime_utc")] - pub datetime: NaiveDateTime, - /// CIS - Metro Train Vehicle Location data expiry time - pub expiry_time: Option, // TODO: Add a deser. No information in docs. -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum ServiceOperator { - #[serde(rename = "Metro Trains Melbourne")] - MetroTrainsMelbourne, - #[serde(rename = "Yarra Trams")] - YarraTrams, - #[serde(rename = "Ventura Bus Line")] - VenturaBusLine, - Other(String), -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct VehicleDescriptor { - /// Operator name of the vehicle such as "Metro Trains Melbourne", "Yarra Trams", "Ventura Bus Line", etc. - /// Only available for some runs. - pub operator: Option, - /// Operator identifier of the vehicle. Only available for some runs. - pub id: Option, - /// Indicator if the vehicle has a low floor. Only available for some tram runs. - pub low_floor: Option, - /// Indicator if the vehicle is air conditioned. Only available for some tram runs. - pub air_conditioned: Option, - /// Vehicle description such as "6 Car Comeng". Only available for some train runs. - pub description: Option, - /// Supplier of the vehicle descriptor data - pub supplier: String, - /// The length of the vehicle. Applies to CIS - Metro Trains - /// Meters? Feet? Who knows. - pub length: Option, -} - -#[derive(Serialize, Default)] -pub struct SearchOpts { - #[serde(skip_serializing_if = "Option::is_none")] - pub route_types: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub latitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub longitude: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub max_distance: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_addresses: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub include_outlets: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub match_stop_by_suburb: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub match_stop_by_gtfs_stop_id: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct ResultStop { - #[serde(flatten)] - pub stop: Stop, - pub routes: Vec, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct SearchResponse { - pub stops: Vec, - pub routes: Vec, - pub outlets: Vec, - pub status: Status, -} - -#[derive(Serialize, Default)] -pub struct StopsIdRouteTypeOpts { - /// Indicates if stop locaiton information should be returned - #[serde(rename = "stop_location")] - #[serde(skip_serializing_if = "Option::is_none")] - pub location: Option, - /// Indicates if stop amenities information should be returned - #[serde(rename = "stop_amenities")] - #[serde(skip_serializing_if = "Option::is_none")] - pub amenities: Option, - #[serde(rename = "stop_accessibility")] - #[serde(skip_serializing_if = "Option::is_none")] - pub accessibility: Option, - #[serde(rename = "stop_contact")] - #[serde(skip_serializing_if = "Option::is_none")] - pub contact: Option, - #[serde(rename = "stop_ticket")] - #[serde(skip_serializing_if = "Option::is_none")] - pub ticket: Option, - #[serde(rename = "stop_staffing")] - #[serde(skip_serializing_if = "Option::is_none")] - pub staffing: Option, - #[serde(rename = "stop_disruptions")] - #[serde(skip_serializing_if = "Option::is_none")] - pub disruptions: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopGps { - pub latitude: Decimal, - pub longitude: Decimal, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopLocation { - pub gps: StopGps, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopAmenityDetails { - pub toilet: bool, - pub taxi_rank: bool, - pub car_parking: bool, - pub cctv: bool, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopAccessibilityWheelchair { - pub accessible_ramp: bool, - pub parking: bool, - pub telephone: bool, - pub toilet: bool, - pub low_ticket_counter: bool, - pub manouvering: bool, - pub raised_platform: bool, - pub ramp: bool, - pub secondary_path: bool, - pub raised_platform_shelter: bool, - pub steep_ramp: bool, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopAccessibility { - pub lighting: bool, - pub platform_number: bool, - pub escalator: bool, - pub lift: bool, - pub stairs: bool, - pub stop_accessibility: bool, - pub tactile_ground_surface_indicator: bool, - pub waiting_room: bool, - pub wheelchair: StopAccessibilityWheelchair, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopStaffing { - pub fri_am_from: String, - pub fri_am_to: String, - pub fri_pm_from: String, - pub fri_pm_to: String, - pub mon_am_from: String, - pub mon_am_to: String, - pub mon_pm_from: String, - pub mon_pm_to: String, - pub ph_additional_text: String, - pub ph_from: String, - pub ph_to: String, - pub sat_am_from: String, - pub sat_am_to: String, - pub sat_pm_from: String, - pub sat_pm_to: String, - pub sun_am_from: String, - pub sun_am_to: String, - pub sun_pm_from: String, - pub sun_pm_to: String, - pub thu_am_from: String, - pub thu_am_to: String, - pub thu_pm_from: String, - pub thu_pm_to: String, - pub tue_am_from: String, - pub tue_am_to: String, - pub tue_pm_from: String, - pub tue_pm_to: String, - pub wed_am_from: String, - pub wed_am_to: String, - pub wed_pm_from: String, - pub wed_pm_to: String, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopDetails { - pub disruption_ids: Vec, - #[serde(rename = "stop_id")] - pub id: StopId, - pub station_type: String, - pub station_description: String, - pub route_type: RouteType, - // assumption, because it just says object - pub routes: Vec, - #[serde(rename = "stop_landmark")] - pub landmark: String, - #[serde(rename = "stop_name")] - pub name: String, - #[serde(rename = "stop_amenities")] - pub amenities: Option, - #[serde(rename = "stop_accessibility")] - pub accessibility: Option, - #[serde(rename = "stop_staffing")] - pub staffing: Option, - #[serde(rename = "stop_location")] - pub location: Option, -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StopResponse { - pub stop: StopDetails, - pub disruptions: Vec, - pub status: Status, -} diff --git a/v3 b/v3 new file mode 100644 index 0000000..46a70f2 --- /dev/null +++ b/v3 @@ -0,0 +1 @@ +{"swagger":"2.0","info":{"version":"v3","title":"PTV Timetable API - Version 3","description":"The PTV Timetable API provides direct access to Public Transport Victoria's public transport timetable data.\r\n\r\nThe API returns scheduled timetable, route and stop data for all metropolitan and regional train, tram and bus services in Victoria, including Night Network(Night Train and Night Tram data are included in metropolitan train and tram services data, respectively, whereas Night Bus is a separate route type).\r\n\r\nThe API also returns real-time data for metropolitan train, tram and bus services (where this data is made available to PTV), as well as disruption information, stop facility information, and access to myki ticket outlet data.\r\n\r\nThis Swagger is for Version 3 of the PTV Timetable API. By using this documentation you agree to comply with the licence and terms of service.\r\n\r\nTrain timetable data is updated daily, while the remaining data is updated weekly, taking into account any planned timetable changes (for example, due to holidays or planned disruptions). The PTV timetable API is the same API used by PTV for its apps. To access the most up to date data PTV has (including real-time data) you must use the API dynamically.\r\n\r\nYou can access the PTV Timetable API through a HTTP or HTTPS interface, as follows:\r\n\r\n base URL / version number / API name / query string\r\nThe base URL is either:\r\n * http://timetableapi.ptv.vic.gov.au\r\nor\r\n * https://timetableapi.ptv.vic.gov.au\r\n\r\nThe Swagger JSON file is available at http://timetableapi.ptv.vic.gov.au/swagger/docs/v3\r\n\r\nFrequently asked questions are available on the PTV website at http://ptv.vic.gov.au/apifaq\r\n\r\nLinks to the following information are also provided on the PTV website at http://ptv.vic.gov.au/ptv-timetable-api/\r\n* How to register for an API key and calculate a signature\r\n* PTV Timetable API V2 to V3 Migration Guide\r\n* PTV Timetable API Data Quality Statement\r\n\r\nAll information about how to use the API is in this documentation. PTV cannot provide technical support for the API.\r\n","termsOfService":"See http://ptv.vic.gov.au/ptv-timetable-api/","contact":{"name":"Public Transport Victoria","url":"http://ptv.vic.gov.au/digital"},"license":{"name":"Creative Commons Attribution 4.0 International","url":"https://creativecommons.org/licenses/by/4.0/"}},"host":"timetableapi.ptv.vic.gov.au","schemes":["https","http"],"paths":{"/v3/departures/route_type/{route_type}/stop/{stop_id}":{"get":{"tags":["Departures"],"summary":"View departures for all routes from a stop","operationId":"Departures_GetForStop","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_type","in":"path","description":"Number identifying transport mode; values returned via RouteTypes API","required":true,"type":"integer","format":"int32","enum":[0,1,2,3,4]},{"name":"stop_id","in":"path","description":"Identifier of stop; values returned by Stops API","required":true,"type":"integer","format":"int32"},{"name":"platform_numbers","in":"query","description":"Filter by platform number at stop","required":false,"type":"array","items":{"type":"integer","format":"int32"},"collectionFormat":"multi"},{"name":"direction_id","in":"query","description":"Filter by identifier of direction of travel; values returned by Directions API - /v3/directions/route/{route_id}","required":false,"type":"integer","format":"int32"},{"name":"gtfs","in":"query","description":"Indicates that stop_id parameter will accept \"GTFS stop_id\" data","required":false,"type":"boolean"},{"name":"date_utc","in":"query","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","required":false,"type":"string","format":"date-time"},{"name":"max_results","in":"query","description":"Maximum number of results returned","required":false,"type":"integer","format":"int32"},{"name":"include_cancelled","in":"query","description":"Indicates if cancelled services (if they exist) are returned (default = false) - metropolitan train only","required":false,"type":"boolean"},{"name":"look_backwards","in":"query","description":"Indicates if filtering runs (and their departures) to those that arrive at destination before date_utc (default = false). Requires max_results > 0.","required":false,"type":"boolean"},{"name":"expand","in":"query","description":"List of objects to be returned in full (i.e. expanded) - options include: All, Stop, Route, Run, Direction, Disruption, VehiclePosition, VehicleDescriptor or None.\r\nRun must be expanded to receive VehiclePosition and VehicleDescriptor information.","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,3,4,5,6,7,2147483647]},"collectionFormat":"multi"},{"name":"include_geopath","in":"query","description":"Indicates if the route geopath should be returned","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Service departures from the specified stop for all routes of the specified route type; departures are timetabled and real-time (if applicable).","schema":{"$ref":"#/definitions/V3.DeparturesResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/departures/route_type/{route_type}/stop/{stop_id}/route/{route_id}":{"get":{"tags":["Departures"],"summary":"View departures for a specific route from a stop","operationId":"Departures_GetForStopAndRoute","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_type","in":"path","description":"Number identifying transport mode; values returned via RouteTypes API","required":true,"type":"integer","format":"int32","enum":[0,1,2,3,4]},{"name":"stop_id","in":"path","description":"Identifier of stop; values returned by Stops API","required":true,"type":"integer","format":"int32"},{"name":"route_id","in":"path","description":"Identifier of route; values returned by Routes API - v3/routes","required":true,"type":"string"},{"name":"direction_id","in":"query","description":"Filter by identifier of direction of travel; values returned by Directions API - /v3/directions/route/{route_id}","required":false,"type":"integer","format":"int32"},{"name":"gtfs","in":"query","description":"Indicates that stop_id parameter will accept \"GTFS stop_id\" data","required":false,"type":"boolean"},{"name":"date_utc","in":"query","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","required":false,"type":"string","format":"date-time"},{"name":"max_results","in":"query","description":"Maximum number of results returned","required":false,"type":"integer","format":"int32"},{"name":"include_cancelled","in":"query","description":"Indicates if cancelled services (if they exist) are returned (default = false) - metropolitan train only","required":false,"type":"boolean"},{"name":"look_backwards","in":"query","description":"Indicates if filtering runs (and their departures) to those that arrive at destination before date_utc (default = false). Requires max_results > 0.","required":false,"type":"boolean"},{"name":"expand","in":"query","description":"List of objects to be returned in full (i.e. expanded) - options include: All, Stop, Route, Run, Direction, Disruption, VehiclePosition, VehicleDescriptor or None.\r\nRun must be expanded to receive VehiclePosition and VehicleDescriptor information.","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,3,4,5,6,7,2147483647]},"collectionFormat":"multi"},{"name":"include_geopath","in":"query","description":"Indicates if the route geopath should be returned","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Service departures from the specified stop for the specified route (and route type); departures are timetabled and real-time (if applicable).","schema":{"$ref":"#/definitions/V3.DeparturesResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/directions/route/{route_id}":{"get":{"tags":["Directions"],"summary":"View directions that a route travels in","operationId":"Directions_ForRoute","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_id","in":"path","description":"Identifier of route; values returned by Routes API - v3/routes","required":true,"type":"integer","format":"int32"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"The directions that a specified route travels in.","schema":{"$ref":"#/definitions/V3.DirectionsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/directions/{direction_id}":{"get":{"tags":["Directions"],"summary":"View all routes for a direction of travel","operationId":"Directions_ForDirection","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"direction_id","in":"path","description":"Identifier of direction of travel; values returned by Directions API - /v3/directions/route/{route_id}","required":true,"type":"integer","format":"int32"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All routes that travel in the specified direction.","schema":{"$ref":"#/definitions/V3.DirectionsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/directions/{direction_id}/route_type/{route_type}":{"get":{"tags":["Directions"],"summary":"View all routes of a particular type for a direction of travel","operationId":"Directions_ForDirectionAndType","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"direction_id","in":"path","description":"Identifier of direction of travel; values returned by Directions API - /v3/directions/route/{route_id}","required":true,"type":"integer","format":"int32"},{"name":"route_type","in":"path","description":"Number identifying transport mode; values returned via RouteTypes API","required":true,"type":"integer","format":"int32","enum":[0,1,2,3,4]},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All routes of the specified route type that travel in the specified direction.","schema":{"$ref":"#/definitions/V3.DirectionsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/disruptions":{"get":{"tags":["Disruptions"],"summary":"View all disruptions for all route types","operationId":"Disruptions_GetAllDisruptions","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_types","in":"query","description":"Filter by route_type; values returned via RouteTypes API","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,3,4]},"collectionFormat":"multi"},{"name":"disruption_modes","in":"query","description":"Filter by disruption_mode; values returned via v3/disruptions/modes API","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[1,2,3,4,5,7,8,9,10,11,12,13,14,100]},"collectionFormat":"multi"},{"name":"disruption_status","in":"query","description":"Filter by status of disruption","required":false,"type":"integer","format":"int32","enum":[0,1]},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All disruption information for all route types.","schema":{"$ref":"#/definitions/V3.DisruptionsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/disruptions/route/{route_id}":{"get":{"tags":["Disruptions"],"summary":"View all disruptions for a particular route","operationId":"Disruptions_GetDisruptionsByRoute","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_id","in":"path","description":"Identifier of route; values returned by Routes API - v3/routes","required":true,"type":"integer","format":"int32"},{"name":"disruption_status","in":"query","description":"Filter by status of disruption","required":false,"type":"integer","format":"int32","enum":[0,1]},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All disruption information (if any exists) for the specified route.","schema":{"$ref":"#/definitions/V3.DisruptionsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/disruptions/route/{route_id}/stop/{stop_id}":{"get":{"tags":["Disruptions"],"summary":"View all disruptions for a particular route and stop","operationId":"Disruptions_GetDisruptionsByRouteAndStop","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_id","in":"path","description":"Identifier of route; values returned by Routes API - v3/routes","required":true,"type":"integer","format":"int32"},{"name":"stop_id","in":"path","description":"Identifier of stop; values returned by Stops API - v3/stops","required":true,"type":"integer","format":"int32"},{"name":"disruption_status","in":"query","description":"Filter by status of disruption","required":false,"type":"integer","format":"int32","enum":[0,1]},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All disruption information (if any exists) for the specified route and stop.","schema":{"$ref":"#/definitions/V3.DisruptionsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/disruptions/stop/{stop_id}":{"get":{"tags":["Disruptions"],"summary":"View all disruptions for a particular stop","operationId":"Disruptions_GetDisruptionsByStop","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"stop_id","in":"path","description":"Identifier of stop; values returned by Stops API - v3/stops","required":true,"type":"integer","format":"int32"},{"name":"disruption_status","in":"query","description":"Filter by status of disruption","required":false,"type":"integer","format":"int32","enum":[0,1]},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All disruption information (if any exists) for the specified stop.","schema":{"$ref":"#/definitions/V3.DisruptionsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/disruptions/{disruption_id}":{"get":{"tags":["Disruptions"],"summary":"View a specific disruption","operationId":"Disruptions_GetDisruptionById","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"disruption_id","in":"path","description":"Identifier of disruption; values returned by Disruptions API - /v3/disruptions OR /v3/disruptions/route/{route_id}","required":true,"type":"integer","format":"int64"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Disruption information for the specified disruption ID.","schema":{"$ref":"#/definitions/V3.DisruptionResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/disruptions/modes":{"get":{"tags":["Disruptions"],"summary":"Get all disruption modes","operationId":"Disruptions_GetDisruptionModes","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Disruption specific modes","schema":{"$ref":"#/definitions/V3.DisruptionModesResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/fare_estimate/min_zone/{minZone}/max_zone/{maxZone}":{"get":{"tags":["FareEstimate"],"summary":"Estimate a fare by zone","operationId":"FareEstimate_GetFareEstimateByZone","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"minZone","in":"path","description":"Minimum Zone travelled through ie. 1","required":true,"type":"integer","format":"int32"},{"name":"maxZone","in":"path","description":"Maximum Zone travelled through id. 6","required":true,"type":"integer","format":"int32"},{"name":"journey_touch_on_utc","in":"query","description":"JourneyTouchOnUtc in format yyyy-M-d h:m (e.g 2016-5-31 16:53).","required":false,"type":"string","format":"date-time"},{"name":"journey_touch_off_utc","in":"query","description":"JourneyTouchOffUtc in format yyyy-M-d h:m (e.g 2016-5-31 16:53).","required":false,"type":"string","format":"date-time"},{"name":"is_journey_in_free_tram_zone","in":"query","required":false,"type":"boolean"},{"name":"travelled_route_types","in":"query","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,3,4]},"collectionFormat":"multi"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Resultant set fare estimates","schema":{"$ref":"#/definitions/V3.FareEstimateResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/outlets":{"get":{"tags":["Outlets"],"summary":"List all ticket outlets","operationId":"Outlets_GetAllOutlets","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"max_results","in":"query","description":"Maximum number of results returned (default = 30)","required":false,"type":"integer","format":"int32"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Ticket outlets.","schema":{"$ref":"#/definitions/V3.OutletResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/outlets/location/{latitude},{longitude}":{"get":{"tags":["Outlets"],"summary":"List ticket outlets near a specific location","operationId":"Outlets_GetOutletsByGeolocation","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"latitude","in":"path","description":"Geographic coordinate of latitude","required":true,"type":"number","format":"float"},{"name":"longitude","in":"path","description":"Geographic coordinate of longitude","required":true,"type":"number","format":"float"},{"name":"max_distance","in":"query","description":"Filter by maximum distance (in metres) from location specified via latitude and longitude parameters (default = 300)","required":false,"type":"number","format":"double"},{"name":"max_results","in":"query","description":"Maximum number of results returned (default = 30)","required":false,"type":"integer","format":"int32"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Ticket outlets near the specified location.","schema":{"$ref":"#/definitions/V3.OutletGeolocationResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/pattern/run/{run_ref}/route_type/{route_type}":{"get":{"tags":["Patterns"],"summary":"View the stopping pattern for a specific trip/service run","operationId":"Patterns_GetPatternByRun","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"run_ref","in":"path","description":"The run_ref is the identifier of a run as returned by the departures/* and runs/* endpoints. WARNING, run_id is deprecated. Use run_ref instead.","required":true,"type":"string"},{"name":"route_type","in":"path","description":"Number identifying transport mode; values returned via RouteTypes API","required":true,"type":"integer","format":"int32","enum":[0,1,2,3,4]},{"name":"expand","in":"query","description":"List of objects to be returned in full (i.e. expanded) - options include: All, Stop, Route, Run, Direction, Disruption, VehiclePosition, VehicleDescriptor and None. Default is Disruption. Run must be expanded to receive VehiclePosition and VehicleDescriptor information.","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,3,4,5,6,7,2147483647]},"collectionFormat":"multi"},{"name":"stop_id","in":"query","description":"Filter by stop_id; values returned by Stops API","required":false,"type":"integer","format":"int32"},{"name":"date_utc","in":"query","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","required":false,"type":"string","format":"date-time"},{"name":"include_skipped_stops","in":"query","description":"Include any skipped stops in a stopping pattern. Defaults to false.","required":false,"type":"boolean"},{"name":"include_geopath","in":"query","description":"Indicates if geopath data will be returned (default = false)","required":false,"type":"boolean"},{"name":"include_advertised_interchange","in":"query","description":"Indicates whether data related to interchanges should be included in the response (default = false)\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"The stopping pattern of the specified run_ref and route type. (NOTE: the departure sequence field should be used to sort departures in chronological order, however it is not always N+1 or N-1 of the previous or following departure. e.g 100, 200, 250, 300 instead of 1, 2, 3, 4)","schema":{"$ref":"#/definitions/V3.StoppingPattern"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/routes":{"get":{"tags":["Routes"],"summary":"View route names and numbers for all routes","operationId":"Routes_OneOrMoreRoutes","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_types","in":"query","description":"Filter by route_type; values returned via RouteTypes API","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,3,4]},"collectionFormat":"multi"},{"name":"route_name","in":"query","description":"Filter by name of route (accepts partial route name matches)","required":false,"type":"string"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Route names and numbers for all routes of all route types.","schema":{"$ref":"#/definitions/V3.RouteResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/routes/{route_id}":{"get":{"tags":["Routes"],"summary":"View route name and number for specific route ID","operationId":"Routes_RouteFromId","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_id","in":"path","description":"Identifier of route; values returned by Departures, Directions and Disruptions APIs","required":true,"type":"integer","format":"int32"},{"name":"include_geopath","in":"query","description":"Indicates kif geopath data will be returned (default = false)","required":false,"type":"boolean"},{"name":"geopath_utc","in":"query","description":"Filter geopaths by date (ISO 8601 UTC format) (default = current date)","required":false,"type":"string","format":"date-time"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"The route name and number for the specified route ID.","schema":{"$ref":"#/definitions/V3.RouteResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/route_types":{"get":{"tags":["RouteTypes"],"summary":"View all route types and their names","operationId":"RouteTypes_GetRouteTypes","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All route types (i.e. identifiers of transport modes) and their names.","schema":{"$ref":"#/definitions/V3.RouteTypesResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/runs/route/{route_id}":{"get":{"tags":["Runs"],"summary":"View all trip/service runs for a specific route ID","operationId":"Runs_ForRoute","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_id","in":"path","description":"Identifier of route; values returned by Routes API - v3/routes.","required":true,"type":"integer","format":"int32"},{"name":"expand","in":"query","description":"List of objects to be returned in full (i.e. expanded) - options include: All, VehiclePosition, VehicleDescriptor, or None. Default is None.","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,2147483647]},"collectionFormat":"multi"},{"name":"date_utc","in":"query","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","required":false,"type":"string","format":"date-time"},{"name":"include_advertised_interchange","in":"query","description":"Indicates whether data related to interchanges should be included in the response (default = false).\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All trip/service run details for the specified route ID.","schema":{"$ref":"#/definitions/V3.RunsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/runs/route/{route_id}/route_type/{route_type}":{"get":{"tags":["Runs"],"summary":"View all trip/service runs for a specific route ID and route type","operationId":"Runs_ForRouteAndRouteType","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_id","in":"path","description":"Identifier of route; values returned by Routes API - v3/routes.","required":true,"type":"integer","format":"int32"},{"name":"route_type","in":"path","description":"Number identifying transport mode; values returned via RouteTypes API","required":true,"type":"integer","format":"int32","enum":[0,1,2,3,4]},{"name":"expand","in":"query","description":"List of objects to be returned in full (i.e. expanded) - options include: All, VehiclePosition, VehicleDescriptor, or None. Default is None.","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,2147483647]},"collectionFormat":"multi"},{"name":"date_utc","in":"query","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","required":false,"type":"string","format":"date-time"},{"name":"include_advertised_interchange","in":"query","description":"Indicates whether data related to interchanges should be included in the response (default = false).\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All trip/service run details for the specified route ID and route type.","schema":{"$ref":"#/definitions/V3.RunsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/runs/{run_ref}":{"get":{"tags":["Runs"],"summary":"View all trip/service runs for a specific run_ref","operationId":"Runs_ForRun","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"run_ref","in":"path","description":"The run_ref is the identifier of a run as returned by the departures/* and runs/* endpoints. WARNING, run_id is deprecated. Use run_ref instead.","required":true,"type":"string"},{"name":"include_geopath","in":"query","description":"Indicates if geopath data will be returned (default = false)","required":false,"type":"boolean"},{"name":"expand","in":"query","description":"List of objects to be returned in full (i.e. expanded) - options include: All, VehiclePosition, VehicleDescriptor, or None. Default is None.","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,2147483647]},"collectionFormat":"multi"},{"name":"date_utc","in":"query","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","required":false,"type":"string","format":"date-time"},{"name":"include_advertised_interchange","in":"query","description":"Indicates whether data related to interchanges should be included in the response (default = false).\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All trip/service run details for the specified run_ref.","schema":{"$ref":"#/definitions/V3.RunsResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/runs/{run_ref}/route_type/{route_type}":{"get":{"tags":["Runs"],"summary":"View the trip/service run for a specific run_ref and route type","operationId":"Runs_ForRunAndRouteType","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"run_ref","in":"path","description":"The run_ref is the identifier of a run as returned by the departures/* and runs/* endpoints. WARNING, run_id is deprecated. Use run_ref instead.","required":true,"type":"string"},{"name":"route_type","in":"path","description":"Number identifying transport mode; values returned via RouteTypes API","required":true,"type":"integer","format":"int32","enum":[0,1,2,3,4]},{"name":"expand","in":"query","description":"List of objects to be returned in full (i.e. expanded) - options include: All, VehiclePosition, VehicleDescriptor, or None. Default is None.","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,2147483647]},"collectionFormat":"multi"},{"name":"date_utc","in":"query","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","required":false,"type":"string","format":"date-time"},{"name":"include_geopath","in":"query","description":"Indicates if geopath data will be returned (default = false)","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"The trip/service run details for the run_ref and route type specified.","schema":{"$ref":"#/definitions/V3.RunResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/search/{search_term}":{"get":{"tags":["Search"],"summary":"View stops, routes and myki ticket outlets that match the search term","operationId":"Search_Search","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"search_term","in":"path","description":"Search text (note: if search text is numeric and/or less than 3 characters, the API will only return routes)","required":true,"type":"string"},{"name":"route_types","in":"query","description":"Filter by route_type; values returned via RouteTypes API (note: stops and routes are ordered by route_types specified)","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,3,4]},"collectionFormat":"multi"},{"name":"latitude","in":"query","description":"Filter by geographic coordinate of latitude","required":false,"type":"number","format":"float"},{"name":"longitude","in":"query","description":"Filter by geographic coordinate of longitude","required":false,"type":"number","format":"float"},{"name":"max_distance","in":"query","description":"Filter by maximum distance (in metres) from location specified via latitude and longitude parameters","required":false,"type":"number","format":"float"},{"name":"include_addresses","in":"query","description":"Placeholder for future development; currently unavailable","required":false,"type":"boolean"},{"name":"include_outlets","in":"query","description":"Indicates if outlets will be returned in response (default = true)","required":false,"type":"boolean"},{"name":"match_stop_by_suburb","in":"query","description":"Indicates whether to find stops by suburbs in the search term (default = true)","required":false,"type":"boolean"},{"name":"match_route_by_suburb","in":"query","description":"Indicates whether to find routes by suburbs in the search term (default = true)","required":false,"type":"boolean"},{"name":"match_stop_by_gtfs_stop_id","in":"query","description":"Indicates whether to search for stops according to a metlink stop ID (default = false)","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Stops, routes and myki ticket outlets that contain the search term (note: stops and routes are ordered by route_type by default).","schema":{"$ref":"#/definitions/V3.SearchResult"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/stops/{stop_id}/route_type/{route_type}":{"get":{"tags":["Stops"],"summary":"View facilities at a specific stop (Metro and V/Line stations only)","operationId":"Stops_StopDetails","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"stop_id","in":"path","description":"Identifier of stop; values returned by Stops API","required":true,"type":"integer","format":"int32"},{"name":"route_type","in":"path","description":"Number identifying transport mode; values returned via RouteTypes API","required":true,"type":"integer","format":"int32","enum":[0,1,2,3,4]},{"name":"stop_location","in":"query","description":"Indicates if stop location information will be returned (default = false)","required":false,"type":"boolean"},{"name":"stop_amenities","in":"query","description":"Indicates if stop amenity information will be returned (default = false)","required":false,"type":"boolean"},{"name":"stop_accessibility","in":"query","description":"Indicates if stop accessibility information will be returned (default = false)","required":false,"type":"boolean"},{"name":"stop_contact","in":"query","description":"Indicates if stop contact information will be returned (default = false)","required":false,"type":"boolean"},{"name":"stop_ticket","in":"query","description":"Indicates if stop ticket information will be returned (default = false)","required":false,"type":"boolean"},{"name":"gtfs","in":"query","description":"Incdicates whether the stop_id is a GTFS ID or not","required":false,"type":"boolean"},{"name":"stop_staffing","in":"query","description":"Indicates if stop staffing information will be returned (default = false)","required":false,"type":"boolean"},{"name":"stop_disruptions","in":"query","description":"Indicates if stop disruption information will be returned (default = false)","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"Stop location, amenity and accessibility facility information for the specified stop (metropolitan and V/Line stations only).","schema":{"$ref":"#/definitions/V3.StopResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/stops/route/{route_id}/route_type/{route_type}":{"get":{"tags":["Stops"],"summary":"View all stops on a specific route","operationId":"Stops_StopsForRoute","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"route_id","in":"path","description":"Identifier of route; values returned by Routes API - v3/routes","required":true,"type":"integer","format":"int32"},{"name":"route_type","in":"path","description":"Number identifying transport mode; values returned via RouteTypes API","required":true,"type":"integer","format":"int32","enum":[0,1,2,3,4]},{"name":"direction_id","in":"query","description":"Direction for which the stops need to be returned","required":false,"type":"integer","format":"int32"},{"name":"stop_disruptions","in":"query","description":"Flag to specify whether disruptions should be included in the response","required":false,"type":"boolean"},{"name":"include_geopath","in":"query","description":"Flag to specify whether geo_path should be included in the response","required":false,"type":"boolean"},{"name":"geopath_utc","in":"query","description":"Filter geopaths by date (ISO 8601 UTC format) (default = current date)","required":false,"type":"string","format":"date-time"},{"name":"include_advertised_interchange","in":"query","description":"Flag to specify whether additional stops for interchanges should be included in the response. Note-: To make use of this flag please pass in direction_id.","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All stops on the specified route.","schema":{"$ref":"#/definitions/V3.StopsOnRouteResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}},"/v3/stops/location/{latitude},{longitude}":{"get":{"tags":["Stops"],"summary":"View all stops near a specific location","operationId":"Stops_StopsByGeolocation","consumes":[],"produces":["application/json","text/json"],"parameters":[{"name":"latitude","in":"path","description":"Geographic coordinate of latitude","required":true,"type":"number","format":"float"},{"name":"longitude","in":"path","description":"Geographic coordinate of longitude","required":true,"type":"number","format":"float"},{"name":"route_types","in":"query","description":"Filter by route_type; values returned via RouteTypes API","required":false,"type":"array","items":{"type":"integer","format":"int32","enum":[0,1,2,3,4]},"collectionFormat":"multi"},{"name":"max_results","in":"query","description":"Maximum number of results returned (default = 30)","required":false,"type":"integer","format":"int32"},{"name":"max_distance","in":"query","description":"Filter by maximum distance (in metres) from location specified via latitude and longitude parameters (default = 300)","required":false,"type":"number","format":"double"},{"name":"stop_disruptions","in":"query","description":"Indicates if stop disruption information will be returned (default = false)","required":false,"type":"boolean"},{"name":"token","in":"query","description":"Please ignore","required":false,"type":"string"},{"name":"devid","in":"query","description":"Your developer id","required":false,"type":"string"},{"name":"signature","in":"query","description":"Authentication signature for request","required":false,"type":"string"}],"responses":{"200":{"description":"All stops near the specified location.","schema":{"$ref":"#/definitions/V3.StopsByDistanceResponse"}},"400":{"description":"Invalid Request","schema":{"$ref":"#/definitions/V3.ErrorResponse"}},"403":{"description":"Access Denied","schema":{"$ref":"#/definitions/V3.ErrorResponse"}}}}}},"definitions":{"V3.CacheKeysRemoveResponse":{"type":"object","properties":{"keys":{"description":"Status of key","type":"object","additionalProperties":{"$ref":"#/definitions/V3.CacheKeyRemoved"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.CacheKeyRemoved":{"type":"object","properties":{"Removed":{"type":"boolean"}}},"V3.Status":{"type":"object","properties":{"version":{"description":"API Version number","type":"string"},"health":{"format":"int32","description":"API system health status (0=offline, 1=online)","enum":[0,1],"type":"integer"}}},"V3.ErrorResponse":{"description":"An error response","type":"object","properties":{"message":{"description":"Error message","type":"string"},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.CacheKeyResponse":{"type":"object","properties":{"keys":{"description":"Status of key","type":"object","additionalProperties":{"$ref":"#/definitions/V3.CacheItem"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.CacheItem":{"type":"object","properties":{"Type":{"type":"string"},"Value":{"type":"object"}}},"V3.DeparturesBroadParameters":{"type":"object","properties":{"platform_numbers":{"description":"Filter by platform number at stop","type":"array","items":{"format":"int32","type":"integer"}},"direction_id":{"format":"int32","description":"Filter by identifier of direction of travel; values returned by Directions API - /v3/directions/route/{route_id}","type":"integer"},"gtfs":{"description":"Indicates that stop_id parameter will accept \"GTFS stop_id\" data","type":"boolean"},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"max_results":{"format":"int32","description":"Maximum number of results returned","type":"integer"},"include_cancelled":{"description":"Indicates if cancelled services (if they exist) are returned (default = false) - metropolitan train only","type":"boolean"},"look_backwards":{"description":"Indicates if filtering runs (and their departures) to those that arrive at destination before date_utc (default = false). Requires max_results > 0.","type":"boolean"},"expand":{"description":"List of objects to be returned in full (i.e. expanded) - options include: All, Stop, Route, Run, Direction, Disruption, VehiclePosition, VehicleDescriptor or None.\r\nRun must be expanded to receive VehiclePosition and VehicleDescriptor information.","type":"array","items":{"format":"int32","enum":[0,1,2,3,4,5,6,7,2147483647],"type":"integer"}},"include_geopath":{"description":"Indicates if the route geopath should be returned","type":"boolean"}}},"V3.DeparturesResponse":{"type":"object","properties":{"departures":{"description":"Timetabled and real-time service departures","type":"array","items":{"$ref":"#/definitions/V3.Departure"}},"stops":{"description":"A train station, tram stop, bus stop, regional coach stop or Night Bus stop","type":"object","additionalProperties":{"$ref":"#/definitions/V3.StopModel"}},"routes":{"description":"Train lines, tram routes, bus routes, regional coach routes, Night Bus routes","type":"object","additionalProperties":{"type":"object"}},"runs":{"description":"Individual trips/services of a route","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Run"}},"directions":{"description":"Directions of travel of route","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Direction"}},"disruptions":{"description":"Disruption information applicable to relevant routes or stops","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Disruption"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.Departure":{"type":"object","properties":{"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"run_id":{"format":"int32","description":"Numeric trip/service run identifier. Defaults to -1 when run identifier is Alphanumeric","type":"integer","readOnly":true},"run_ref":{"description":"Alphanumeric trip/service run identifier","type":"string"},"direction_id":{"format":"int32","description":"Direction of travel identifier","type":"integer"},"disruption_ids":{"description":"Disruption information identifier(s)","type":"array","items":{"format":"int64","type":"integer"}},"scheduled_departure_utc":{"format":"date-time","description":"Scheduled (i.e. timetabled) departure time and date in ISO 8601 UTC format","type":"string"},"estimated_departure_utc":{"format":"date-time","description":"Real-time estimate of departure time and date in ISO 8601 UTC format","type":"string"},"at_platform":{"description":"Indicates if the metropolitan train service is at the platform at the time of query; returns false for other modes","type":"boolean"},"platform_number":{"description":"Platform number at stop (metropolitan train only; returns null for other modes)","type":"string"},"flags":{"description":"Flag indicating special condition for run (e.g. RR Reservations Required, GC Guaranteed Connection, DOO Drop Off Only, PUO Pick Up Only, MO Mondays only, TU Tuesdays only, WE Wednesdays only, TH Thursdays only, FR Fridays only, SS School days only; ignore E flag)","type":"string"},"departure_sequence":{"format":"int32","description":"Chronological sequence for the departures in a run. Order ascendingly by this field to get chronological order (earliest first) of departures with the same run_ref. NOTE, this field is not always N+1 or N-1 of the previous or following departure. e.g 100, 200, 250, 300 instead of 1, 2, 3, 4","type":"integer"},"departure_note":{"description":"Additional descriptive text associated with the departure","type":"string"}}},"V3.StopModel":{"type":"object","properties":{"stop_distance":{"format":"float","description":"Distance of stop from input location (in metres); returns 0 if no location is input","type":"number"},"stop_suburb":{"description":"suburb of stop","type":"string"},"stop_name":{"description":"Name of stop","type":"string"},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"stop_latitude":{"format":"float","description":"Geographic coordinate of latitude at stop","type":"number"},"stop_longitude":{"format":"float","description":"Geographic coordinate of longitude at stop","type":"number"},"stop_landmark":{"description":"Landmark in proximity of stop","type":"string"},"stop_sequence":{"format":"int32","description":"Sequence of the stop on the route/run; return 0 when route_id or run_id not specified. Order ascendingly by this field (when non zero) to get physical order (earliest first) of stops on the route_id/run_id.","type":"integer"}}},"V3.Run":{"type":"object","properties":{"run_id":{"format":"int32","description":"Numeric trip/service run identifier. Defaults to -1 when run identifier is Alphanumeric","type":"integer","readOnly":true},"run_ref":{"description":"Alphanumeric trip/service run identifier","type":"string"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"final_stop_id":{"format":"int32","description":"stop_id of final stop of run","type":"integer"},"destination_name":{"description":"Name of destination of run","type":"string"},"status":{"description":"Status of metropolitan train run; returns \"scheduled\" for other modes","type":"string"},"direction_id":{"format":"int32","description":"Direction of travel identifier","type":"integer"},"run_sequence":{"format":"int32","description":"Chronological sequence of the trip/service run on the route in direction. Order ascendingly by this field to get chronological order (earliest first) of runs with the same route_id and direction_id.","type":"integer"},"express_stop_count":{"format":"int32","description":"The number of remaining skipped/express stations for the run/service from a stop","type":"integer"},"vehicle_position":{"$ref":"#/definitions/V3.VehiclePosition","description":"Position of the trip/service run. Available for some Bus, Nightrider and Train runs. May be null."},"vehicle_descriptor":{"$ref":"#/definitions/V3.VehicleDescriptor","description":"Descriptor of the trip/service run. Only available for some runs. May be null."},"geopath":{"description":"Geopath of the route","type":"array","items":{"type":"object"}},"interchange":{"$ref":"#/definitions/V3.Interchange","description":"Connection link between two runs"},"run_note":{"description":"Additional descriptive text associated with the run","type":"string"},"externalService":{"format":"int32","enum":[0,1,2,3,4,5,6,7,8,9,10],"type":"integer"}}},"V3.Direction":{"type":"object","properties":{"direction_id":{"format":"int32","description":"Direction of travel identifier","type":"integer"},"direction_name":{"description":"Name of direction of travel","type":"string"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"}}},"V3.Disruption":{"type":"object","properties":{"disruption_id":{"format":"int64","description":"Disruption information identifier","type":"integer"},"title":{"description":"Headline title summarising disruption information","type":"string"},"url":{"description":"URL of relevant article on PTV website","type":"string"},"description":{"description":"Description of the disruption","type":"string"},"disruption_status":{"description":"Status of the disruption (e.g. \"Planned\", \"Current\")","type":"string"},"disruption_type":{"description":"Type of disruption","type":"string"},"published_on":{"format":"date-time","description":"Date and time disruption information is published on PTV website, in ISO 8601 UTC format","type":"string"},"last_updated":{"format":"date-time","description":"Date and time disruption information was last updated by PTV, in ISO 8601 UTC format","type":"string"},"from_date":{"format":"date-time","description":"Date and time at which disruption begins, in ISO 8601 UTC format","type":"string"},"to_date":{"format":"date-time","description":"Date and time at which disruption ends, in ISO 8601 UTC format (returns null if unknown)","type":"string"},"routes":{"description":"Route relevant to a disruption (if applicable)","type":"array","items":{"$ref":"#/definitions/V3.DisruptionRoute"}},"stops":{"description":"Stop relevant to a disruption (if applicable)","type":"array","items":{"$ref":"#/definitions/V3.DisruptionStop"}},"colour":{"type":"string"},"display_on_board":{"type":"boolean"},"display_status":{"type":"boolean"}}},"V3.VehiclePosition":{"type":"object","properties":{"latitude":{"format":"double","description":"Geographic coordinate of latitude of the vehicle when known. May be null.\r\nOnly available for some bus runs.","type":"number"},"longitude":{"format":"double","description":"Geographic coordinate of longitude of the vehicle when known. \r\nOnly available for some bus runs.","type":"number"},"easting":{"format":"double","description":"CIS - Metro Train Vehicle Location Easting coordinate","type":"number"},"northing":{"format":"double","description":"CIS - Metro Train Vehicle Location Northing coordinate","type":"number"},"direction":{"description":"CIS - Metro Train Vehicle Location Direction","type":"string"},"bearing":{"format":"double","description":"Compass bearing of the vehicle when known, clockwise from True North, i.e., 0 is North and 90 is East. May be null.\r\nOnly available for some bus runs.","type":"number"},"supplier":{"description":"Supplier of vehicle position data.","type":"string"},"datetime_utc":{"format":"date-time","description":"Date and time that the vehicle position data was supplied.","type":"string"},"expiry_time":{"format":"date-time","description":"CIS - Metro Train Vehicle Location data expiry time","type":"string"}}},"V3.VehicleDescriptor":{"type":"object","properties":{"operator":{"description":"Operator name of the vehicle such as \"Metro Trains Melbourne\", \"Yarra Trams\", \"Ventura Bus Line\", \"CDC\" or \"Sita Bus Lines\" . May be null/empty.\r\nOnly available for train, tram, v/line and some bus runs.","type":"string"},"id":{"description":"Operator identifier of the vehicle such as \"26094\". May be null/empty. Only available for some tram and bus runs.","type":"string"},"low_floor":{"description":"Indicator if vehicle has a low floor. May be null. Only available for some tram runs.","type":"boolean"},"air_conditioned":{"description":"Indicator if vehicle is air conditioned. May be null. Only available for some tram runs.","type":"boolean"},"description":{"description":"Vehicle description such as \"6 Car Comeng\", \"6 Car Xtrapolis\", \"3 Car Comeng\", \"6 Car Siemens\", \"3 Car Siemens\". May be null/empty.\r\nOnly available for some metropolitan train runs.","type":"string"},"supplier":{"description":"Supplier of vehicle descriptor data.","type":"string"},"length":{"description":"The length of the vehicle. Applies to CIS - Metro Trains","type":"string"}}},"V3.Interchange":{"description":"When two runs connect","type":"object","properties":{"feeder":{"$ref":"#/definitions/V3.InterchangeRun","description":"The run that a vehicle was previously on"},"distributor":{"$ref":"#/definitions/V3.InterchangeRun","description":"The run that a vehicle will become"}}},"V3.DisruptionRoute":{"type":"object","properties":{"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"route_name":{"description":"Name of route","type":"string"},"route_number":{"description":"Route number presented to public (i.e. not route_id)","type":"string"},"route_gtfs_id":{"description":"GTFS Identifer of the route","type":"string"},"direction":{"$ref":"#/definitions/V3.DisruptionDirection","description":"Direction of travel relevant to a disruption (if applicable)"}}},"V3.DisruptionStop":{"type":"object","properties":{"stop_id":{"format":"int32","type":"integer"},"stop_name":{"type":"string"}}},"V3.InterchangeRun":{"description":"Feeder / Distributor details","type":"object","properties":{"run_ref":{"description":"Run Identifier","type":"string"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"advertised":{"description":"Indicates whether the interchange information is shown to end users","type":"boolean"},"direction_id":{"format":"int32","description":"Indicates whether the direction for this run","type":"integer"},"destination_name":{"description":"Indicates the destination name","type":"string"}}},"V3.DisruptionDirection":{"type":"object","properties":{"route_direction_id":{"format":"int32","description":"Route and direction of travel combination identifier","type":"integer"},"direction_id":{"format":"int32","description":"Direction of travel identifier","type":"integer"},"direction_name":{"description":"Name of direction of travel","type":"string"},"service_time":{"description":"Time of service to which disruption applies, in 24 hour clock format (HH:MM:SS) AEDT/AEST; returns null if disruption applies to multiple (or no) services","type":"string"}}},"V3.DeparturesSpecificParameters":{"type":"object","properties":{"direction_id":{"format":"int32","description":"Filter by identifier of direction of travel; values returned by Directions API - /v3/directions/route/{route_id}","type":"integer"},"gtfs":{"description":"Indicates that stop_id parameter will accept \"GTFS stop_id\" data","type":"boolean"},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"max_results":{"format":"int32","description":"Maximum number of results returned","type":"integer"},"include_cancelled":{"description":"Indicates if cancelled services (if they exist) are returned (default = false) - metropolitan train only","type":"boolean"},"look_backwards":{"description":"Indicates if filtering runs (and their departures) to those that arrive at destination before date_utc (default = false). Requires max_results > 0.","type":"boolean"},"expand":{"description":"List of objects to be returned in full (i.e. expanded) - options include: All, Stop, Route, Run, Direction, Disruption, VehiclePosition, VehicleDescriptor or None.\r\nRun must be expanded to receive VehiclePosition and VehicleDescriptor information.","type":"array","items":{"format":"int32","enum":[0,1,2,3,4,5,6,7,2147483647],"type":"integer"}},"include_geopath":{"description":"Indicates if the route geopath should be returned","type":"boolean"}}},"V3.RouteDeparturesSpecificParameters":{"type":"object","properties":{"train_scheduled_timetables":{"description":"DEPRECATED - use `scheduled_timetables` instead","type":"boolean"},"scheduled_timetables":{"description":"When set to true, all timetable information returned by Chronos will be sourced from the scheduled timetables,\r\nwhile when set to false (default state), the operational timetables will be used where available.","type":"boolean"},"include_advertised_interchange":{"description":"Indicates whether data related to interchanges should be included in the response (default = false)\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","type":"boolean"},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"max_results":{"format":"int32","description":"Maximum number of results returned","type":"integer"},"include_cancelled":{"description":"Indicates if cancelled services (if they exist) are returned (default = false) - metropolitan train only","type":"boolean"},"look_backwards":{"description":"Indicates if filtering runs (and their departures) to those that arrive at destination before date_utc (default = false). Requires max_results > 0.","type":"boolean"},"expand":{"description":"List of objects to be returned in full (i.e. expanded) - options include: All, Stop, Route, Run, Direction, Disruption, VehiclePosition, VehicleDescriptor or None.\r\nRun must be expanded to receive VehiclePosition and VehicleDescriptor information.","type":"array","items":{"format":"int32","enum":[0,1,2,3,4,5,6,7,2147483647],"type":"integer"}},"include_geopath":{"description":"Indicates if the route geopath should be returned","type":"boolean"}}},"V3.BulkDeparturesRequest":{"required":["requests"],"type":"object","properties":{"requests":{"description":"Collection of departure requests","type":"array","items":{"$ref":"#/definitions/V3.StopDepartureRequest"}},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"look_backwards":{"description":"Indicates if filtering runs (and their departures) to those that arrive at destination before date_utc (default = false). Requires max_results > 0.","type":"boolean"},"include_cancelled":{"description":"Indicates if cancelled services (if they exist) are returned (default = false) - metropolitan train only","type":"boolean"},"include_geopath":{"description":"Indicates if the route geopath should be returned","type":"boolean"},"expand":{"description":"List objects to be returned in full (i.e. expanded) - options include: all, stop, route, run, direction, disruption, none","type":"array","items":{"format":"int32","enum":[0,1,2,3,4,5,6,7,2147483647],"type":"integer"}},"include_advertised_interchange":{"description":"Indicates whether data related to interchanges should be included in the response (default = false)\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","type":"boolean"}}},"V3.StopDepartureRequest":{"required":["route_directions"],"type":"object","properties":{"route_type":{"format":"int32","description":"Number identifying transport mode; values returned via RouteTypes API","enum":[0,1,2,3,4],"type":"integer"},"stop_id":{"format":"int32","description":"Identifier of stop; values returned by Stops API","maximum":2147483647,"minimum":0,"type":"integer"},"max_results":{"format":"int32","description":"Maximum number of results returned","maximum":2147483647,"minimum":0,"type":"integer"},"gtfs":{"description":"Indicates that stop_id parameter will accept \"GTFS stop_id\" data and route_directions[x].route_id parameters will accept route_gtfs_id data","type":"boolean"},"route_directions":{"description":"The route directions to find departures for at this stop.","type":"array","items":{"$ref":"#/definitions/V3.StopDepartureRequestRouteDirection"}}}},"V3.StopDepartureRequestRouteDirection":{"required":["direction_name"],"type":"object","properties":{"route_id":{"description":"Identifier of route; values returned by Routes API - v3/routes","type":"string"},"direction_id":{"format":"int32","description":"Direction of travel identifier; values returned by Directions API - v3/directions","maximum":2147483647,"minimum":0,"type":"integer"},"direction_name":{"description":"Name of direction of travel; values returned by Directions API - v3/directions","type":"string"}}},"V3.BulkDeparturesResponse":{"type":"object","properties":{"responses":{"description":"Contains departures for the requested stop and route(s). It includes details as to the route_direction and whether it is still valid.","type":"array","items":{"$ref":"#/definitions/V3.BulkDeparturesUpdateResponse"}},"stops":{"description":"A train station, tram stop, bus stop, regional coach stop or Night Bus stop","type":"object","additionalProperties":{"$ref":"#/definitions/V3.BulkDeparturesStopResponse"}},"routes":{"description":"Train lines, tram routes, bus routes, regional coach routes, Night Bus routes","type":"array","items":{"type":"object"}},"runs":{"description":"Individual trips/services of a route","type":"array","items":{"$ref":"#/definitions/V3.Run"}},"directions":{"description":"Directions of travel of route","type":"array","items":{"$ref":"#/definitions/V3.Direction"}},"disruptions":{"description":"Disruption information applicable to relevant routes or stops","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Disruption"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.BulkDeparturesUpdateResponse":{"type":"object","properties":{"departures":{"description":"Timetabled and real-time service departures","type":"array","items":{"$ref":"#/definitions/V3.Departure"}},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"requested_route_direction":{"$ref":"#/definitions/V3.BulkDeparturesRouteDirectionResponse","description":"The route direction that these departures are for. Will be one of the requested route directions"},"route_direction_status":{"description":"The status of the route direction (changed | unchanged).\r\nIf changed, requests should change the requested_route_direction for the route_direction supplied.","type":"string"},"route_direction":{"$ref":"#/definitions/V3.BulkDeparturesRouteDirectionResponse","description":"The route direction found matching the requested_route_direction"}}},"V3.BulkDeparturesStopResponse":{"type":"object","properties":{"stop_name":{"description":"Name of stop","type":"string"},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"stop_latitude":{"format":"float","description":"Geographic coordinate of latitude at stop","type":"number"},"stop_longitude":{"format":"float","description":"Geographic coordinate of longitude at stop","type":"number"},"stop_suburb":{"description":"suburb of stop","type":"string"},"stop_landmark":{"description":"Landmark in proximity of stop","type":"string"}}},"V3.BulkDeparturesRouteDirectionResponse":{"type":"object","properties":{"route_id":{"description":"Route identifier","type":"string"},"direction_id":{"format":"int32","description":"Direction of travel identifier","type":"integer"},"direction_name":{"description":"Name of direction of travel","type":"string"}}},"V3.DirectionsResponse":{"type":"object","properties":{"directions":{"description":"Directions of travel of route","type":"array","items":{"$ref":"#/definitions/V3.DirectionWithDescription"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.DirectionWithDescription":{"type":"object","properties":{"route_direction_description":{"type":"string"},"direction_id":{"format":"int32","description":"Direction of travel identifier","type":"integer"},"direction_name":{"description":"Name of direction of travel","type":"string"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"}}},"V3.DisruptionsResponse":{"type":"object","properties":{"disruptions":{"$ref":"#/definitions/V3.Disruptions","description":"Disruption information applicable to relevant routes or stops"},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.Disruptions":{"type":"object","properties":{"general":{"description":"Subset of disruption information applicable to multiple route_types","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"metro_train":{"description":"Subset of disruption information applicable to metropolitan train","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"metro_tram":{"description":"Subset of disruption information applicable to metropolitan tram","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"metro_bus":{"description":"Subset of disruption information applicable to metropolitan bus","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"regional_train":{"description":"Subset of disruption information applicable to V/Line train","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"regional_coach":{"description":"Subset of disruption information applicable to V/Line coach","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"regional_bus":{"description":"Subset of disruption information applicable to regional bus","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"school_bus":{"description":"Subset of disruption information applicable to school bus","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"telebus":{"description":"Subset of disruption information applicable to telebus services","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"night_bus":{"description":"Subset of disruption information applicable to night bus","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"ferry":{"description":"Subset of disruption information applicable to ferry","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"interstate_train":{"description":"Subset of disruption information applicable to interstate train","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"skybus":{"description":"Subset of disruption information applicable to skybus","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"taxi":{"description":"Subset of disruption information applicable to taxi","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}}}},"V3.DisruptionResponse":{"type":"object","properties":{"disruption":{"$ref":"#/definitions/V3.Disruption","description":"Disruption information applicable to relevant routes or stops"},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.StopToStopDisruptionsResponse":{"type":"object","properties":{"disruptions":{"type":"array","items":{"$ref":"#/definitions/V3.StopToStopDisruption"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.StopToStopDisruption":{"type":"object","properties":{"end":{"$ref":"#/definitions/V3.StopBasic"},"start":{"$ref":"#/definitions/V3.StopBasic"},"region":{"description":"Disrupted region of the route type's network (e.g. \"Blakburn to Boxhill\")","type":"string"},"alternate_transport":{"description":"Whether alternate transportation has been arranged for the area (e.g. \"Will be arranged\", \"Has been arranged\")","type":"string"},"status":{"description":"Status of the disruption (e.g. \"Current\")","type":"string"},"published_on":{"format":"date-time","description":"Date and time disruption information is published, in ISO 8601 UTC format","type":"string"},"direction_name":{"description":"The direction of the disruption (e.g. \"Outbound\", \"Inbound\", \"Both\")","type":"string"}}},"V3.StopBasic":{"type":"object","properties":{"stop_id":{"format":"int32","type":"integer"},"stop_name":{"type":"string"}}},"V3.DisruptionModesResponse":{"type":"object","properties":{"disruption_modes":{"description":"Transport mode identifiers","type":"array","items":{"$ref":"#/definitions/V3.DisruptionMode"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.DisruptionMode":{"type":"object","properties":{"disruption_mode_name":{"description":"Name of disruption mode","type":"string"},"disruption_mode":{"format":"int32","description":"Disruption mode identifier","type":"integer"}}},"V3.FareEstimateParameters":{"type":"object","properties":{"journey_touch_on_utc":{"format":"date-time","description":"JourneyTouchOnUtc in format yyyy-M-d h:m (e.g 2016-5-31 16:53).","type":"string"},"journey_touch_off_utc":{"format":"date-time","description":"JourneyTouchOffUtc in format yyyy-M-d h:m (e.g 2016-5-31 16:53).","type":"string"},"is_journey_in_free_tram_zone":{"type":"boolean"},"travelled_route_types":{"type":"array","items":{"format":"int32","enum":[0,1,2,3,4],"type":"integer"}}}},"V3.FareEstimateResponse":{"type":"object","properties":{"FareEstimateResultStatus":{"$ref":"#/definitions/V3.FareEstimateResultStatus"},"FareEstimateResult":{"$ref":"#/definitions/V3.FareEstimateResult"}}},"V3.FareEstimateResultStatus":{"type":"object","properties":{"StatusCode":{"format":"int32","type":"integer"},"Message":{"type":"string"}}},"V3.FareEstimateResult":{"type":"object","properties":{"IsEarlyBird":{"type":"boolean"},"IsJourneyInFreeTramZone":{"type":"boolean"},"IsThisWeekendJourney":{"type":"boolean"},"ZoneInfo":{"$ref":"#/definitions/V3.ZoneInfo"},"PassengerFares":{"type":"array","items":{"$ref":"#/definitions/V3.PassengerFare"}}}},"V3.ZoneInfo":{"type":"object","properties":{"MinZone":{"format":"int32","type":"integer"},"MaxZone":{"format":"int32","type":"integer"},"UniqueZones":{"type":"array","items":{"format":"int32","type":"integer"}}}},"V3.PassengerFare":{"type":"object","properties":{"PassengerType":{"type":"string"},"Fare2HourPeak":{"format":"double","type":"number"},"Fare2HourOffPeak":{"format":"double","type":"number"},"FareDailyPeak":{"format":"double","type":"number"},"FareDailyOffPeak":{"format":"double","type":"number"},"Pass7Days":{"format":"double","type":"number"},"Pass28To69DayPerDay":{"format":"double","type":"number"},"Pass70PlusDayPerDay":{"format":"double","type":"number"},"WeekendCap":{"format":"double","type":"number"},"HolidayCap":{"format":"double","type":"number"}}},"V3.JourneyPlannerParameters":{"type":"object","properties":{"TimeUtc":{"format":"date-time","type":"string"},"DepartFrom":{"type":"boolean"},"TransferSpeed":{"type":"string"},"TransferMaxTime":{"format":"int32","type":"integer"},"TransferMethod":{"type":"string"},"InclTrain":{"type":"boolean"},"InclTram":{"type":"boolean"},"InclBus":{"type":"boolean"},"InclVline":{"type":"boolean"},"InclRegCoach":{"type":"boolean"},"InclSkybus":{"type":"boolean"},"RouteType":{"type":"string"},"InclPathCoords":{"type":"boolean"},"InclFareEstimate":{"type":"boolean"},"Wheelchair":{"type":"boolean"},"NoSolidStairs":{"type":"boolean"}}},"V3.JourneyPlannerResponse":{"type":"object","properties":{"Journey":{"$ref":"#/definitions/V3.JourneyResponse"},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.JourneyResponse":{"type":"object","properties":{"Status":{"type":"string"},"OriginOptions":{"type":"array","items":{"$ref":"#/definitions/V3.LocationOption"}},"DestinationOptions":{"type":"array","items":{"$ref":"#/definitions/V3.LocationOption"}},"ChronosLog":{"type":"array","items":{"type":"string"}},"ChronosTimings":{"type":"array","items":{"type":"string"}},"ChronosStart":{"format":"date-time","type":"string"},"Itinerary":{"type":"array","items":{"$ref":"#/definitions/V3.Journey"}},"RequestUrl":{"type":"string"}}},"V3.LocationOption":{"type":"object","properties":{"Name":{"type":"string"},"Url":{"type":"string"}}},"V3.Journey":{"type":"object","properties":{"TimeDeparture":{"format":"date-time","type":"string"},"TimeArrival":{"format":"date-time","type":"string"},"ChronosJourneyLog":{"type":"array","items":{"type":"string"}},"RealTimeMessage":{"type":"string"},"TimeDepartureStr":{"type":"string","readOnly":true},"DateDepartureStr":{"type":"string","readOnly":true},"TimeArrivalStr":{"type":"string","readOnly":true},"DateArrivalStr":{"type":"string","readOnly":true},"DurationMins":{"format":"int32","type":"integer"},"Legs":{"type":"array","items":{"$ref":"#/definitions/V3.JourneyLeg"}},"Zones":{"type":"array","items":{"type":"string"}},"FareEstimate":{"$ref":"#/definitions/V3.FareEstimateResponse"}}},"V3.JourneyLeg":{"type":"object","properties":{"Type":{"description":"Transport type for journey leg. Support values are: \r\nTrain, Tram, Bus, Regional Train, NightRider, SkyBus, Regional Bus, Regional Coach, TeleBus, Interstate nonV/Line, Walk, Taxi, Drive, Ride","type":"string"},"LineName":{"type":"string"},"DirectionName":{"type":"string"},"OperatedBy":{"type":"string"},"AlternateLines":{"type":"array","items":{"type":"string"}},"Sequence":{"format":"int32","type":"integer"},"LineId":{"type":"string"},"DirectionCode":{"type":"string"},"DirectionId":{"type":"string"},"Direction":{"$ref":"#/definitions/V3.Direction"},"TimeDeparture":{"format":"date-time","type":"string"},"TimeArrival":{"format":"date-time","type":"string"},"TimeRealtime":{"format":"date-time","type":"string"},"Instructions":{"description":"Summary walking instructions. Exist only when Type=Walk","type":"string"},"InstructionDetails":{"description":"Turn by turn detailed walking instructions. Exist only when Type=Walk","type":"array","items":{"$ref":"#/definitions/V3.LegDirection"}},"Information":{"type":"array","items":{"type":"string"}},"Zones":{"type":"array","items":{"type":"string"}},"TimeDepartureStr":{"type":"string","readOnly":true},"DateDepartureStr":{"type":"string","readOnly":true},"TimeArrivalStr":{"type":"string","readOnly":true},"DateArrivalStr":{"type":"string","readOnly":true},"StopDeparture":{"$ref":"#/definitions/V3.JourneyPlannerLocation"},"StopArrival":{"$ref":"#/definitions/V3.JourneyPlannerLocation"},"StoppingPattern":{"type":"array","items":{"$ref":"#/definitions/V3.JourneyPlannerStop"}},"IsRealtime":{"type":"boolean"},"Disruptions":{"type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"PathCoordinates":{"description":"Array of geographic coordinates (latitude and longitude) for the approximate linear path of the journey leg. Only returned if inclPathCoords=true. May be null/empty.","type":"array","items":{"$ref":"#/definitions/V3.JourneyLegPathCoordinate"}}}},"V3.LegDirection":{"description":"Directions for walking leg","type":"object","properties":{"TurnDirection":{"description":"Leg turning direction. e.g SLIGHT_RIGHT, LEFT, SHARP_LEFT, STRAIGHT, UNKNOWN_TURN_DIRECTION","type":"string"},"TurningManoeuvre":{"description":"Leg turning manoeuvre, e.g ORIGIN, DESTINATION, KEEP, TURN","type":"string"},"Lon":{"format":"float","description":"GPS longitude of leg point of origin","type":"number"},"Lat":{"format":"float","description":"GPS latitude of leg point of origin","type":"number"},"StreetName":{"description":"Street name","type":"string"},"FromPathLinkIdx":{"type":"string"},"ToPathLinkIdx":{"type":"string"},"SkyDirection":{"type":"string"},"TravelTime":{"format":"int32","description":"Leg travel time in seconds","type":"integer"},"CumTravelTime":{"format":"int32","description":"Cumulative travel time in seconds","type":"integer"},"Distance":{"format":"int32","description":"Leg distance in meters","type":"integer"},"CumDistance":{"format":"int32","description":"Cumulative distance in meters","type":"integer"}}},"V3.JourneyPlannerLocation":{"type":"object","properties":{"LocationName":{"type":"string"},"StopId":{"format":"int32","type":"integer"},"PlaceId":{"format":"int32","type":"integer"},"Locality":{"type":"string"},"Platform":{"type":"string"},"Lat":{"format":"float","type":"number"},"Lon":{"format":"float","type":"number"},"RouteTypes":{"type":"array","items":{"format":"int32","enum":[0,1,2,3,4],"type":"integer"}},"DisruptionIds":{"type":"array","items":{"format":"int64","type":"integer"}},"StopTicket":{"$ref":"#/definitions/V3.StopTicket"}}},"V3.JourneyPlannerStop":{"type":"object","properties":{"Location":{"$ref":"#/definitions/V3.JourneyPlannerLocation"},"TimeTimetableUtc":{"format":"date-time","type":"string"},"TimeStr":{"type":"string","readOnly":true},"TimeRealtimeUtc":{"format":"date-time","type":"string"},"IsRealtime":{"type":"boolean"}}},"V3.JourneyLegPathCoordinate":{"type":"object","properties":{"Lat":{"format":"float","type":"number"},"Lon":{"format":"float","type":"number"}}},"V3.StopTicket":{"type":"object","properties":{"ticket_type":{"description":"Indicates the ticket type for the stop (myki, paper or both)","type":"string"},"zone":{"description":"Description of the zone","type":"string"},"is_free_fare_zone":{"description":"Indicates whether the stop is inside the free fare zone","type":"boolean"},"ticket_machine":{"type":"boolean"},"ticket_checks":{"type":"boolean"},"vline_reservation":{"type":"boolean"},"ticket_zones":{"type":"array","items":{"format":"int32","type":"integer"}}}},"V3.NetworkMapsResponse":{"type":"object","properties":{"maps":{"type":"array","items":{"$ref":"#/definitions/V3.NetworkMap"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.NetworkMap":{"type":"object","properties":{"version":{"type":"string"},"url":{"type":"string"},"size":{"type":"string"}}},"V3.OperatorsSocialFeedsResponse":{"type":"object","properties":{"operators":{"$ref":"#/definitions/V3.OperatorsSocialMediaModes"},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.OperatorsSocialMediaModes":{"type":"object","properties":{"metro_train":{"type":"array","items":{"$ref":"#/definitions/V3.OperatorSocialMedia"}},"metro_bus":{"type":"array","items":{"$ref":"#/definitions/V3.OperatorSocialMedia"}},"metro_tram":{"type":"array","items":{"$ref":"#/definitions/V3.OperatorSocialMedia"}},"regional_train":{"type":"array","items":{"$ref":"#/definitions/V3.OperatorSocialMedia"}},"regional_coach":{"type":"array","items":{"$ref":"#/definitions/V3.OperatorSocialMedia"}},"regional_bus":{"type":"array","items":{"$ref":"#/definitions/V3.OperatorSocialMedia"}}}},"V3.OperatorSocialMedia":{"type":"object","properties":{"name":{"type":"string"},"accounts":{"type":"array","items":{"$ref":"#/definitions/V3.OperatorSocialMediaAccount"}}}},"V3.OperatorSocialMediaAccount":{"type":"object","properties":{"type":{"type":"string"},"account_name":{"type":"string"},"url":{"type":"string"},"iOS_url":{"type":"string"}}},"V3.OperatorsResponse":{"type":"object","properties":{"operators":{"type":"array","items":{"$ref":"#/definitions/V3.Operator"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.OutletParameters":{"type":"object","properties":{"max_results":{"format":"int32","description":"Maximum number of results returned (default = 30)","type":"integer"}}},"V3.OutletResponse":{"type":"object","properties":{"outlets":{"description":"myki ticket outlets","type":"array","items":{"$ref":"#/definitions/V3.Outlet"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.Outlet":{"type":"object","properties":{"outlet_slid_spid":{"description":"The SLID / SPID","type":"string"},"outlet_name":{"description":"The location name of the outlet","type":"string"},"outlet_business":{"description":"The business name of the outlet","type":"string"},"outlet_latitude":{"format":"float","description":"Geographic coordinate of latitude at outlet","type":"number"},"outlet_longitude":{"format":"float","description":"Geographic coordinate of longitude at outlet","type":"number"},"outlet_suburb":{"description":"The city/municipality the outlet is in","type":"string"},"outlet_postcode":{"format":"int32","description":"The postcode for the outlet","type":"integer"},"outlet_business_hour_mon":{"description":"The business hours on Monday","type":"string"},"outlet_business_hour_tue":{"description":"The business hours on Tuesday","type":"string"},"outlet_business_hour_wed":{"description":"The business hours on Wednesday","type":"string"},"outlet_business_hour_thur":{"description":"The business hours on Thursday","type":"string"},"outlet_business_hour_fri":{"description":"The business hours on Friday","type":"string"},"outlet_business_hour_sat":{"description":"The business hours on Saturday","type":"string"},"outlet_business_hour_sun":{"description":"The business hours on Sunday","type":"string"},"outlet_notes":{"description":"Any additional notes for the outlet such as 'Buy pre-loaded myki cards only'. May be null/empty.","type":"string"}}},"V3.OutletGeolocationParameters":{"type":"object","properties":{"max_distance":{"format":"double","description":"Filter by maximum distance (in metres) from location specified via latitude and longitude parameters (default = 300)","type":"number"},"max_results":{"format":"int32","description":"Maximum number of results returned (default = 30)","type":"integer"}}},"V3.OutletGeolocationResponse":{"type":"object","properties":{"outlets":{"description":"myki ticket outlets","type":"array","items":{"$ref":"#/definitions/V3.OutletGeolocation"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.OutletGeolocation":{"type":"object","properties":{"outlet_distance":{"format":"float","description":"Distance of outlet from input location (in metres); returns 0 if no location is input","type":"number"},"outlet_slid_spid":{"description":"The SLID / SPID","type":"string"},"outlet_name":{"description":"The location name of the outlet","type":"string"},"outlet_business":{"description":"The business name of the outlet","type":"string"},"outlet_latitude":{"format":"float","description":"Geographic coordinate of latitude at outlet","type":"number"},"outlet_longitude":{"format":"float","description":"Geographic coordinate of longitude at outlet","type":"number"},"outlet_suburb":{"description":"The city/municipality the outlet is in","type":"string"},"outlet_postcode":{"format":"int32","description":"The postcode for the outlet","type":"integer"},"outlet_business_hour_mon":{"description":"The business hours on Monday","type":"string"},"outlet_business_hour_tue":{"description":"The business hours on Tuesday","type":"string"},"outlet_business_hour_wed":{"description":"The business hours on Wednesday","type":"string"},"outlet_business_hour_thur":{"description":"The business hours on Thursday","type":"string"},"outlet_business_hour_fri":{"description":"The business hours on Friday","type":"string"},"outlet_business_hour_sat":{"description":"The business hours on Saturday","type":"string"},"outlet_business_hour_sun":{"description":"The business hours on Sunday","type":"string"},"outlet_notes":{"description":"Any additional notes for the outlet such as 'Buy pre-loaded myki cards only'. May be null/empty.","type":"string"}}},"V3.PatternsParameters":{"type":"object","properties":{"expand":{"description":"List of objects to be returned in full (i.e. expanded) - options include: All, Stop, Route, Run, Direction, Disruption, VehiclePosition, VehicleDescriptor and None. Default is Disruption. Run must be expanded to receive VehiclePosition and VehicleDescriptor information.","type":"array","items":{"format":"int32","enum":[0,1,2,3,4,5,6,7,2147483647],"type":"integer"}},"stop_id":{"format":"int32","description":"Filter by stop_id; values returned by Stops API","type":"integer"},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"include_skipped_stops":{"description":"Include any skipped stops in a stopping pattern. Defaults to false.","type":"boolean"},"include_geopath":{"description":"Indicates if geopath data will be returned (default = false)","type":"boolean"},"include_advertised_interchange":{"description":"Indicates whether data related to interchanges should be included in the response (default = false)\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","type":"boolean"}}},"V3.StoppingPattern":{"type":"object","properties":{"disruptions":{"description":"Disruption information applicable to relevant routes or stops","type":"array","items":{"$ref":"#/definitions/V3.Disruption"}},"departures":{"description":"Timetabled and real-time service departures","type":"array","items":{"$ref":"#/definitions/V3.PatternDeparture"}},"stops":{"description":"A train station, tram stop, bus stop, regional coach stop or Night Bus stop","type":"object","additionalProperties":{"$ref":"#/definitions/V3.StoppingPatternStop"}},"routes":{"description":"Train lines, tram routes, bus routes, regional coach routes, Night Bus routes","type":"object","additionalProperties":{"type":"object"}},"runs":{"description":"Individual trips/services of a route","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Run"}},"directions":{"description":"Directions of travel of route","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Direction"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.PatternDeparture":{"type":"object","properties":{"skipped_stops":{"description":"The stops to be skipped following the current departure in order.","type":"array","items":{"$ref":"#/definitions/V3.StopModel"}},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"run_id":{"format":"int32","description":"Numeric trip/service run identifier. Defaults to -1 when run identifier is Alphanumeric","type":"integer","readOnly":true},"run_ref":{"description":"Alphanumeric trip/service run identifier","type":"string"},"direction_id":{"format":"int32","description":"Direction of travel identifier","type":"integer"},"disruption_ids":{"description":"Disruption information identifier(s)","type":"array","items":{"format":"int64","type":"integer"}},"scheduled_departure_utc":{"format":"date-time","description":"Scheduled (i.e. timetabled) departure time and date in ISO 8601 UTC format","type":"string"},"estimated_departure_utc":{"format":"date-time","description":"Real-time estimate of departure time and date in ISO 8601 UTC format","type":"string"},"at_platform":{"description":"Indicates if the metropolitan train service is at the platform at the time of query; returns false for other modes","type":"boolean"},"platform_number":{"description":"Platform number at stop (metropolitan train only; returns null for other modes)","type":"string"},"flags":{"description":"Flag indicating special condition for run (e.g. RR Reservations Required, GC Guaranteed Connection, DOO Drop Off Only, PUO Pick Up Only, MO Mondays only, TU Tuesdays only, WE Wednesdays only, TH Thursdays only, FR Fridays only, SS School days only; ignore E flag)","type":"string"},"departure_sequence":{"format":"int32","description":"Chronological sequence for the departures in a run. Order ascendingly by this field to get chronological order (earliest first) of departures with the same run_ref. NOTE, this field is not always N+1 or N-1 of the previous or following departure. e.g 100, 200, 250, 300 instead of 1, 2, 3, 4","type":"integer"},"departure_note":{"description":"Additional descriptive text associated with the departure","type":"string"}}},"V3.StoppingPatternStop":{"type":"object","properties":{"stop_ticket":{"$ref":"#/definitions/V3.StopTicket","description":"Stop ticket information"},"stop_distance":{"format":"float","description":"Distance of stop from input location (in metres); returns 0 if no location is input","type":"number"},"stop_suburb":{"description":"suburb of stop","type":"string"},"stop_name":{"description":"Name of stop","type":"string"},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"stop_latitude":{"format":"float","description":"Geographic coordinate of latitude at stop","type":"number"},"stop_longitude":{"format":"float","description":"Geographic coordinate of longitude at stop","type":"number"},"stop_landmark":{"description":"Landmark in proximity of stop","type":"string"},"stop_sequence":{"format":"int32","description":"Sequence of the stop on the route/run; return 0 when route_id or run_id not specified. Order ascendingly by this field (when non zero) to get physical order (earliest first) of stops on the route_id/run_id.","type":"integer"}}},"V3.PeriodsResponse":{"type":"object","properties":{"periods":{"type":"array","items":{"$ref":"#/definitions/V3.Period"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.RouteResponse":{"type":"object","properties":{"route":{"$ref":"#/definitions/V3.RouteWithStatus","description":"Train lines, tram routes, bus routes, regional coach routes, Night Bus routes"},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.RouteWithStatus":{"type":"object","properties":{"route_service_status":{"$ref":"#/definitions/V3.RouteServiceStatus","description":"Service status for the route (indicates disruptions)"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"route_name":{"description":"Name of route","type":"string"},"route_number":{"description":"Route number presented to public (nb. not route_id)","type":"string"},"route_gtfs_id":{"description":"GTFS Identifer of the route","type":"string"},"geopath":{"description":"GeoPath of the route","type":"array","items":{"type":"object"}}}},"V3.RouteServiceStatus":{"type":"object","properties":{"description":{"type":"string"},"timestamp":{"format":"date-time","type":"string"}}},"V3.RouteTypesResponse":{"type":"object","properties":{"route_types":{"description":"Transport mode identifiers","type":"array","items":{"$ref":"#/definitions/V3.RouteType"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.RouteType":{"type":"object","properties":{"route_type_name":{"description":"Name of transport mode","type":"string"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"}}},"V3.RunsBroadParameters":{"type":"object","properties":{"expand":{"description":"List of objects to be returned in full (i.e. expanded) - options include: All, VehiclePosition, VehicleDescriptor, or None. Default is None.","type":"array","items":{"format":"int32","enum":[0,1,2,2147483647],"type":"integer"}},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"include_advertised_interchange":{"description":"Indicates whether data related to interchanges should be included in the response (default = false).\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","type":"boolean"}}},"V3.RunsResponse":{"type":"object","properties":{"runs":{"description":"Individual trips/services of a route","type":"array","items":{"$ref":"#/definitions/V3.Run"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.RunsSpecificParameters":{"type":"object","properties":{"include_geopath":{"description":"Indicates if geopath data will be returned (default = false)","type":"boolean"},"expand":{"description":"List of objects to be returned in full (i.e. expanded) - options include: All, VehiclePosition, VehicleDescriptor, or None. Default is None.","type":"array","items":{"format":"int32","enum":[0,1,2,2147483647],"type":"integer"}},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"include_advertised_interchange":{"description":"Indicates whether data related to interchanges should be included in the response (default = false).\r\nWhen set to true, this parameter enables API clients to retrieve additional exchange information (stops, routes, runs, directions and disruptions) in a single call instead of making multiple requests","type":"boolean"}}},"V3.RunAndRouteTypeParameters":{"type":"object","properties":{"expand":{"description":"List of objects to be returned in full (i.e. expanded) - options include: All, VehiclePosition, VehicleDescriptor, or None. Default is None.","type":"array","items":{"format":"int32","enum":[0,1,2,2147483647],"type":"integer"}},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"include_geopath":{"description":"Indicates if geopath data will be returned (default = false)","type":"boolean"}}},"V3.RunResponse":{"type":"object","properties":{"run":{"$ref":"#/definitions/V3.Run","description":"Individual trip/service of a route"},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.SearchParameters":{"type":"object","properties":{"route_types":{"description":"Filter by route_type; values returned via RouteTypes API (note: stops and routes are ordered by route_types specified)","type":"array","items":{"format":"int32","enum":[0,1,2,3,4],"type":"integer"}},"latitude":{"format":"float","description":"Filter by geographic coordinate of latitude","type":"number"},"longitude":{"format":"float","description":"Filter by geographic coordinate of longitude","type":"number"},"max_distance":{"format":"float","description":"Filter by maximum distance (in metres) from location specified via latitude and longitude parameters","type":"number"},"include_addresses":{"description":"Placeholder for future development; currently unavailable","type":"boolean"},"include_outlets":{"description":"Indicates if outlets will be returned in response (default = true)","type":"boolean"},"match_stop_by_suburb":{"description":"Indicates whether to find stops by suburbs in the search term (default = true)","type":"boolean"},"match_route_by_suburb":{"description":"Indicates whether to find routes by suburbs in the search term (default = true)","type":"boolean"},"match_stop_by_gtfs_stop_id":{"description":"Indicates whether to search for stops according to a metlink stop ID (default = false)","type":"boolean"}}},"V3.SearchResult":{"type":"object","properties":{"stops":{"description":"Train stations, tram stops, bus stops, regional coach stops or Night Bus stops","type":"array","items":{"$ref":"#/definitions/V3.ResultStop"}},"routes":{"description":"Train lines, tram routes, bus routes, regional coach routes, Night Bus routes","type":"array","items":{"$ref":"#/definitions/V3.ResultRoute"}},"outlets":{"description":"myki ticket outlets","type":"array","items":{"$ref":"#/definitions/V3.ResultOutlet"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.ResultStop":{"type":"object","properties":{"stop_distance":{"format":"float","description":"Distance of stop from input location (in metres); returns 0 if no location is input","type":"number"},"stop_suburb":{"description":"suburb of stop","type":"string"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"routes":{"description":"List of routes travelling through the stop","type":"array","items":{"$ref":"#/definitions/V3.ResultRoute"}},"stop_latitude":{"format":"float","description":"Geographic coordinate of latitude at stop","type":"number"},"stop_longitude":{"format":"float","description":"Geographic coordinate of longitude at stop","type":"number"},"stop_sequence":{"format":"int32","description":"Sequence of the stop on the route/run; return 0 when route_id or run_id not specified. Order ascendingly by this field (when non zero) to get physical order (earliest first) of stops on the route_id/run_id.","type":"integer"},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"stop_name":{"description":"Name of stop","type":"string"},"stop_landmark":{"description":"Landmark in proximity of stop","type":"string"}}},"V3.ResultRoute":{"type":"object","properties":{"route_name":{"description":"Name of route","type":"string"},"route_number":{"description":"Route number presented to public (nb. not route_id)","type":"string"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"route_gtfs_id":{"description":"GTFS Identifer of the route","type":"string"},"route_service_status":{"$ref":"#/definitions/V3.RouteServiceStatus","description":"Service status for the route (indicates disruptions)"}}},"V3.ResultOutlet":{"type":"object","properties":{"outlet_distance":{"format":"float","description":"Distance of outlet from input location (in metres); returns 0 if no location is input","type":"number"},"outlet_slid_spid":{"description":"The SLID / SPID","type":"string"},"outlet_name":{"description":"The location name of the outlet","type":"string"},"outlet_business":{"description":"The business name of the outlet","type":"string"},"outlet_latitude":{"format":"float","description":"Geographic coordinate of latitude at outlet","type":"number"},"outlet_longitude":{"format":"float","description":"Geographic coordinate of longitude at outlet","type":"number"},"outlet_suburb":{"description":"The city/municipality the outlet is in","type":"string"},"outlet_postcode":{"format":"int32","description":"The postcode for the outlet","type":"integer"},"outlet_business_hour_mon":{"description":"The business hours on Monday","type":"string"},"outlet_business_hour_tue":{"description":"The business hours on Tuesday","type":"string"},"outlet_business_hour_wed":{"description":"The business hours on Wednesday","type":"string"},"outlet_business_hour_thur":{"description":"The business hours on Thursday","type":"string"},"outlet_business_hour_fri":{"description":"The business hours on Friday","type":"string"},"outlet_business_hour_sat":{"description":"The business hours on Saturday","type":"string"},"outlet_business_hour_sun":{"description":"The business hours on Sunday","type":"string"},"outlet_notes":{"description":"Any additional notes for the outlet such as 'Buy pre-loaded myki cards only'. May be null/empty.","type":"string"}}},"V3.GenerateDivaMappingResponse":{"type":"object","properties":{"mapping_version":{"type":"string","readOnly":true},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.SiriReferenceDataRequest":{"required":["line_refs","mapping_version"],"type":"object","properties":{"line_refs":{"type":"array","items":{"$ref":"#/definitions/V3.SiriLineRefDirectionRefStopPointRef"}},"stop_point_refs":{"description":"Siri StopPointRef","type":"array","items":{"format":"int32","type":"integer"}},"date_utc":{"format":"date-time","description":"Filter by the date and time of the request (ISO 8601 UTC format) (default = current date and time)","type":"string"},"mapping_version":{"description":"DIVA mapping version generated by Chronos during a Parser or RealtimeBusConfig load","type":"string"}}},"V3.SiriLineRefDirectionRefStopPointRef":{"required":["line_ref","direction_ref","stop_point_ref"],"type":"object","properties":{"line_ref":{"description":"Siri LineRef","type":"string"},"direction_ref":{"format":"int32","description":"Siri DirectionRef (in, out, up, down, clockwise, counterclockwise, Inbound, Outbound)","enum":[1,2,5,10,16,32,65,130],"type":"integer"},"stop_point_ref":{"format":"int32","description":"Siri StopPointRef","type":"integer"}}},"V3.SiriReferenceDataMappingsResponse":{"type":"object","properties":{"mapping_version":{"type":"string","readOnly":true},"line_refs":{"description":"SIRI LineRef","type":"object","additionalProperties":{"$ref":"#/definitions/V3.SiriDirectionRefsDictionary"},"readOnly":true},"stop_point_refs":{"type":"object","additionalProperties":{"$ref":"#/definitions/V3.StopPoint"},"readOnly":true},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.SiriDirectionRefsDictionary":{"type":"object","properties":{"direction_refs":{"type":"object","additionalProperties":{"$ref":"#/definitions/V3.SiriStopsRefsDictionary"},"readOnly":true}}},"V3.StopPoint":{"type":"object","properties":{"stop_id":{"format":"int32","type":"integer"}}},"V3.SiriStopsRefsDictionary":{"type":"object","properties":{"stop_point_refs":{"type":"object","additionalProperties":{"$ref":"#/definitions/V3.SiriReferenceDataDetail"},"readOnly":true},"unmatched_stop_point_refs":{"type":"object","additionalProperties":{"type":"string"},"readOnly":true}}},"V3.SiriReferenceDataDetail":{"type":"object","properties":{"NoMatchReason":{"format":"int32","enum":[1,2,3,4,5],"type":"integer"},"route_id":{"format":"int32","type":"integer"},"route_number_short":{"description":"Route number","type":"string"},"direction_id":{"format":"int32","type":"integer"},"tracking_supplier_id":{"format":"int32","description":"Authority (Upstream SIRI provider) of a route and direction","type":"integer"},"route_type":{"format":"int32","type":"integer"}}},"V3.SiriLineRefsRequest":{"required":["mapping_version"],"type":"object","properties":{"line_refs":{"type":"array","items":{"$ref":"#/definitions/V3.SiriLineRef"}},"mapping_version":{"description":"DIVA mapping version generated by Chronos during a Parser or RealtimeBusConfig load","type":"string"}}},"V3.SiriLineRef":{"required":["line_ref"],"type":"object","properties":{"line_ref":{"description":"Siri LineRef","type":"string"},"direction_ref":{"format":"int32","description":"Siri DirectionRef (in, out, up, down, clockwise, counterclockwise, Inbound, Outbound)","enum":[1,2,5,10,16,32,65,130],"type":"integer"}}},"V3.SiriLineRefMappingsResponse":{"type":"object","properties":{"mapping_version":{"type":"string","readOnly":true},"line_refs":{"type":"object","additionalProperties":{"$ref":"#/definitions/V3.SiriLineRefDirectionRefsDictionary"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.SiriLineRefDirectionRefsDictionary":{"type":"object","properties":{"direction_refs":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/definitions/V3.SiriReferenceDataDetail"}}},"unmatched_direction_refs":{"type":"object","additionalProperties":{"type":"string"}}}},"V3.DynamoDbTimetablesReponse":{"type":"object","properties":{"timetables":{"type":"array","items":{"$ref":"#/definitions/V3.DynamoDbTimetable"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.DynamoDbTimetable":{"type":"object","properties":{"table_name":{"description":"Name of corresponding table in DynamoDB.","type":"string"},"parser_version":{"format":"int64","description":"Parser verison","type":"integer"},"parser_mapping_version":{"description":"Diva Mapping Version used to load Parser into DynamoDB","type":"string"},"pt_version":{"format":"int64","description":"PT version","type":"integer"},"pt_mapping_version":{"description":"Diva Mapping Version used to load PT into DynamoDB","type":"string"},"transport_type":{"format":"int32","description":"A.k.a. Transport Mode (e.g. Train, Tram, Bus, V/Line, Nightrider)","enum":[0,1,2,3,4],"type":"integer"},"applicable_date":{"format":"date-time","description":"Date (in local timezone) for which this timetable is valid.","type":"string"},"applicable_local_date":{"description":"Formated date string of applicable date","type":"string","readOnly":true},"exists":{"description":"True if the named table has been created in DynamoDB (i.e. at least one departure record has been loaded),\r\nor false if there are no records for this date and transport type.","type":"boolean"}}},"V3.SiriDownstreamSubscription":{"type":"object","properties":{"subscriber_ref":{"type":"string"},"subscription_ref":{"type":"string"},"message_type":{"format":"int32","enum":[0,1],"type":"integer"},"siri_format":{"format":"int32","enum":[0,1],"type":"integer"},"siri_version":{"pattern":"1.3|2.0","type":"string"},"consumer_address":{"type":"string"},"initial_termination_time":{"format":"date-time","type":"string"},"validity_period_start":{"format":"date-time","type":"string"},"validity_period_end":{"format":"date-time","type":"string"},"preview_interval":{"type":"string"},"topics":{"type":"array","items":{"$ref":"#/definitions/V3.SiriDownstreamSubscriptionTopic"}}}},"V3.SiriDownstreamSubscriptionTopic":{"type":"object","properties":{"line_ref":{"type":"string"},"direction_ref":{"format":"int32","enum":[1,2,5,10,16,32,65,130],"type":"integer"},"route_type":{"format":"int32","enum":[0,1,2,3,4],"type":"integer"}}},"V3.SiriProductionTimetableSubscriptionRequest":{"required":["start_time","end_time","subscriber_ref","subscription_ref","siri_format","siri_version","consumer_address","initial_termination_time","topics"],"type":"object","properties":{"start_time":{"format":"date-time","description":"Siri Start Time of the Validity Period","type":"string"},"end_time":{"format":"date-time","description":"Siri End Time of the Validity Period","type":"string"},"subscriber_ref":{"description":"Siri Subscriber Ref","type":"string"},"subscription_ref":{"description":"Siri Subscription Ref - Unique to a Subscriber Ref","type":"string"},"siri_format":{"format":"int32","description":"Siri Message Format 'xml' or 'json'","enum":[0,1],"type":"integer"},"siri_version":{"description":"Siri Message Version '1.3' or '2.0'","pattern":"1.3|2.0","type":"string"},"consumer_address":{"description":"Siri Consumer Address - Baseline and Updates will be sent to this address","type":"string"},"initial_termination_time":{"format":"date-time","description":"Siri Initial Termination Time - Expiry of the subscription","type":"string"},"topics":{"type":"array","items":{"$ref":"#/definitions/V3.SiriSubscriptionTopic"}}}},"V3.SiriSubscriptionTopic":{"required":["line_ref","route_type"],"type":"object","properties":{"line_ref":{"description":"Siri LineRef","type":"string"},"direction_ref":{"format":"int32","description":"Siri DirectionRef (in, out, up, down, clockwise, counterclockwise, Inbound, Outbound)","enum":[1,2,5,10,16,32,65,130],"type":"integer"},"route_type":{"format":"int32","description":"Route Type eg. 0 (Train) 1 (Tram) 2 (Bus) 3 (Vline) 4 (NightRider)","enum":[0,1,2,3,4],"type":"integer"}}},"V3.SiriDownstreamSubscriptionResponse":{"type":"object","properties":{"valid_until":{"format":"date-time","description":"The Data Horizon of Chronos","type":"string"}}},"V3.SiriEstimatedTimetableSubscriptionRequest":{"required":["preview_interval","subscriber_ref","subscription_ref","siri_format","siri_version","consumer_address","initial_termination_time","topics"],"type":"object","properties":{"preview_interval":{"description":"Siri Preview Interval","type":"string"},"subscriber_ref":{"description":"Siri Subscriber Ref","type":"string"},"subscription_ref":{"description":"Siri Subscription Ref - Unique to a Subscriber Ref","type":"string"},"siri_format":{"format":"int32","description":"Siri Message Format 'xml' or 'json'","enum":[0,1],"type":"integer"},"siri_version":{"description":"Siri Message Version '1.3' or '2.0'","pattern":"1.3|2.0","type":"string"},"consumer_address":{"description":"Siri Consumer Address - Baseline and Updates will be sent to this address","type":"string"},"initial_termination_time":{"format":"date-time","description":"Siri Initial Termination Time - Expiry of the subscription","type":"string"},"topics":{"type":"array","items":{"$ref":"#/definitions/V3.SiriSubscriptionTopic"}}}},"V3.SiriDownstreamSubscriptionDeleteRequest":{"required":["subscriber_ref"],"type":"object","properties":{"subscriber_ref":{"description":"Siri Subscriber Ref","type":"string"},"subscription_ref":{"description":"Siri Subscription Reference(s) - Unique to a Subscriber Ref.\r\nIf `null`, then all subscriptions will be terminated for the referenced Subscriber.","type":"array","items":{"type":"string"}}}},"V3.Void":{"type":"object","properties":{}},"V3.StopResponse":{"type":"object","properties":{"stop":{"$ref":"#/definitions/V3.StopDetails","description":"A metropolitan or V/Line train station"},"disruptions":{"description":"Disruption information applicable to relevant routes or stops","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Disruption"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.StopDetails":{"type":"object","properties":{"disruption_ids":{"description":"Disruption information identifier(s)","type":"array","items":{"format":"int64","type":"integer"}},"station_type":{"description":"Type of metropolitan train station (i.e. \"Premium\", \"Host\" or \"Unstaffed\" station); returns null for V/Line train","type":"string"},"station_description":{"description":"The definition applicable to the station_type; returns null for V/Line train","type":"string"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"stop_location":{"$ref":"#/definitions/V3.StopLocation","description":"Location details of the stop"},"stop_amenities":{"$ref":"#/definitions/V3.StopAmenityDetails","description":"Amenity and facility details at the stop"},"stop_accessibility":{"$ref":"#/definitions/V3.StopAccessibility","description":"Facilities relating to the accessibility of the stop"},"stop_staffing":{"$ref":"#/definitions/V3.StopStaffing","description":"Staffing details for the stop"},"routes":{"description":"Routes travelling through the stop","type":"array","items":{"type":"object"}},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"stop_name":{"description":"Name of stop","type":"string"},"stop_landmark":{"description":"Landmark in proximity of stop","type":"string"}}},"V3.StopLocation":{"type":"object","properties":{"gps":{"$ref":"#/definitions/V3.StopGps","description":"GPS coordinates of the stop"}}},"V3.StopAmenityDetails":{"type":"object","properties":{"toilet":{"description":"Indicates if there is a public toilet at or near the stop","type":"boolean"},"taxi_rank":{"description":"Indicates if there is a taxi rank at or near the stop","type":"boolean"},"car_parking":{"description":"The number of free car parking spots at the stop","type":"string"},"cctv":{"description":"Indicates if there are CCTV (i.e. closed circuit television) cameras at the stop","type":"boolean"}}},"V3.StopAccessibility":{"type":"object","properties":{"lighting":{"description":"Indicates if there is lighting at the stop","type":"boolean"},"platform_number":{"format":"int32","description":"Indicates the platform number for xivic information (Platform 0 indicates general stop facilities)","type":"integer"},"audio_customer_information":{"description":"Indicates if there is at least one audio customer information at the stop/platform","type":"boolean"},"escalator":{"description":"Indicates if there is at least one accessible escalator at the stop/platform that complies with the Disability Standards for Accessible Public Transport under the Disability Discrimination Act (1992)","type":"boolean"},"hearing_loop":{"description":"Indicates if there is a hearing loop facility at the stop/platform","type":"boolean"},"lift":{"description":"Indicates if there is an elevator at the stop/platform","type":"boolean"},"stairs":{"description":"Indicates if there are stairs available in the stop","type":"boolean"},"stop_accessible":{"description":"Indicates if the stop is accessible","type":"boolean"},"tactile_ground_surface_indicator":{"description":"Indicates if there are tactile tiles (also known as tactile ground surface indicators, or TGSIs) at the stop","type":"boolean"},"waiting_room":{"description":"Indicates if there is a general waiting area at the stop","type":"boolean"},"wheelchair":{"$ref":"#/definitions/V3.StopAccessibilityWheelchair","description":"Facilities relating to the accessibility of the stop by wheelchair"}}},"V3.StopStaffing":{"type":"object","properties":{"fri_am_from":{"description":"Stop staffing hours","type":"string"},"fri_am_to":{"description":"Stop staffing hours","type":"string"},"fri_pm_from":{"description":"Stop staffing hours","type":"string"},"fri_pm_to":{"description":"Stop staffing hours","type":"string"},"mon_am_from":{"description":"Stop staffing hours","type":"string"},"mon_am_to":{"description":"Stop staffing hours","type":"string"},"mon_pm_from":{"description":"Stop staffing hours","type":"string"},"mon_pm_to":{"description":"Stop staffing hours","type":"string"},"ph_additional_text":{"description":"Stop staffing hours","type":"string"},"ph_from":{"description":"Stop staffing hours","type":"string"},"ph_to":{"description":"Stop staffing hours","type":"string"},"sat_am_from":{"description":"Stop staffing hours","type":"string"},"sat_am_to":{"description":"Stop staffing hours","type":"string"},"sat_pm_from":{"description":"Stop staffing hours","type":"string"},"sat_pm_to":{"description":"Stop staffing hours","type":"string"},"sun_am_from":{"description":"Stop staffing hours","type":"string"},"sun_am_to":{"description":"Stop staffing hours","type":"string"},"sun_pm_from":{"description":"Stop staffing hours","type":"string"},"sun_pm_to":{"description":"Stop staffing hours","type":"string"},"thu_am_from":{"description":"Stop staffing hours","type":"string"},"thu_am_to":{"description":"Stop staffing hours","type":"string"},"thu_pm_from":{"description":"Stop staffing hours","type":"string"},"thu_pm_to":{"description":"Stop staffing hours","type":"string"},"tue_am_from":{"description":"Stop staffing hours","type":"string"},"tue_am_to":{"description":"Stop staffing hours","type":"string"},"tue_pm_from":{"description":"Stop staffing hours","type":"string"},"tue_pm_to":{"description":"Stop staffing hours","type":"string"},"wed_am_from":{"description":"Stop staffing hours","type":"string"},"wed_am_to":{"description":"Stop staffing hours","type":"string"},"wed_pm_from":{"description":"Stop staffing hours","type":"string"},"wed_pm_To":{"description":"Stop staffing hours","type":"string"}}},"V3.StopGps":{"type":"object","properties":{"latitude":{"format":"float","description":"Geographic coordinate of latitude at stop","type":"number"},"longitude":{"format":"float","description":"Geographic coordinate of longitude at stop","type":"number"}}},"V3.StopAccessibilityWheelchair":{"type":"object","properties":{"accessible_ramp":{"type":"boolean"},"parking":{"description":"Indicates if there is at least one accessible parking spot at the stop that complies with the Disability Standards for Accessible Public Transport under the Disability Discrimination Act (1992)","type":"boolean"},"telephone":{"description":"Indicates if there is at least one accessible telephone at the stop/platform that complies with the Disability Standards for Accessible Public Transport under the Disability Discrimination Act (1992)","type":"boolean"},"toilet":{"description":"Indicates if there is at least one accessible toilet at the stop/platform that complies with the Disability Standards for Accessible Public Transport under the Disability Discrimination Act (1992)","type":"boolean"},"low_ticket_counter":{"description":"Indicates if there is at least one low ticket counter at the stop that complies with the Disability Standards for Accessible Public Transport under the Disability Discrimination Act (1992)","type":"boolean"},"manouvering":{"description":"Indicates if there is a space for mobility device to board on or off a transport mode","type":"boolean"},"raised_platform":{"description":"Indicates if there is a raised platform to board a train","type":"boolean"},"ramp":{"description":"Indicates if there are ramps (<1:14) at the stop/platform","type":"boolean"},"secondary_path":{"description":"Indicates if there is a path beyond the stop which is accessible","type":"boolean"},"raised_platform_shelther":{"description":"Indicates if there is shelter near the raised platform","type":"boolean"},"steep_ramp":{"description":"Indicates if there are ramps (>1:14) at the stop/platform","type":"boolean"}}},"V3.StopsByRouteIdParameters":{"type":"object","properties":{"direction_id":{"format":"int32","description":"Direction for which the stops need to be returned","type":"integer"},"stop_disruptions":{"description":"Flag to specify whether disruptions should be included in the response","type":"boolean"},"include_geopath":{"description":"Flag to specify whether geo_path should be included in the response","type":"boolean"},"geopath_utc":{"format":"date-time","description":"Filter geopaths by date (ISO 8601 UTC format) (default = current date)","type":"string"},"include_advertised_interchange":{"description":"Flag to specify whether additional stops for interchanges should be included in the response. Note-: To make use of this flag please pass in direction_id.","type":"boolean"}}},"V3.StopsOnRouteResponse":{"type":"object","properties":{"stops":{"description":"Train stations, tram stops, bus stops, regional coach stops or Night Bus stops","type":"array","items":{"$ref":"#/definitions/V3.StopOnRoute"}},"disruptions":{"description":"Disruption information applicable to relevant routes or stops","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Disruption"}},"geopath":{"description":"GeoPath for the route","type":"array","items":{"type":"object"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.StopOnRoute":{"type":"object","properties":{"disruption_ids":{"description":"Disruption information identifier(s)","type":"array","items":{"format":"int64","type":"integer"}},"stop_suburb":{"description":"suburb of stop","type":"string"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"stop_latitude":{"format":"float","description":"Geographic coordinate of latitude at stop","type":"number"},"stop_longitude":{"format":"float","description":"Geographic coordinate of longitude at stop","type":"number"},"stop_sequence":{"format":"int32","description":"Sequence of the stop on the route/run; return 0 when route_id or run_id not specified. Order ascendingly by this field (when non zero) to get physical order (earliest first) of stops on the route_id/run_id.","type":"integer"},"stop_ticket":{"$ref":"#/definitions/V3.StopTicket","description":"Stop ticket information"},"interchange":{"description":"Interchange information for connecting routes at this stop","type":"array","items":{"$ref":"#/definitions/V3.InterchangeRoute"}},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"stop_name":{"description":"Name of stop","type":"string"},"stop_landmark":{"description":"Landmark in proximity of stop","type":"string"}}},"V3.InterchangeRoute":{"description":"Information about route interchange","type":"object","properties":{"route_id":{"format":"int32","description":"Route identifier","type":"integer"},"advertised":{"description":"Indicates whether the interchange information is shown to end users","type":"boolean"}}},"V3.StopsByDistanceResponse":{"type":"object","properties":{"stops":{"description":"Train stations, tram stops, bus stops, regional coach stops or Night Bus stops","type":"array","items":{"$ref":"#/definitions/V3.StopGeosearch"}},"disruptions":{"description":"Disruption information applicable to relevant routes or stops","type":"object","additionalProperties":{"$ref":"#/definitions/V3.Disruption"}},"status":{"$ref":"#/definitions/V3.Status","description":"API Status / Metadata"}}},"V3.StopGeosearch":{"type":"object","properties":{"disruption_ids":{"description":"Disruption information identifier(s)","type":"array","items":{"format":"int64","type":"integer"}},"stop_distance":{"format":"float","description":"Distance of stop from input location (in metres); returns 0 if no location is input","type":"number"},"stop_suburb":{"description":"suburb of stop","type":"string"},"stop_name":{"description":"Name of stop","type":"string"},"stop_id":{"format":"int32","description":"Stop identifier","type":"integer"},"route_type":{"format":"int32","description":"Transport mode identifier","type":"integer"},"routes":{"description":"List of routes travelling through the stop","type":"array","items":{"type":"object"}},"stop_latitude":{"format":"float","description":"Geographic coordinate of latitude at stop","type":"number"},"stop_longitude":{"format":"float","description":"Geographic coordinate of longitude at stop","type":"number"},"stop_landmark":{"description":"Landmark in proximity of stop","type":"string"},"stop_sequence":{"format":"int32","description":"Sequence of the stop on the route/run; return 0 when route_id or run_id not specified. Order ascendingly by this field (when non zero) to get physical order (earliest first) of stops on the route_id/run_id.","type":"integer"}}}}}