2

How would you alter the example from Rocket's website to take a date rather than an age/u8?

The example from the website:

#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use] extern crate rocket;

#[get("/hello/<name>/<age>")]
fn hello(name: String, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

fn main() {
    rocket::ignite().mount("/", routes![hello]).launch();
}

I'd like to have more or less the same output (Hello, 58 year old named John!) but have something like this

#[get("/hello/<name>/<birthdate>")]

instead of that

#[get("/hello/<name>/<age>")]

I think the right struct is chrono::DateTime and that somehow rocket::request::FromParam is involved but I'm a bit lost from there.

harvzor
  • 2,832
  • 1
  • 22
  • 40
pandark
  • 313
  • 1
  • 10
  • 1
    What have you tried? It looks like you need to implement `FromParam` for `DateTime`, but this isn't possible due to the orphan rule, so you'll need a wrapper type. – apetranzilla Mar 06 '19 at 18:42
  • Well, I guess I tried that… and it failed, probably for the reason that you said :D (although I'm not sure what the "orphan rule" is) – pandark Mar 06 '19 at 21:42
  • 2
    To avoid conflicts, Rust doesn't allow you to implement a trait where both the trait and the type it's implemented for are from another crate (otherwise, you could have multiple crates implementing the same trait for the same type and have a conflict). To get around this, you could create a wrapper type (e.g. `struct MyDate(chrono::DateTime)`) and implement the trait for that. Then, you can use your wrapper as a parameter for the function, and just extract the value in the body of the function. – apetranzilla Mar 06 '19 at 23:41

3 Answers3

3

Kinda sucks that we have to do this ourselves.

Maybe in the future, there will be a library provided which gives us interop between Rocket and other libraries.

use chrono::NaiveDate;
use chrono::NaiveTime;
use chrono::NaiveDateTime;

// https://stackoverflow.com/questions/25413201/how-do-i-implement-a-trait-i-dont-own-for-a-type-i-dont-own
// https://github.com/SergioBenitez/Rocket/issues/602#issuecomment-380497269
pub struct NaiveDateForm(NaiveDate);
pub struct NaiveTimeForm(NaiveTime);
pub struct NaiveDateTimeForm(NaiveDateTime);

impl<'v> FromFormValue<'v> for NaiveDateForm {
    type Error = &'v RawStr;

    fn from_form_value(form_value: &'v RawStr) -> Result<NaiveDateForm, &'v RawStr> {
        let decoded = form_value.url_decode().map_err(|_| form_value)?;
        if let Ok(date) = NaiveDate::parse_from_str(&decoded, "%Y-%m-%d") {
            return Ok(NaiveDateForm(date));
        }
        Err(form_value)
    }
}

impl<'v> FromFormValue<'v> for NaiveTimeForm {
    type Error = &'v RawStr;

    fn from_form_value(form_value: &'v RawStr) -> Result<Self, Self::Error> {
        let decoded = form_value.url_decode().map_err(|_| form_value)?;
        if let Ok(time) = NaiveTime::parse_from_str(&decoded, "%H:%M:%S%.3f") {
            // if time.nanosecond() >= 1_000_000_000 {
            //     return Err(form_value);
            // }
            return Ok(NaiveTimeForm(time));
        }
        if let Ok(time) = NaiveTime::parse_from_str(&decoded, "%H:%M") {
            return Ok(NaiveTimeForm(time));
        }
        Err(form_value)
    }
}

impl<'v> FromFormValue<'v> for NaiveDateTimeForm {
    type Error = &'v RawStr;

    fn from_form_value(form_value: &'v RawStr) -> Result<NaiveDateTimeForm, &'v RawStr> {
        let decoded = form_value.url_decode().map_err(|_| form_value)?;
        if decoded.len() < "0000-00-00T00:00".len() {
            return Err(form_value)
        }
        let date = NaiveDateForm::from_form_value(RawStr::from_str(&decoded[.."0000-00-00".len()]))
            .map_err(|_| form_value)?;
        let time = NaiveTimeForm::from_form_value(RawStr::from_str(&decoded["0000-00-00T".len()..]))
            .map_err(|_| form_value)?;
        Ok(NaiveDateTimeForm(NaiveDateTime::new(*date, *time)))
    }
}

impl Deref for NaiveDateForm {
    type Target = NaiveDate;
    fn deref(&self) -> &NaiveDate {
        &self.0
    }
}

impl Deref for NaiveTimeForm {
    type Target = NaiveTime;
    fn deref(&self) -> &NaiveTime {
        &self.0
    }
}

impl Deref for NaiveDateTimeForm {
    type Target = NaiveDateTime;
    fn deref(&self) -> &NaiveDateTime {
        &self.0
    }
}

You should then be able to do:

#[get("/hello/<name>/<age>")]
fn hello(name: String, age: NaiveDateTimeForm) -> String {
    // Deref back to chrono::NaiveDatetime
    let date_time = *age;

    // write some code to figure out their age
}

My dependencies:

chrono = { version = "0.4.19", features = ["serde"] }
rocket = "0.4.2"

This implementation is mostly stolen from https://github.com/chronotope/chrono/pull/362/files where someone made a PR to try to get this stuff already into Chrono.

Probably rather than age, you should have birthday so you can calculate their age.

harvzor
  • 2,832
  • 1
  • 22
  • 40
0

NaiveDate can be represented as "number of days since January 1, 1" with type i32. There are methods self.num_days_from_ce() and from_num_days_from_ce(). I believe this is the most convenient way. Documentation

Laney
  • 1,571
  • 9
  • 7
0

i follow harvzor's code with a work version for rocket 0.5.

  use chrono::NaiveDate;
  use chrono::ParseError;
  
  use rocket::request::FromParam;
  
  // https://stackoverflow.com/questions/25413201/how-do-i-implement-a-      trait-i-dont-own-for-a-type-i-dont-own
  // https://github.com/SergioBenitez/Rocket/issues/602#issuecomment-380497269
  pub struct NaiveDateForm(pub NaiveDate);


  impl<'a> FromParam<'a> for NaiveDateForm {
      type Error = ParseError;
  
      fn from_param(param: &'a str) -> Result<Self, Self::Error>{
        match NaiveDate::parse_from_str(&param, "%Y-%m-%d") {
            Ok(date)=> Ok(NaiveDateForm(date)),
            Err(e) =>Err(e),
          }
      }
  }
RyanShao
  • 429
  • 2
  • 9