Work with struct, tuple and match

#1

I’ve the below struct:

use chrono::prelude::*;

#[derive(Serialize, Deserialize, Debug)]
pub struct Person {
    pub id: usize,
    pub fname: String,
    pub lname: String,
    pub uname: String,
    pub pswd: String,
    pub role: String,
    pub emil: String,
    pub created_on: NaiveDateTime,
    pub last_login: NaiveDateTime
}

And with rocket. and postgres and recieving username and password as json, reading the database to confirm user existence with correct password using the below code:

#[derive(Serialize, Deserialize, Debug)]
pub struct Login_user{
    pub name: String,
    pub password: String
}

#[post("/login", format = "application/json", data = "<user>")]
pub fn user_login(mut cookies: Cookies, user: Json<Login_user>) -> &'static str {
    println!("user: {:#?}", user);

    let mut uid: i32 = 0;
    let mut uname = String::new();
    let mut pswd = String::new();
    let mut person = Person {
        id: 0,
        fname: "".to_string(),
        lname: "".to_string(),
        uname: "".to_string(),
        pswd: "".to_string(),
        role: "".to_string(),
        emil: "".to_string(),
        created_on: Local::now().naive_local(),
        last_login: Local::now().naive_local()
    };
    for row in &conn().query("
         SELECT * FROM public.users
         where username = $1
     ", &[&user.name]).unwrap() {
        uid = row.get(0);
        uname = row.get(1);
        pswd = row.get(2);

        person = Person {
            id: uid as usize,
            fname: row.get(4),
            lname: row.get(5),
            uname: uname,
            pswd: pswd,
            role: row.get(3),
            emil: row.get(6),
            created_on: row.get(7),
            last_login: row.get(8)
        };
    }
    println!("Found person {:#?}", &person);
    match (user.name, user.password) {
            (person.uname, person.pswd) => {
                        cookies.add_private(Cookie::new("logged", "YES"));
                        "user found"
                    }
            (person.uname, _) => "wrong password",
            _                 => "user not found"
        }
}

But I’m getting the below error:

> error: expected one of `)`, `,`, or `@`, found `.`
>   --> src\routes\login.rs:62:20
>    |
> 62 |             (person.uname, person.pswd) => {
>    |                    ^ expected one of `)`, `,`, or `@` here

Any idea how to fix it, this is the main point of this thread.

I’ve 2 other points appreciate if got resolved:

  1. How can i ensure the match is done after the SQL statement check, I tried adding it to the .unwrap(){...} block, but failed.
  2. How can I define default struct values for Person, so I do not need to repeat the code as above.
0 Likes

#2

I don’t think a match is the right fit for what you’re trying to do here - the stuff on the left hand side of the arrow has to be a pattern, not a dynamic value. Putting an identifier on the left hand side of a match effectively creates a new variable with that name.

In this case, I think you’re better off just using plain old if statements.

1 Like

#3

Thanks, it is ok with old if, can you help with the other 2 points

0 Likes

#4

I can try :slight_smile: Although I haven’t used the postgres crate, so this is all just my guessing based on the API docs.

I don’t think you need to initialize all of those variables up front - you’re just going to overwrite them straight away! Also, since you only care about finding a single user, there’s no need to loop over all the rows found. With this in mind, you could simplify your code quite drastically:

// Login_user should be called LoginUser - types should
// always be in UpperCascalCase!

#[post("/login", format = "application/json", data = "<user>")]
pub fn user_login(mut cookies: Cookies, user: Json<Login_user>) -> &'static str {
    println!("user: {:#?}", user);

    let query = "SELECT * FROM public.users where username = $1";

    // Note that this will panic if there's zero rows -
    // you should add some checks to make sure there is a row 0 first
    let row = &conn().query(query, &[&user.name]).unwrap().get(0);

    // It'd probably be more maintainable if you referred to columns by name
    // instead of index - row.get can take a string!
    let person = Person {
        id: row.get(0) as usize;
        uname: row.get(1),
        pswd: row.get(2),
        role: row.get(3),
        fname: row.get(4),
        lname: row.get(5),
        emil: row.get(6),
        created_on: row.get(7),
        last_login: row.get(8),
    };

    println!("Found person {:#?}", &person);

    if user.name == person.uname {
        if user.password == person.pswd {
            cookies.add_private(Cookie::new("logged", "YES"));
            "user found"
        } else {
            "wrong password"
        }
    } else {
        "user not found"
    }
}

You could simplify it further by just totally getting rid of the Person struct and just comparing against the row data directly, but I leave it to you if you want to do that :slight_smile:

1 Like

#5

Thank, I got an error at:

   |
30 |     let firstRow = &conn().query(query, &[&user.name]).unwrap().get(0);
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       - temporary value is freed at the end
of this statement
   |                     |
   |                     creates a temporary which is freed while still in use

So, I used it as:

    let row = &conn().query(query, &[&user.name]).unwrap();
    let firstRow = row.get(0);

    let uid: i32 = firstRow.get("user_id");
    let person = Person::new(
        uid as usize, firstRow.get("first_name"), firstRow.get("last_name"),
        firstRow.get("username"), firstRow.get("password"), firstRow.get("role"),
        firstRow.get("email"), firstRow.get("created_on"),
        firstRow.get("last_login")
    );

As I redefined the Person struct to be:

use chrono::prelude::*;

#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Person {
    pub id: usize,
    pub fname: String,
    pub lname: String,
    pub uname: String,
    pub pswd: String,
    pub role: String,
    pub emil: String,
    pub created_on: NaiveDateTime,
    pub last_login: NaiveDateTime
}

// Implementation block, all `Point` methods go in here
impl Person {
    // This is a static method // Person::New()
    pub fn new(id: usize, fname: String, lname: String, uname: String, pswd: String, role: String,
           emil: String, created_on: NaiveDateTime, last_login: NaiveDateTime) -> Self { // Person
        Person {
            id: id,
            fname: fname,
            lname: lname,
            uname: uname,
            pswd: pswd,
            role: role,
            emil: emil,
            created_on: created_on,
            last_login: last_login
        }
    }
    // This is an instance method // Person.who()
    pub fn who(&self) -> String {
        format!("{} {}", self.fname, self.lname)
    }
}

impl Default for Person {
    fn default() -> Self {  // Person
        Person::new(
            usize::default(), String::default(), String::default(),
            String::default(), String::default(), String::default(),
            String::default(),
            Local::now().naive_local(), Local::now().naive_local()
        )
    }
}

Then things run smoothly.

1 Like

#6

Ah, sorry, I didn’t get chance to test the code so I didn’t realize that would fall foul of the borrow checker. Glad you were able to fix it :slight_smile:

0 Likes