Compare commits
19 Commits
Author | SHA1 | Date |
---|---|---|
Paul van Tilburg | a289bd9ef0 | |
Paul van Tilburg | 122f98a92d | |
Paul van Tilburg | 1426405943 | |
Paul van Tilburg | 34be96d187 | |
Paul van Tilburg | bc140a9d1e | |
Paul van Tilburg | 39c224eb90 | |
Paul van Tilburg | b517448fd7 | |
Paul van Tilburg | 3de66dbd41 | |
Paul van Tilburg | 6a04fc958f | |
Paul van Tilburg | c8b951ab7e | |
Paul van Tilburg | 32ec6b516c | |
Paul van Tilburg | a6301fa678 | |
Paul van Tilburg | f00537d5f3 | |
Paul van Tilburg | c8970fa3bb | |
Paul van Tilburg | aee3409f4a | |
Paul van Tilburg | dbdd7bef0f | |
Paul van Tilburg | d749233b24 | |
Paul van Tilburg | abb6657212 | |
Paul van Tilburg | 8b03f2162b |
|
@ -0,0 +1,82 @@
|
|||
name: "Check, Test and Lint Using Cargo"
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
- workflow_dispatch
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: debian-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: https://github.com/actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Use sparse Cargo index for crates.io
|
||||
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
|
||||
|
||||
- name: Run cargo check
|
||||
uses: https://github.com/actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
runs-on: debian-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: https://github.com/actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Use sparse Cargo index for crates.io
|
||||
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
|
||||
|
||||
- name: Run cargo test
|
||||
uses: https://github.com/actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features
|
||||
|
||||
lints:
|
||||
name: Lints
|
||||
runs-on: debian-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: https://github.com/actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Use sparse Cargo index for crates.io
|
||||
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
|
||||
|
||||
- name: Run cargo fmt
|
||||
uses: https://github.com/actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Run cargo clippy
|
||||
uses: https://github.com/actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.2.5]
|
||||
|
||||
### Added
|
||||
|
||||
* Add Gitea Actions workflow for cargo
|
||||
|
||||
### Changed
|
||||
|
||||
* Updated dependencies on `cached`, `chrono-tz` and `geocoding`
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix float comparison in tests
|
||||
* Fix clippy issues
|
||||
|
||||
### Security
|
||||
|
||||
* Update dependencies ([RUSTSEC-2023-0018](https://rustsec.org/advisories/RUSTSEC-2023-0018.html))
|
||||
|
||||
## [0.2.4] - 2022-07-05
|
||||
|
||||
### Added
|
||||
|
@ -69,7 +88,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
Initial release.
|
||||
|
||||
[Unreleased]: https://git.luon.net/paul/sinoptik/compare/v0.2.4...HEAD
|
||||
[Unreleased]: https://git.luon.net/paul/sinoptik/compare/v0.2.5...HEAD
|
||||
[0.2.5]: https://git.luon.net/paul/sinoptik/compare/v0.2.4...v0.2.5
|
||||
[0.2.4]: https://git.luon.net/paul/sinoptik/compare/v0.2.3...v0.2.4
|
||||
[0.2.3]: https://git.luon.net/paul/sinoptik/compare/v0.2.2...v0.2.3
|
||||
[0.2.2]: https://git.luon.net/paul/sinoptik/compare/v0.2.1...v0.2.2
|
||||
|
|
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sinoptik"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
authors = [
|
||||
"Admar Schoonen <admar@luon.net",
|
||||
"Paul van Tilburg <paul@luon.net>"
|
||||
|
@ -12,14 +12,14 @@ repository = "https://git.luon.net/paul/sinoptik"
|
|||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
cached = { version = "0.34.0", features = ["async"] }
|
||||
cached = { version = "0.42.0", features = ["async"] }
|
||||
chrono = "0.4.19"
|
||||
chrono-tz = "0.6.1"
|
||||
chrono-tz = "0.8.1"
|
||||
csv = "1.1.6"
|
||||
geocoding = "0.3.1"
|
||||
geocoding = "0.4.0"
|
||||
image = "0.24.1"
|
||||
reqwest = { version = "0.11.9", features = ["json"] }
|
||||
rocket = { version = "0.5.0-rc.2", features = ["json"] }
|
||||
rocket = { version = "0.5.0-rc.3", features = ["json"] }
|
||||
thiserror = "1.0.31"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
21
README.md
21
README.md
|
@ -67,7 +67,6 @@ GET /forecast?address=Stationsplein,Utrecht&metrics[]=all
|
|||
|
||||
or directly by using its geocoded position:
|
||||
|
||||
|
||||
```http
|
||||
GET /forecast?lat=52.0902&lon=5.1114&metrics[]=all
|
||||
```
|
||||
|
@ -75,8 +74,8 @@ GET /forecast?lat=52.0902&lon=5.1114&metrics[]=all
|
|||
### Metrics
|
||||
|
||||
When querying, the metrics need to be selected. It can be one of: `AQI`, `NO2`,
|
||||
`O3`, `PAQI`, `PM10`, `pollen`, `precipitation` or `UVI`. If you use metric `all`, or
|
||||
`all` is part of the selected metrics, all metrics will be retrieved.
|
||||
`O3`, `PAQI`, `PM10`, `pollen`, `precipitation` or `UVI`. If you use metric
|
||||
`all`, or `all` is part of the selected metrics, all metrics will be retrieved.
|
||||
Note that the parameter "array" notation as well as the repeated parameter
|
||||
notation are supported. For example:
|
||||
|
||||
|
@ -86,7 +85,7 @@ GET /forecast?address=Stationsplein,Utrecht&metrics=AQI&metrics=pollen
|
|||
GET /forecast?address=Stationsplein,Utrecht&metrics=all
|
||||
```
|
||||
|
||||
### Response
|
||||
### Forecast responses
|
||||
|
||||
The response of the API is a JSON object that contains three fixed fields:
|
||||
|
||||
|
@ -160,8 +159,8 @@ selecting the maximum value for each hour:
|
|||
|
||||
#### Errors
|
||||
|
||||
If geocoding of an address is requested but fails, a not found error is returned (HTTP 404).
|
||||
with the following body (this will change in the future):
|
||||
If geocoding of an address is requested but fails, a not found error is
|
||||
returned (HTTP 404). with the following body (this will change in the future):
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -173,10 +172,10 @@ with the following body (this will change in the future):
|
|||
}
|
||||
```
|
||||
|
||||
If for any specific metric an error occurs, the list with forecast items will be absent.
|
||||
However, the `errors` field will contain the error message for each failed metric.
|
||||
For example, say Buienradar is down and precipitation forecast items can not be
|
||||
retrieved:
|
||||
If for any specific metric an error occurs, the list with forecast items will
|
||||
be absent. However, the `errors` field will contain the error message for each
|
||||
failed metric. For example, say Buienradar is down and precipitation forecast
|
||||
items can not be retrieved:
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -210,7 +209,7 @@ or directly by using its geocoded position:
|
|||
GET /map?lat=52.0902&lon=5.1114&metric=pollen
|
||||
```
|
||||
|
||||
### Response
|
||||
### Map responses
|
||||
|
||||
The response is a PNG image with a crosshair drawn on the map. If geocoding of
|
||||
an address fails or if the position is out of bounds of the map, nothing is
|
||||
|
|
22
src/lib.rs
22
src/lib.rs
|
@ -1,9 +1,17 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
missing_copy_implementations,
|
||||
missing_debug_implementations,
|
||||
rust_2018_idioms,
|
||||
rustdoc::broken_intra_doc_links
|
||||
rustdoc::broken_intra_doc_links,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
renamed_and_removed_lints,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
|
@ -60,7 +68,7 @@ pub(crate) enum Error {
|
|||
UnsupportedMetric(Metric),
|
||||
}
|
||||
|
||||
impl<'r, 'o: 'r> rocket::response::Responder<'r, 'o> for Error {
|
||||
impl<'r, 'o: 'r> Responder<'r, 'o> for Error {
|
||||
fn respond_to(self, _request: &'r Request<'_>) -> rocket::response::Result<'o> {
|
||||
eprintln!("💥 Encountered error during request: {}", self);
|
||||
|
||||
|
@ -155,7 +163,7 @@ fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
|
|||
.attach(AdHoc::on_liftoff("Maps refresher", |_| {
|
||||
Box::pin(async move {
|
||||
// We don't care about the join handle nor error results?
|
||||
let _ = rocket::tokio::spawn(maps_refresher);
|
||||
let _refresher = rocket::tokio::spawn(maps_refresher);
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
@ -205,8 +213,8 @@ mod tests {
|
|||
let response = client.get("/forecast?address=eindhoven").dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
||||
assert_f64_near!(json["lat"].as_f64().unwrap(), 51.4392648);
|
||||
assert_f64_near!(json["lon"].as_f64().unwrap(), 5.478633);
|
||||
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.44855695, 1e-8);
|
||||
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.45012252, 1e-8);
|
||||
assert_matches!(json["time"], JsonValue::Number(_));
|
||||
assert_matches!(json.get("AQI"), None);
|
||||
assert_matches!(json.get("NO2"), None);
|
||||
|
@ -223,8 +231,8 @@ mod tests {
|
|||
.dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
|
||||
assert_f64_near!(json["lat"].as_f64().unwrap(), 51.4392648);
|
||||
assert_f64_near!(json["lon"].as_f64().unwrap(), 5.478633);
|
||||
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.44855695, 1e-8);
|
||||
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.45012252, 1e-8);
|
||||
assert_matches!(json["time"], JsonValue::Number(_));
|
||||
assert_matches!(json.get("AQI"), Some(JsonValue::Array(_)));
|
||||
assert_matches!(json.get("NO2"), Some(JsonValue::Array(_)));
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -1,9 +1,17 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
missing_copy_implementations,
|
||||
missing_debug_implementations,
|
||||
rust_2018_idioms,
|
||||
rustdoc::broken_intra_doc_links
|
||||
rustdoc::broken_intra_doc_links,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
renamed_and_removed_lints,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
|
|
|
@ -393,7 +393,7 @@ fn sample<I: GenericImageView<Pixel = Rgba<u8>>>(
|
|||
.expect("Maximum color is always a map key color") as u8;
|
||||
|
||||
samples.push(Sample { time, score });
|
||||
time = time + chrono::Duration::seconds(interval as i64);
|
||||
time += chrono::Duration::seconds(interval);
|
||||
offset += width;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
//! and <https://www.buienradar.nl/overbuienradar/gratis-weerdata>.
|
||||
|
||||
use cached::proc_macro::cached;
|
||||
use chrono::offset::TimeZone;
|
||||
use chrono::serde::ts_seconds;
|
||||
use chrono::{DateTime, Datelike, Duration, NaiveTime, ParseError, Utc};
|
||||
use chrono::{DateTime, Datelike, Duration, NaiveTime, ParseError, TimeZone, Utc};
|
||||
use chrono_tz::Europe;
|
||||
use csv::ReaderBuilder;
|
||||
use reqwest::Url;
|
||||
|
@ -66,10 +65,10 @@ impl TryFrom<Row> for Item {
|
|||
/// time zone.
|
||||
fn parse_time(t: &str) -> Result<DateTime<Utc>, ParseError> {
|
||||
// First, get the current date in the Europe/Amsterdam time zone.
|
||||
let today = Utc::now().with_timezone(&Europe::Amsterdam).date();
|
||||
let today = Utc::now().with_timezone(&Europe::Amsterdam).date_naive();
|
||||
// Then, parse the time and interpret it relative to "today".
|
||||
let ntime = NaiveTime::parse_from_str(t, "%H:%M")?;
|
||||
let ndtime = today.naive_local().and_time(ntime);
|
||||
let ndtime = today.and_time(ntime);
|
||||
// Finally, interpret the naive date/time in the Europe/Amsterdam time zone and convert it to
|
||||
// the UTC time zone.
|
||||
let ldtime = Europe::Amsterdam.from_local_datetime(&ndtime).unwrap();
|
||||
|
@ -98,8 +97,9 @@ fn fix_items_day_boundary(items: Vec<Item>) -> Vec<Item> {
|
|||
let now = Utc::now().with_timezone(&Europe::Amsterdam);
|
||||
// Use noon on the same day as "now" as a comparison moment.
|
||||
let noon = Europe::Amsterdam
|
||||
.ymd(now.year(), now.month(), now.day())
|
||||
.and_hms(12, 0, 0);
|
||||
.with_ymd_and_hms(now.year(), now.month(), now.day(), 12, 0, 0)
|
||||
.single()
|
||||
.expect("Invalid date: input date is invalid or not unambiguous");
|
||||
|
||||
if now < noon {
|
||||
// It is still before noon, so bump timestamps after noon a day back.
|
||||
|
@ -107,7 +107,7 @@ fn fix_items_day_boundary(items: Vec<Item>) -> Vec<Item> {
|
|||
.into_iter()
|
||||
.map(|mut item| {
|
||||
if item.time > noon {
|
||||
item.time = item.time - Duration::days(1)
|
||||
item.time -= Duration::days(1)
|
||||
}
|
||||
item
|
||||
})
|
||||
|
@ -118,7 +118,7 @@ fn fix_items_day_boundary(items: Vec<Item>) -> Vec<Item> {
|
|||
.into_iter()
|
||||
.map(|mut item| {
|
||||
if item.time < noon {
|
||||
item.time = item.time + Duration::days(1)
|
||||
item.time += Duration::days(1)
|
||||
}
|
||||
item
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue