ripress 2.5.1

An Express.js-inspired web framework for Rust
Documentation
#[cfg(test)]
mod tests {
    use crate::{
        helpers::{
            extract_boundary, find_subsequence, get_all_query, parse_multipart_form, path_matches,
        },
        req::query_params::QueryParams,
    };

    #[test]
    fn test_exact_match() {
        assert!(path_matches("/api", "/api"));
        assert!(path_matches("", ""));
        assert!(path_matches("/", "/"));
    }

    #[test]
    fn test_prefix_with_slash() {
        assert!(path_matches("/api", "/api/v1"));
        assert!(path_matches("/foo", "/foo/bar/baz"));
        assert!(path_matches("/", "/something"));
    }

    #[test]
    fn test_no_match() {
        assert!(!path_matches("/api", "/apix"));
        assert!(!path_matches("/foo", "/foobar"));
        assert!(!path_matches("/foo", "/fo"));
        assert!(!path_matches("/foo", "/bar/foo"));
    }

    #[test]
    fn test_prefix_is_empty() {
        assert!(path_matches("", ""));
        assert!(path_matches("", "/anything"));
    }

    #[test]
    fn test_path_is_empty() {
        assert!(!path_matches("/api", ""));
        assert!(path_matches("", ""));
    }

    #[test]
    fn test_trailing_slash_in_prefix() {
        assert!(path_matches("/api/", "/api/"));
        assert!(path_matches("/api/", "/api/foo"));
        assert!(!path_matches("/api/", "/api")); 
    }

    #[test]
    fn test_get_all_query_empty() {
        let queries = QueryParams::new();
        let result = get_all_query(&queries);
        assert_eq!(result, "");
    }

    #[test]
    fn test_get_all_query_single() {
        let mut queries = QueryParams::new();
        queries.insert("key", "value");
        let result = get_all_query(&queries);
        assert_eq!(result, "key=value");
    }

    #[test]
    fn test_get_all_query_multiple() {
        let mut queries = QueryParams::new();
        queries.insert("foo", "bar");
        queries.insert("baz", "qux");
        let result = get_all_query(&queries);
        let expected1 = "foo=bar&baz=qux";
        let expected2 = "baz=qux&foo=bar";
        assert!(result == expected1 || result == expected2);
    }

    #[test]
    fn test_get_all_query_url_encoding() {
        let mut queries = QueryParams::new();
        queries.insert("sp ce", "v@lue+1");
        let result = get_all_query(&queries);
        assert!(result.contains("sp+ce="));
        assert!(result.contains("v%40lue%2B1"));
    }

    #[test]
    fn extracts_normal() {
        let ct = "multipart/form-data; boundary=abcde1234";
        assert_eq!(extract_boundary(ct), Some("abcde1234".to_string()));
    }

    #[test]
    fn extracts_with_quotes() {
        let ct = "multipart/form-data; boundary=\"abcde1234\"";
        assert_eq!(extract_boundary(ct), Some("abcde1234".to_string()));
    }

    #[test]
    fn extracts_with_whitespace() {
        let ct = "multipart/form-data ; boundary = abcde1234 ";
        assert_eq!(extract_boundary(ct), Some("abcde1234".to_string()));
    }

    #[test]
    fn extracts_with_other_params() {
        let ct = "multipart/form-data; charset=UTF-8; boundary=alpha";
        assert_eq!(extract_boundary(ct), Some("alpha".to_string()));
    }

    #[test]
    fn extracts_with_other_order() {
        let ct = "multipart/form-data; boundary=foo; charset=UTF-8";
        assert_eq!(extract_boundary(ct), Some("foo".to_string()));
    }

    #[test]
    fn extracts_with_uppercase_key() {
        let ct = "multipart/form-data; BOUNDARY=foobar";
        assert_eq!(extract_boundary(ct), Some("foobar".to_string()));
    }

    #[test]
    fn returns_none_missing() {
        let ct = "application/json";
        assert_eq!(extract_boundary(ct), None);
    }

    #[test]
    fn returns_none_empty_boundary() {
        let ct = "multipart/form-data; boundary=";
        assert_eq!(extract_boundary(ct), None);
    }

    #[test]
    fn extracts_nonstandard_no_mime() {
        let ct = "something-else; boundary=yo";
        assert_eq!(extract_boundary(ct), Some("yo".to_string()));
    }

    #[test]
    fn finds_basic_match() {
        let haystack = b"abcdefg";
        let needle = b"cde";
        assert_eq!(find_subsequence(haystack, needle), Some(2));
    }

    #[test]
    fn finds_at_start() {
        let haystack = b"abc";
        let needle = b"ab";
        assert_eq!(find_subsequence(haystack, needle), Some(0));
    }

    #[test]
    fn finds_at_end() {
        let haystack = b"xyzabc";
        let needle = b"abc";
        assert_eq!(find_subsequence(haystack, needle), Some(3));
    }

    #[test]
    fn returns_none_if_no_match() {
        let haystack = b"abcdef";
        let needle = b"gh";
        assert_eq!(find_subsequence(haystack, needle), None);
    }

    #[test]
    fn returns_zero_on_empty_needle() {
        let haystack = b"abcdef";
        let needle: &[u8] = b"";
        assert_eq!(find_subsequence(haystack, needle), Some(0));
    }

    #[test]
    fn handles_empty_haystack() {
        let haystack: &[u8] = b"";
        let needle = b"abc";
        assert_eq!(find_subsequence(haystack, needle), None);
    }

    #[test]
    fn handles_both_empty() {
        let haystack: &[u8] = b"";
        let needle: &[u8] = b"";
        assert_eq!(find_subsequence(haystack, needle), Some(0));
    }

    #[test]
    fn finds_middle_multiple() {
        let haystack = b"aaabaaabaaa";
        let needle = b"ba";
        assert_eq!(find_subsequence(haystack, needle), Some(3));
    }

