mirror of
https://github.com/YGGverse/titanite.git
synced 2026-03-31 17:15:33 +00:00
implement separated header mod for success default status
This commit is contained in:
parent
3aaacdd656
commit
7c62a393ed
4 changed files with 75 additions and 37 deletions
|
|
@ -84,7 +84,7 @@ fn test() {
|
||||||
match target {
|
match target {
|
||||||
Response::Success(ref this) => match this {
|
Response::Success(ref this) => match this {
|
||||||
Success::Default(this) => {
|
Success::Default(this) => {
|
||||||
assert_eq!(this.mime, "text/gemini".to_string());
|
assert_eq!(this.header.mime, "text/gemini");
|
||||||
assert_eq!(this.data, "data".as_bytes());
|
assert_eq!(this.data, "data".as_bytes());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -99,7 +99,7 @@ fn test() {
|
||||||
|
|
||||||
match target {
|
match target {
|
||||||
Response::Redirect(ref this) => match this {
|
Response::Redirect(ref this) => match this {
|
||||||
Redirect::Temporary(this) => assert_eq!(this.target, "target".to_string()),
|
Redirect::Temporary(this) => assert_eq!(this.target, "target"),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
},
|
},
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
|
@ -113,7 +113,7 @@ fn test() {
|
||||||
|
|
||||||
match target {
|
match target {
|
||||||
Response::Redirect(ref this) => match this {
|
Response::Redirect(ref this) => match this {
|
||||||
Redirect::Permanent(this) => assert_eq!(this.target, "target".to_string()),
|
Redirect::Permanent(this) => assert_eq!(this.target, "target"),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
},
|
},
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ impl<'a> Success<'a> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
let request = format!("20 text/gemini\r\ndata");
|
let request = format!("20 text/gemini\r\nDATA");
|
||||||
let source = Success::from_bytes(request.as_bytes()).unwrap();
|
let source = Success::from_bytes(request.as_bytes()).unwrap();
|
||||||
|
|
||||||
match source {
|
match source {
|
||||||
Success::Default(ref this) => {
|
Success::Default(ref this) => {
|
||||||
assert_eq!(this.mime, "text/gemini".to_string());
|
assert_eq!(this.header.mime, "text/gemini");
|
||||||
assert_eq!(this.data, "data".as_bytes());
|
assert_eq!(this.data, "DATA".as_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert_eq!(source.into_bytes(), request.as_bytes());
|
assert_eq!(source.into_bytes(), request.as_bytes());
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,27 @@
|
||||||
use anyhow::{bail, Result};
|
pub mod header;
|
||||||
|
pub use header::Header;
|
||||||
pub const CODE: &[u8] = b"20";
|
|
||||||
|
|
||||||
/// [Success](https://geminiprotocol.net/docs/protocol-specification.gmi#success)
|
/// [Success](https://geminiprotocol.net/docs/protocol-specification.gmi#success)
|
||||||
pub struct Default<'a> {
|
pub struct Default<'a> {
|
||||||
pub mime: String,
|
|
||||||
pub data: &'a [u8],
|
pub data: &'a [u8],
|
||||||
|
pub header: Header,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Default<'a> {
|
impl<'a> Default<'a> {
|
||||||
/// Build `Self` from UTF-8 header bytes
|
/// Build `Self` from UTF-8 header bytes
|
||||||
/// * expected buffer includes leading status code, message, CRLF
|
/// * expected buffer includes leading status code, message, CRLF
|
||||||
pub fn from_bytes(buffer: &'a [u8]) -> Result<Self> {
|
pub fn from_bytes(buffer: &'a [u8]) -> Result<Self> {
|
||||||
use crate::Header;
|
let header = Header::from_bytes(buffer)?;
|
||||||
use regex::Regex;
|
|
||||||
use std::str::from_utf8;
|
|
||||||
let h = buffer.header_bytes()?;
|
|
||||||
if h.get(..2)
|
|
||||||
.is_none_or(|c| c[0] != CODE[0] || c[1] != CODE[1])
|
|
||||||
{
|
|
||||||
bail!("Invalid status code")
|
|
||||||
}
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
mime: match Regex::new(r"^([^\/]+\/[^\s;]+)")?.captures(from_utf8(&h[3..])?) {
|
data: buffer.get(header.to_bytes().len()..).unwrap_or(&[]),
|
||||||
Some(c) => match c.get(1) {
|
header,
|
||||||
Some(m) => m.as_str().to_string(),
|
|
||||||
None => bail!("Content type required"),
|
|
||||||
},
|
|
||||||
None => bail!("Could not parse content type"),
|
|
||||||
},
|
|
||||||
data: &buffer[h.len() + 2..],
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert `Self` into UTF-8 bytes presentation
|
/// Convert `Self` into UTF-8 bytes presentation
|
||||||
pub fn into_bytes(self) -> Vec<u8> {
|
pub fn into_bytes(self) -> Vec<u8> {
|
||||||
let mut bytes = Vec::with_capacity(3 + self.mime.len() + 2 + self.data.len());
|
let mut bytes = Vec::new();
|
||||||
bytes.extend(CODE);
|
bytes.extend(self.header.into_bytes());
|
||||||
bytes.push(b' ');
|
|
||||||
bytes.extend(self.mime.into_bytes());
|
|
||||||
bytes.extend([b'\r', b'\n']);
|
|
||||||
bytes.extend(self.data);
|
bytes.extend(self.data);
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
@ -47,10 +29,11 @@ impl<'a> Default<'a> {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test() {
|
fn test() {
|
||||||
let source = format!("20 text/gemini\r\ndata");
|
const BYTES: &[u8] = "20 text/gemini\r\nDATA".as_bytes();
|
||||||
let target = Default::from_bytes(source.as_bytes()).unwrap();
|
let default = Default::from_bytes(BYTES).unwrap();
|
||||||
|
|
||||||
assert_eq!(target.mime, "text/gemini".to_string());
|
assert_eq!(default.header.mime, "text/gemini".to_string());
|
||||||
assert_eq!(target.data, "data".as_bytes());
|
assert_eq!(default.into_bytes(), BYTES);
|
||||||
assert_eq!(target.into_bytes(), source.as_bytes());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
|
||||||
55
src/response/success/default/header.rs
Normal file
55
src/response/success/default/header.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
pub const CODE: &[u8] = b"20";
|
||||||
|
|
||||||
|
pub struct Header {
|
||||||
|
pub mime: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
/// Build `Self` from UTF-8 header bytes
|
||||||
|
/// * expected buffer includes leading status code, message, CRLF
|
||||||
|
pub fn from_bytes(buffer: &[u8]) -> Result<Self> {
|
||||||
|
use crate::Header;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::str::from_utf8;
|
||||||
|
let h = buffer.header_bytes()?;
|
||||||
|
if h.get(..2)
|
||||||
|
.is_none_or(|c| c[0] != CODE[0] || c[1] != CODE[1])
|
||||||
|
{
|
||||||
|
bail!("Invalid status code")
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
mime: match Regex::new(r"^([^\/]+\/[^\s;]+)")?.captures(from_utf8(&h[3..])?) {
|
||||||
|
Some(c) => match c.get(1) {
|
||||||
|
Some(m) => m.as_str().to_string(),
|
||||||
|
None => bail!("Content type required"),
|
||||||
|
},
|
||||||
|
None => bail!("Could not parse content type"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert `Self` into UTF-8 bytes presentation
|
||||||
|
pub fn into_bytes(self) -> Vec<u8> {
|
||||||
|
self.to_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut bytes = Vec::with_capacity(3 + self.mime.len() + 2);
|
||||||
|
bytes.extend(CODE);
|
||||||
|
bytes.push(b' ');
|
||||||
|
bytes.extend(self.mime.as_bytes());
|
||||||
|
bytes.extend([b'\r', b'\n']);
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
const BYTES: &[u8] = "20 text/gemini\r\nDATA".as_bytes();
|
||||||
|
let header = Header::from_bytes(BYTES).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(header.mime, "text/gemini".to_string());
|
||||||
|
assert_eq!(header.into_bytes(), BYTES[..BYTES.len() - 4]); // skip DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue