Ogeon / rustful

[OUTDATED] A light HTTP framework for Rust
https://docs.rs/rustful
Apache License 2.0
862 stars 51 forks source link

jsonrpc for rustful #131

Closed greenpdx closed 7 years ago

greenpdx commented 7 years ago

I am new at rust and I am having ownership problems.

My json rps functions look this this,

fn cmd_hello(data: &Database, context: Context, cmd: Value) -> Result<Option<String>, Error>{} 

I copied the the code from the todo.rs to parse the context.body let todo: Value = try!(serde_json::from_reader(context.body).maperr(|| Error::ParseError));

then I get the command let cmd = todo.get("cmd").unwrap().as_str().unwrap()

then send it to the correct handler match cmd { "hello" => cmdhello(data, context, todo), .... => Err(Error::CmdNotFound) }

I am getting ownership errors on both context and todo. I understand context.body is where I borrow context and todo.get() is where I borrow todo. BUt knowing where the problem is does not help.

Could some one please post a small snippet of code that I could use solve this problem.
I plan to use a HashMap later but for now just getting it working is more important.

Ogeon commented 7 years ago

Hi! I would guess that the reason for your ownership problems is that cmd borrows from todo (as_str returns &str), so you can't move todo into cmd_hello until cmd is out of scope. A simple solution would be to convert the command to a String by doing something like this:

let cmd = todo.get("cmd").unwrap().as_str().unwrap().to_owned();
// or
let cmd: String = todo.get("cmd").unwrap().as_str().unwrap().into();
// or
let cmd = String::from(todo.get("cmd").unwrap().as_str().unwrap());
// or
let cmd = todo.get("cmd").unwrap().as_str().unwrap().to_string();

Which one you pick is a matter of taste. :smile: I hope that helps!

greenpdx commented 7 years ago

Please let me confirm what you said. .to_owned() makes cmd the owner cmd: String = ....into() move it into cmd String::from() pulls ownership from todo .to_string() does it just convert or does it take ownership?

Next, if "cmd" is not found in todo.get(), None is returned, will it get a crash if None is returned?

Ogeon commented 7 years ago

They will all have the exact same result, just through slightly different paths.

Next, if "cmd" is not found in todo.get(), None is returned, will it get a crash if None is returned?

Yes, and that's not particularly desirable in a web server. What you can do, since both of the unwrap cases are for Option, is sending a response to the user when something is not right:

let cmd = if let Some(cmd) = todo.get("cmd")and_then(|v| v.as_str()).and_then(String::from) {
    cmd // just returns it to the variable
} else {
    return Err(...); // something about "cmd" being a mandatory string
};

The .and_then(String::from) is just the same thing as doing String::from(...) on the content of the Option, but without an extra closure/lambda. We are just passing the function in there instead.

Ogeon commented 7 years ago

(Updated with some links)

greenpdx commented 7 years ago

Thank you for helping. I have programmed in many languages and like the concepts of rust but getting my head around the syntax of rust is "interesting"

let cmd = if let Some(cmd) = todo.get("cmd")and_then(|v| v.as_str()).and_then(String::from) { cmd // just returns it to the variable

the if let Some(cmd) does the same thing as unwrap() ? it is needed because an Option is passed back. todo.get("cmd") finds the object in todo. and_then() returns None is "cmd" is not found or calls the |v| v.as_str() converting it &str that returns a Option. the next andthen converts Option<&str> to Option. I an getting an error here. .andthen(String::from) it is Option but it expects String. I understand that "_" is being passed to String::from

Ogeon commented 7 years ago

Sorry, .and_then(String::from) should be .map(String::from). I suggest that you read about the methods of Option in the documentation, where both of these are explained.

if let <pattern> = <expression> { is like match, but for only one case, so if let Some(x) = ... { x } else { 0 } will result in x if it gets Some or 0 if it gets None. You could say that it's like unwrap, but you can choose what to do in each case.

greenpdx commented 7 years ago

So the difference between map and and_then is map() just executes the function and and_then checks for None then executes the function.

I have copied the code in question here. I am still getting and error on passing context to cmd_hello(tnvdata, context, todo). the from_reader(context.body) takes some ownership. I understand that when I read the context.body I take ownership of part of context. I was thinking I could copy "todo" to something else but then how do I release the ownership of that todo taken from context.

so I would take copy release that is what I wanted to do in rdBody()

fn cmd_hello(tnvdata: &Database, context: Context, cmd: Value) -> Result<Option, Error> { let mut rslt = json!({"salt": "1023456", "nonce":"12345"}); //just temp Ok(serde_json::to_string(&rslt)) }

fn rdBody(context: Context) -> Result<Option, Error> { let todo: Value = try!(serde_json::from_reader(context.body).maperr(|| Error::ParseError)); println!("{:?}", todo.as_object() ); Ok(Some(todo)) }

fn tnv_post(tnvdata: &Database, context: Context) -> Result<Option, Error> { // let b = rdBody(context); let todo: Value = try!(serde_json::from_reader(context.body).maperr(|| Error::ParseError)); println!("{:?}", todo); // let obj = todo.as_object().unwrap(); let cmd = if let Some(cmd) = todo.get("cmd").and_then(|v| v.as_str()).map(String::from) { cmd // just returns it to the variable } else { return Err(Error::CmdNotFound); // something about "cmd" being a mandatory string }; println!("{:?}", cmd); let hello = "hello".to_string(); match cmd { hello => cmd_hello(tnvdata, context, todo), // "login" => cmd_login(tnvdata, context, todo), // "pass" => cmd_pass(tnvdata, context, todo), // "regdata" => cmdregdata(tnvdata, context, todo), => Err(Error::CmdNotFound) }

// let mut rslt = json!({"salt": "1023456", "nonce":"12345"}); // Ok(Some(serde_json::to_string(&rslt).unwrap())) }

Ogeon commented 7 years ago

So the difference between map and and_then is map() just executes the function and and_then checks for None then executes the function.

Nah, and_then is like map, but expects another Option to be returned instead of just the value. That's why it's good for chaining operations that return Option.

the from_reader(context.body) takes some ownership.

I didn't notice that. You don't have to move body out of context, you can use a reference: from_reader(&mut context.body)

greenpdx commented 7 years ago

I tried &context.body and it failed but &mut context.body works, why?

Ogeon commented 7 years ago

Because reading is an operation that changes the body. It consumes its content, so it has to be mutable.

greenpdx commented 7 years ago

That is because context.body is a reader stream. Ah!

I have only been programming rust for about a week. Talk about jumping into the deep end.

My final goal is to have a mongodb backed jsonrpc like server. Right now I am working on the login and session part. I am using tweetnacl as the javascript library. What is the best crypto library for rust that supports ed25519?

Thanks for all the help.

Ogeon commented 7 years ago

No problem! Feel free to ask again if you have any questions about Rustful.

Deep end or not, a clear goal and some determination will you far. It's at least a learning experience!

Unfortunately I don't really know much about the crypto ecosystem. Your best bet is to search crates.io and ask in the user forum or on Reddit.

Good luck!