    fn make_body(parts: &[(&str, &str, Option<&[u8]>)], boundary: &str) -> Vec<u8> {
        let mut body = Vec::new();
        for (i, (field, value, file_bytes)) in parts.iter().enumerate() {
            if i != 0 {
                body.extend_from_slice(b"\r\n");
            }
            body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes());
            if let Some(bytes) = file_bytes {
                body.extend_from_slice(
                    format!(
                        "Content-Disposition: form-data; name=\"{}\"; filename=\"file.bin\"\r\n\r\n",
                        field
                    )
                    .as_bytes(),
                );
                body.extend_from_slice(bytes);
            } else {
                body.extend_from_slice(
                    format!(
                        "Content-Disposition: form-data; name=\"{}\"\r\n\r\n{}",
                        field, value
                    )
                    .as_bytes(),
                );
            }
        }
        body.extend_from_slice(format!("\r\n--{}--", boundary).as_bytes());
        body
    }

    #[test]
    fn parses_simple_field() {
        let boundary = "AaB03x";
        let body = format!(
            "--AaB03x\r\nContent-Disposition: form-data; name=\"submit-name\"\r\n\r\nLarry\r\n--AaB03x--"
        );
        let (fields, files) = parse_multipart_form(body.as_bytes(), &boundary.to_string());
        assert_eq!(fields, vec![("submit-name", "Larry")]);
        assert_eq!(files.len(), 0);
    }

    #[test]
    fn parses_multiple_fields() {
        let boundary = "xyz";
        let body = format!(
            "--xyz\r\nContent-Disposition: form-data; name=\"f1\"\r\n\r\nv1\r\n--xyz\r\nContent-Disposition: form-data; name=\"f2\"\r\n\r\nv2\r\n--xyz--"
        );
        let (fields, files) = parse_multipart_form(body.as_bytes(), &boundary.to_string());
        assert_eq!(fields, vec![("f1", "v1"), ("f2", "v2")]);
        assert_eq!(files.len(), 0);
    }

    #[test]
    fn parses_file_and_field() {
        let boundary = "b";
        let file_content = b"\xDE\xAD\xBE\xEF";
        let body = make_body(
            &[("desc", "mydesc", None), ("upload", "", Some(file_content))],
            boundary,
        );
        let (fields, files) = parse_multipart_form(&body, &boundary.to_string());
        assert!(fields.contains(&("desc", "mydesc")));
        assert_eq!(files.len(), 1);
        assert_eq!(files[0].0, file_content);
        assert_eq!(files[0].1, Some("upload"));
    }

    #[test]
    fn parses_multiple_files_and_fields() {
        let boundary = "b7";
        let content1 = b"filecontent1";
        let content2 = b"filecontent2";
        let body = make_body(
            &[
                ("n1", "v1", None),
                ("file1", "", Some(content1)),
                ("n2", "v2", None),
                ("file2", "", Some(content2)),
            ],
            boundary,
        );
        let (fields, files) = parse_multipart_form(&body, &boundary.to_string());
        assert!(fields.contains(&("n1", "v1")));
        assert!(fields.contains(&("n2", "v2")));
        assert_eq!(files.len(), 2);
        assert_eq!(files[0].0, content1);
        assert_eq!(files[1].0, content2);
        assert_eq!(files[0].1, Some("file1"));
        assert_eq!(files[1].1, Some("file2"));
    }

    #[test]
    fn handles_file_name_variants() {
        let boundary = "multistar";
        let body = format!(
            "--multistar\r\nContent-Disposition: form-data; name=\"file\"; filename*=\"myfile.txt\"\r\n\r\nabc\r\n--multistar--"
        );
        let (fields, files) = parse_multipart_form(body.as_bytes(), &boundary.to_string());
        assert_eq!(fields.len(), 0);
        assert_eq!(files.len(), 1);
        assert_eq!(files[0].1, Some("file"));
        assert_eq!(files[0].0, b"abc");
    }

    #[test]
    fn trims_crlf_on_field() {
        let boundary = "wxc";
        let value = "a_line\r\n";
        let body = format!(
            "--wxc\r\nContent-Disposition: form-data; name=\"nm\"\r\n\r\n{}--wxc--",
            value
        );
        let (fields, files) = parse_multipart_form(body.as_bytes(), &boundary.to_string());
        assert_eq!(fields, vec![("nm", "a_line")]);
        assert_eq!(files.len(), 0);
    }

    #[test]
    fn returns_empty_for_missing_boundary() {
        let boundary = "abs";
        let body = b"--xxx\r\nContent-Disposition: form-data; name=\"nm\"\r\n\r\nvv\r\n--xxx--";
        let (fields, files) = parse_multipart_form(body, &boundary.to_string());
        assert_eq!(fields.len(), 0);
        assert_eq!(files.len(), 0);
    }

    #[test]
    fn handles_non_utf8_file_content() {
        let boundary = "binary";
        let file_content = b"\xF0\x90\x80\x80\xFF";
        let body = make_body(&[("file", "", Some(file_content))], boundary);
        let (_, files) = parse_multipart_form(&body, &boundary.to_string());
        assert_eq!(files.len(), 1);
        assert_eq!(files[0].0, file_content);
    }

    #[test]
    fn handles_no_crlf_after_last_field() {
        let boundary = "plain";
        let body = b"--plain\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nbar--plain--";
        let (fields, files) = parse_multipart_form(body, &boundary.to_string());
        assert_eq!(fields, vec![("foo", "bar")]);
        assert_eq!(files.len(), 0);
    }

    #[test]
    fn trims_crlf_from_file_part() {
        let boundary = "def";
        let file_content = b"abc\r\n";
        let body = format!(
            "--def\r\nContent-Disposition: form-data; name=\"up\"; filename=\"f.txt\"\r\n\r\n{}--def--",
            std::str::from_utf8(file_content).unwrap()
        );
        let (fields, files) = parse_multipart_form(body.as_bytes(), &boundary.to_string());
        assert_eq!(fields.len(), 0);
        assert_eq!(files.len(), 1);
        assert_eq!(files[0].0, b"abc");
    }
}