3

I'm trying to write a wrapper around serde_json & Rocket's FromData to strongly type some of the JSON I exchange with a server.

I can't compile the following code:

extern crate serde_json;
extern crate rocket;
extern crate serde;

use serde::ser::Error;
use serde_json::Value;

use rocket::data::DataStream;
use rocket::outcome::IntoOutcome;

use std::io::Read;

static NULL: Value = serde_json::Value::Null;

pub struct ResponseJSON<'v> {
    success: bool,
    http_code: u16,
    data: &'v serde_json::Value,
}

impl<'v> ResponseJSON<'v> {
    pub fn ok() -> ResponseJSON<'v> {
        ResponseJSON {
            success: true,
            http_code: 200,
            data: &NULL,
        } 
    }
    pub fn http_code(mut self, code: u16) -> ResponseJSON<'v> {
        self.http_code = code;
        self
    }
    pub fn data(mut self, ref_data: &'v serde_json::Value) -> ResponseJSON<'v> {
        self.data = ref_data;
        self
    }

    pub fn from_serde_value(json: &'v serde_json::Value) -> ResponseJSON<'v> {
        if !json["success"].is_null() {
            ResponseJSON::ok()
                .http_code(json["http_code"].as_u64().unwrap() as u16)
                .data(json.get("data").unwrap_or(&NULL))
        } else {
            ResponseJSON::ok()
                .data(json.pointer("").unwrap())
        }
    }
}

impl<'v> rocket::data::FromData for ResponseJSON<'v> {
    type Error = serde_json::Error;

    fn from_data(request: &rocket::Request, data: rocket::Data) -> rocket::data::Outcome<Self, serde_json::Error> {
        if !request.content_type().map_or(false, |ct| ct.is_json()) {
            println!("Content-Type is not JSON.");
            return rocket::Outcome::Forward(data);
        }

        let data_from_reader = data.open().take(1<<20);
        let value = serde_json::from_reader(data_from_reader);
        let unwraped_value : Value = if value.is_ok() { value.unwrap() } else { Value::Null };

        if !unwraped_value.is_null() {
            Ok(ResponseJSON::from_serde_value(&unwraped_value)).into_outcome()
        } else {
            Err(serde_json::Error::custom("Unable to create JSON from reader")).into_outcome()
        }
    }
}

fn main() {
    println!("it runs!");
}

The compiler's error:

   Compiling tests v0.1.0 (file:///Users/bgbahoue/Projects.nosync/tests)
error: `unwraped_value` does not live long enough
  --> src/main.rs:64:48
   |
64 |             Ok(ResponseJSON::from_serde_value(&unwraped_value)).into_outcome()
   |                                                ^^^^^^^^^^^^^^ does not live long enough
...
68 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'v as defined on the body at 53:114...
  --> src/main.rs:53:115
   |
53 |       fn from_data(request: &rocket::Request, data: rocket::Data) -> rocket::data::Outcome<Self, serde_json::Error> {
   |  ___________________________________________________________________________________________________________________^
54 | |         if !request.content_type().map_or(false, |ct| ct.is_json()) {
55 | |             println!("Content-Type is not JSON.");
56 | |             return rocket::Outcome::Forward(data);
...  |
67 | |         }
68 | |     }
   | |_____^

error: aborting due to previous error

Since data_from_reader, value and unwraped_value come from data I thought the compiler could infer that it had the same lifetime but apparently it's not the case. Is there any way I could state that or do something that would work in such a case ?

serde_json::from_reader:

pub fn from_reader<R, T>(rdr: R) -> Result<T> 
where
    R: Read,
    T: DeserializeOwned, 

rocket::data::Data::open:

fn open(self) -> DataStream

rocket::data::DataStream::take:

fn take(self, limit: u64) -> Take<Self>
Boris
  • 991
  • 1
  • 9
  • 15
  • 2
    But your problem probably boils down to: [Is there any way to return a reference to a variable created in a function?](http://stackoverflow.com/questions/32682876/is-there-any-way-to-return-a-reference-to-a-variable-created-in-a-function/32683309#32683309) To which the answer is basically: no. Your `ResponseJson` should own the `serde_json::Value` instead of borrowing it. If you want to sometimes borrow, sometimes own it, you might be interested in [`Cow`](https://doc.rust-lang.org/stable/std/borrow/enum.Cow.html). – Lukas Kalbertodt May 17 '17 at 05:10
  • @LukasKalbertodt there you go. Sorry for missing the first lines. You're correct it boils down to returning a reference to a variable created in a function in order to implement a Trait (FormData in that case). What is weird is that I do manage to get it done in the `from_serde_value(&Value)` function since I'm basically returning a newly created ResponseJSON. I figured it wouldn't be too different in the case of `from_data()` function – Boris May 17 '17 at 05:26
  • As per @LukasKalbertodt suggestion it does compile if ResponseJSON owns the `serde_json::Value` removing all references (in struct definition and function signatures). Adding that as an answer for the sake of discussion / archive – Boris May 17 '17 at 05:46

1 Answers1

1

As per @LukasKalbertodt 's comment above, it does work when ResponseJSON owns the serde_json::Value

Revised code (pasted as is, even though there are better ways to chain some parts of the code)

#![allow(dead_code)]

extern crate serde_json;
extern crate rocket;
extern crate serde;

use serde::ser::Error;
use serde_json::Value;

use rocket::outcome::IntoOutcome;

use std::io::Read;

static NULL: Value = serde_json::Value::Null;

pub struct ResponseJSON { // <-- changed to remove the lifetime parameter
    success: bool,
    http_code: u16,
    data: serde_json::Value, // <- changed to remove the '&'
}

impl ResponseJSON {
    pub fn ok() -> ResponseJSON {
        ResponseJSON {
            success: true,
            http_code: 200,
            data: Value::Null,
        } 
    }
    pub fn http_code(mut self, code: u16) -> ResponseJSON {
        self.http_code = code;
        self
    }
    pub fn data(mut self, data: serde_json::Value) -> ResponseJSON { // <- changed to remove the '&'
        self.data = data;
        self
    }

    pub fn from_serde_value(json: serde_json::Value) -> ResponseJSON { // <- changed to remove the reference & lifetime parameter
        if !json["success"].is_null() {
            ResponseJSON::ok()
                .http_code(json["http_code"].as_u64().unwrap() as u16)
                .data(json.get("data").unwrap_or(&NULL).clone())
        } else {
            ResponseJSON::ok()
                .data(json.pointer("").unwrap().clone())
        }
    }
}

impl rocket::data::FromData for ResponseJSON {
    type Error = serde_json::Error;

    fn from_data(request: &rocket::Request, data: rocket::Data) -> rocket::data::Outcome<Self, serde_json::Error> {
        if !request.content_type().map_or(false, |ct| ct.is_json()) {
            println!("Content-Type is not JSON.");
            return rocket::Outcome::Forward(data);
        }

        let data_from_reader = data.open().take(1<<20);
        let value = serde_json::from_reader(data_from_reader);
        let unwraped_value : Value = if value.is_ok() { value.unwrap() } else { Value::Null };

        if !unwraped_value.is_null() {
            Ok(ResponseJSON::from_serde_value(unwraped_value)).into_outcome() // <- changed to remove the '&' in front of `unwraped_value`
        } else {
            Err(serde_json::Error::custom("Unable to create JSON from reader")).into_outcome()
        }
    }
}

fn main() {
    println!("it compiles & runs");
}

cargo run output

   Compiling tests v0.1.0 (file:///Users/bgbahoue/Projects.nosync/tests)
    Finished dev [unoptimized + debuginfo] target(s) in 1.28 secs
     Running `target/debug/tests`
it compiles & runs

My take is that in that case the ownership (lifetime ?) of the input parameter's data is passed to data_from_reader to value to unwraped_value to the temp ResponseJSON to the returned rocket::data::Outcome; so it seems ok.

With references, the temp ResponseJSON didn't outlive the function end, since it outlived the serde_json::Value from which it was created i.e. unwraped_value lifetime i.e. the function's end; hence the compiler issue.

Not 100% sure of my explaination though, would love your thoughts about that

Boris
  • 991
  • 1
  • 9
  • 15