Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
teen phone cutie
Jun 18, 2012

last year i rewrote something awful from scratch because i hate myself
my work gives learning stipends and i want to learn rust (i think). is there any definitive guides or are the usual recommendations (udemy, pluralsight, etc) similar?

or are these docs just what i should be focusing on? https://doc.rust-lang.org/1.0.0-alpha/book/README.html

e: sorry i should also add javascript and python are my strongest languages, so i'm not coming in fresh

Adbot
ADBOT LOVES YOU

teen phone cutie
Jun 18, 2012

last year i rewrote something awful from scratch because i hate myself
hello. i decided to learn Rust this week and to get my hands dirty with it, I decided to build an authorization API that I previously built in Python. It's going well so far

What I've got working is:

a fully dockerized rust HTTP server (using Actix), Postgres, and Nginx
I've got the app talking to the database and have written a few initial migrations to get the DB set up and have those run on app start
a bunch of endpoints returning dummy data like POST /register, POST /login, GET /profile

what I am struggling big time with is the actual rust code and was hoping someone could help me out. I know i should probably just take a pause and spend a couple days watching some more Youtube, but I'm really trying to learn by fire here.

So basically what I'm trying to do is return multiple errors to the user, based on the request body they send to me. So this for example:

JSON code:
// POST /register
{
   username: 'hello'
}
would return this

JSON code:
[{
   error: 'email missing',
   field: 'email'
}, {
   error: 'password missing',
   field: 'password'
}]
I understand Rust wants you to learn on compile-time errors, but I'm not quite understanding the best approach to getting the JSON, iterating over the keys, and creating a list of errors for each missing key.

Actix exposes the request JSON in either Bytes or actual JSON like so

Rust code:
use actix_web::{web, HttpRequest, HttpResponse};

pub async fn register(request: HttpRequest, request_body: web::Bytes) -> HttpResponse {
}
or

Rust code:
use actix_web::{web, HttpRequest, HttpResponse};

pub async fn register(request: HttpRequest, request_body: web::Json<SomeStruct>) -> HttpResponse {
}
I tried writing up a validator method like this, but I'm not really sure the best way to actually convert the JSON into a format this method expects:

Rust code:
use serde::Serialize;
use serde_json::Value;

#[derive(Clone, Serialize)]
pub struct BadPayload {
    error: String,
    field: String,
}

// first argument is your payload
// second is the keys that are required
pub fn validate_payload(
    payload: Value,
    keys: Vec<&str>,
) -> Result<(), Vec<BadPayload>> {
    let mut result = vec![];
    // iterate over the required keys and see if they exist in the payload
    for key in keys.iter() {
        match payload.get(&key.to_string()) {
            Option::Some(val) => (),
            Option::None => result.push(BadPayload {
                error: format!("key {} is missing.", key),
                field: key.to_string(),
            }),
        };
    }

    match result.is_empty() {
        true => Ok(()),
        false => Err(result.clone()),
    }
}

teen phone cutie
Jun 18, 2012

last year i rewrote something awful from scratch because i hate myself
okay after some battling, here's what I came up with

the endpoint:

Rust code:
pub async fn register(request: HttpRequest, request_body: web::Bytes) -> HttpResponse {
    let maybe_info: Info;

    let register = LoginResponse {
        token: "1234".to_string(),
        username: "dummy-user".to_string(),
    };

    // return either 400 or 200 based on the existance of the errors
    match validate_json(&request_body, &vec!["username", "password"]) {
        Ok(result) => HttpResponse::Ok().json(web::Json(register)),
        Err(err) => HttpResponse::Ok().json(web::Json(err))
    }
}
and the actual validating function:

Rust code:
fn validate_json<'a>(
    json: &web::Bytes,
    keys: &'a Vec<&str>,
) -> Result<HashMap<&'a str, serde_json::Value>, Vec<BadPayload>>  {
    // convert json to string
    let json_as_string = &String::from_utf8(json.to_vec()).unwrap();
    // create a hashmap from the passed json
    let lookup: HashMap<String, serde_json::Value> = serde_json::from_str(json_as_string).unwrap();
    // another hashmap for only the key/value pairs we choose to accept from the user
    let mut map = HashMap::new();

    let mut errors = vec![];

    // iterate over all the allowed keys
    for key in keys {
        // if we can find it in the json, copy it to the hashmap
        match lookup.get(*key) {
            Option::Some(val) => {
                map.insert(*key, val.clone());
            },
            // if not, add it to the error vector
            Option::None => {
                errors.push(BadPayload {
                    error: format!("key {} is missing.", *key),
                    field: key.to_string(),
                });
            }
        };
    }

    // return based on error vector existance
    match errors.is_empty() {
        true => Ok(map),
        false => Err(errors.clone()),
    }
}

teen phone cutie
Jun 18, 2012

last year i rewrote something awful from scratch because i hate myself

gonadic io posted:

Certainly looks functional. Some advice to improve it:
- serde_json has from_slice which avoids the String step

thanks for the help! this was the one that was specifically tripping me up though because from_slice has no .get() method. Is there a way to check for the existance of a key without panicking that I was just not finding in my google searches?

as far as I can tell, I can only do something like

Rust code:
serde_json::from_slice(json).username
serde_json::from_slice(json).password
serde_json::from_slice(json).blahblah // panic

teen phone cutie fucked around with this message at 22:12 on Sep 26, 2023

teen phone cutie
Jun 18, 2012

last year i rewrote something awful from scratch because i hate myself

gonadic io posted:

Look at the type signatures of the from_str and from_slice methods. Both can parse into anything that implements the Deserialize trait. If you then click on that and scroll down, you'll see that hashmap is one of those things.
It might be that you need a type annotation like you already have.

oh huh i told the method to return a hashmap and.....it just worked. This seems like magic.

well thank you again. do you know of any docs that explains this kind of type signature conversion to me like i am a child? Python and Typescript are my only background, and some of this stuff is really hard to wrap my head around. Or rather, is this just something special serde_json is doing that isn't typical?

e: we're in business now


teen phone cutie fucked around with this message at 22:45 on Sep 26, 2023

Adbot
ADBOT LOVES YOU

teen phone cutie
Jun 18, 2012

last year i rewrote something awful from scratch because i hate myself
thanks so much for the direction on this really! I joined 2 different discords this week asking these questions and got a bunch of answers ranging from "I would just have all the validation live in the client" or "i wouldn't aggregate errors at all and just return a single 'form invalid' error" and the best one, "just use a library for this" and other completely useless feedback that isn't teaching me how to use rust

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply