I wrote a test to verify the POST was parsed correctly. Here is the endpoint:
#[post("/test_weather_data")]
pub(crate) async fn test_weather_data_post(weather_data: String) -> Result<String> {
let wd: Vec<Reading> = serde_json::from_str(&*weather_data).unwrap();
Ok(format!("id: {}, index: {}", &wd[0].id, &wd[0].index))
}
And the test:
#[actix_rt::test]
async fn test_weather_data_ok() {
let mut app = test::init_service(App::new().service(controller::test_weather_data_post)).await;
let payload = r#"[{"measurement_time_default":"2021-03-24T20:50:00+01:00","id":228,"index":201,"field_description":"relative_humidity","measurement":77.5}]"#;
let resp = test::TestRequest::post()
.uri("/test_weather_data")
.header(header::CONTENT_TYPE, "application/json")
.set_payload(payload)
.send_request(&mut app)
.await;
let result = test::read_body(resp).await;
assert_eq!(result, "id: 228, index: 201".as_bytes());
}
The struct:
#[derive(Deserialize, Insertable)]
pub struct Reading {
#[serde(with = "my_date_format")]
pub measurement_time_default: DateTime<Local>,
pub id: i32,
pub index: i32,
pub field_description: String,
pub measurement: f32,
}
Module my_date_format:
mod my_date_format {
use chrono::{DateTime, Local, TimeZone};
use serde::{self, Deserialize, Deserializer};
const FORMAT: &str = "%+";
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Local>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Local
.datetime_from_str(&s, FORMAT)
.map_err(serde::de::Error::custom)
}
}
cargo test
passes on my local mac mini (M1) but fails on github actions
with the error
thread 'test::test_weather_data_ok' panicked at 'called `Result::unwrap()` on an `Err` value: Error("no possible date and time matching input", line: 1, column: 56)', src/controller.rs:63:65
Here is my YML-file on github which is the default handed out by github:
name: Rust
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
There is a SO-thread with a similar exit code 101
but the error in that case was due to directory layout changes so it doesn't seem to apply in this case.
Omitting the as_bytes() in the test gives the same error.
The documentation for chrono
states that datetime_from_str
will only succeed if there's no time zone in the text or the time zone matches the target TimeZone
, which explains the failure if the time zone is different. The docs point to DateTime::parse_from_str
to parse a DateTime
with any time zone into a DateTime<FixedOffset>
object, so something like this would work
DateTime::parse_from_str(&s, FORMAT)
.map(Into::into)
.map_err(serde::de::Error::custom)
Into::into
is needed to map the type from DateTime<FixedOffset>
to DateTime<Local>
to match your return type.
That said, chrono
implements serialization directly with rfc3339 as the default format. You need to enable the serde
feature flag. Then you can get rid of your custom deserialization code and just go with
#[derive(Deserialize, Insertable)]
pub struct Reading {
pub measurement_time_default: DateTime<Local>,
pub id: i32,
pub index: i32,
pub field_description: String,
pub measurement: f32,
}