First response
This commit is contained in:
parent
9a2a500eb0
commit
80fabcde47
4 changed files with 382 additions and 58 deletions
13
src/main.rs
13
src/main.rs
|
|
@ -7,7 +7,10 @@ use std::{
|
||||||
net::TcpListener,
|
net::TcpListener,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::request::RequestHeader;
|
use crate::{
|
||||||
|
response::{Response, ResponseCode, ResponseHeader},
|
||||||
|
shared_enums::Content,
|
||||||
|
};
|
||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
let listener = TcpListener::bind("127.0.0.1:8080")?;
|
let listener = TcpListener::bind("127.0.0.1:8080")?;
|
||||||
|
|
@ -22,7 +25,13 @@ fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
let mut writer = stream;
|
let mut writer = stream;
|
||||||
|
|
||||||
writer.write_all("Ok".as_bytes())?;
|
let response = Response::new()
|
||||||
|
.with_code(ResponseCode::Ok)
|
||||||
|
.with_data(b"<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"/><title>Hello World!</title></head><body><h1>Ahojky</h1><p>Jou jou jou</p></body></html>".to_vec())
|
||||||
|
.with_header(ResponseHeader::ContentType(Content::html_utf8()));
|
||||||
|
|
||||||
|
response.respond(&mut writer)?;
|
||||||
|
|
||||||
writer.flush()?;
|
writer.flush()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::shared_enums::ContentType;
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{self, BufRead, BufReader, Read, Take},
|
io::{self, BufRead, BufReader, Read, Take},
|
||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::shared_enums::Content;
|
||||||
|
|
||||||
const MAX_LINE_WIDTH: u64 = 4096; // 4 KiB
|
const MAX_LINE_WIDTH: u64 = 4096; // 4 KiB
|
||||||
const MAX_BODY_LENGTH: u64 = 8388608; // 5 MiB
|
const MAX_BODY_LENGTH: u64 = 8388608; // 5 MiB
|
||||||
const MAX_HEADER_COUNT: u64 = 512;
|
const MAX_HEADER_COUNT: u64 = 512;
|
||||||
|
|
@ -22,8 +23,8 @@ pub struct Request {
|
||||||
pub enum RequestHeader {
|
pub enum RequestHeader {
|
||||||
Host(String),
|
Host(String),
|
||||||
UserAgent(String),
|
UserAgent(String),
|
||||||
ContentType(ContentType),
|
ContentType(Content),
|
||||||
Accept(Vec<ContentType>),
|
Accept(Vec<Content>),
|
||||||
Other(Box<str>, Box<str>),
|
Other(Box<str>, Box<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,12 +38,12 @@ impl FromStr for RequestHeader {
|
||||||
[header_type, value] => match *header_type {
|
[header_type, value] => match *header_type {
|
||||||
"Host" => Ok(RequestHeader::Host(value.to_string())),
|
"Host" => Ok(RequestHeader::Host(value.to_string())),
|
||||||
"UserAgent" => Ok(RequestHeader::UserAgent(value.to_string())),
|
"UserAgent" => Ok(RequestHeader::UserAgent(value.to_string())),
|
||||||
"ContentType" => Ok(RequestHeader::ContentType(ContentType::from_str(value)?)),
|
"ContentType" => Ok(RequestHeader::ContentType(Content::from_str(value)?)),
|
||||||
"Accept" => Ok(RequestHeader::Accept(
|
"Accept" => Ok(RequestHeader::Accept(
|
||||||
value
|
value
|
||||||
.split(',')
|
.split(',')
|
||||||
.map(ContentType::from_str)
|
.map(Content::from_str)
|
||||||
.collect::<Result<Vec<ContentType>, io::Error>>()?,
|
.collect::<Result<Vec<Content>, io::Error>>()?,
|
||||||
)),
|
)),
|
||||||
_ => Ok(RequestHeader::Other(
|
_ => Ok(RequestHeader::Other(
|
||||||
header_type.to_string().into_boxed_str(),
|
header_type.to_string().into_boxed_str(),
|
||||||
|
|
|
||||||
173
src/response.rs
173
src/response.rs
|
|
@ -1,18 +1,92 @@
|
||||||
use crate::shared_enums::ContentType;
|
use std::{
|
||||||
|
io::{self, Write},
|
||||||
|
net::TcpStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::shared_enums::Content;
|
||||||
|
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
http_version: String,
|
http_version: Box<str>,
|
||||||
code: ResponseCode,
|
code: ResponseCode,
|
||||||
headers: Vec<ResponseHeader>,
|
headers: Vec<ResponseHeader>,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
pub fn respond(self, stream: &mut TcpStream) -> Result<(), io::Error> {
|
||||||
|
let binding = self.to_str();
|
||||||
|
let mut output = binding.as_bytes().to_vec();
|
||||||
|
output.extend_from_slice(b"\r\n");
|
||||||
|
|
||||||
|
if !self.data.is_empty() {
|
||||||
|
output.extend_from_slice(b"\r\n");
|
||||||
|
output.extend_from_slice(&self.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.write_all(output.as_slice())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_str(&self) -> Box<str> {
|
||||||
|
format!(
|
||||||
|
"{} {}\r\n{}",
|
||||||
|
self.http_version,
|
||||||
|
self.code.to_code(),
|
||||||
|
self.headers
|
||||||
|
.iter()
|
||||||
|
.map(|header| header.to_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\r\n")
|
||||||
|
)
|
||||||
|
.into_boxed_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
http_version: "HTTP/1.1".to_owned().into_boxed_str(),
|
||||||
|
code: ResponseCode::Ok,
|
||||||
|
headers: vec![],
|
||||||
|
data: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_data(self, data: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
http_version: self.http_version,
|
||||||
|
code: self.code,
|
||||||
|
headers: self.headers,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_code(self, code: ResponseCode) -> Self {
|
||||||
|
Self {
|
||||||
|
http_version: self.http_version,
|
||||||
|
code,
|
||||||
|
headers: self.headers,
|
||||||
|
data: self.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_header(mut self, header: ResponseHeader) -> Self {
|
||||||
|
self.headers.push(header);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
http_version: self.http_version,
|
||||||
|
code: self.code,
|
||||||
|
headers: self.headers,
|
||||||
|
data: self.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum ResponseCode {
|
pub enum ResponseCode {
|
||||||
Continue,
|
Continue,
|
||||||
SwitchingProtocols,
|
SwitchingProtocols,
|
||||||
Processing,
|
Processing,
|
||||||
EarlyHints,
|
EarlyHints,
|
||||||
OK,
|
Ok,
|
||||||
Created,
|
Created,
|
||||||
Accepted,
|
Accepted,
|
||||||
NonAuthoritativeInformation,
|
NonAuthoritativeInformation,
|
||||||
|
|
@ -72,23 +146,102 @@ pub enum ResponseCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseCode {
|
impl ResponseCode {
|
||||||
fn to_code(&self) -> u32 {
|
fn to_code(&self) -> &'static str {
|
||||||
|
type R = ResponseCode;
|
||||||
match self {
|
match self {
|
||||||
ResponseCode::Continue => 100,
|
R::Continue => "100 Continue",
|
||||||
ResponseCode::SwitchingProtocols => 101,
|
R::SwitchingProtocols => "101 Switching Protocols",
|
||||||
ResponseCode::Processing => 102,
|
R::Processing => "102 Processing",
|
||||||
ResponseCode::EarlyHints => 103,
|
R::EarlyHints => "103 Early Hints",
|
||||||
ResponseCode::OK => 200,
|
R::Ok => "200 OK",
|
||||||
|
R::Created => "201 Created",
|
||||||
|
R::Accepted => "202 Accepted",
|
||||||
|
R::NonAuthoritativeInformation => "203 Non-Authoritative Information",
|
||||||
|
R::NoContent => "204 No Content",
|
||||||
|
R::ResetContent => "205 Reset Content",
|
||||||
|
R::PartialContent => "206 Partial Content",
|
||||||
|
R::MultiStatus => "207 Multi-Status",
|
||||||
|
R::AlreadyReported => "208 Already Reported",
|
||||||
|
R::IMUsed => "226 IM Used",
|
||||||
|
R::MultipleChoices => "300 Multiple Choices",
|
||||||
|
R::MovedPermanently => "301 Moved Permanently",
|
||||||
|
R::Found => "302 Found",
|
||||||
|
R::SeeOther => "303 See Other",
|
||||||
|
R::NotModified => "304 Not Modified",
|
||||||
|
R::TemporaryRedirect => "307 Temporary Redirect",
|
||||||
|
R::PermanentRedirect => "308 Permanent Redirect",
|
||||||
|
R::BadRequest => "400 Bad Request",
|
||||||
|
R::Unauthorized => "401 Unauthorized",
|
||||||
|
R::PaymentRequired => "402 Payment Required",
|
||||||
|
R::Forbidden => "403 Forbidden",
|
||||||
|
R::NotFound => "404 Not Found",
|
||||||
|
R::MethodNotAllowed => "405 Method Not Allowed",
|
||||||
|
R::NotAcceptable => "406 Not Acceptable",
|
||||||
|
R::ProxyAuthenticationRequired => "407 Proxy Authentication Required",
|
||||||
|
R::RequestTimeout => "408 Request Timeout",
|
||||||
|
R::Conflict => "409 Conflict",
|
||||||
|
R::Gone => "410 Gone",
|
||||||
|
R::LengthRequired => "411 Length Required",
|
||||||
|
R::PreconditionFailed => "412 Precondition Failed",
|
||||||
|
R::ContentTooLarge => "413 Content Too Large",
|
||||||
|
R::URITooLong => "414 URI Too Long",
|
||||||
|
R::UnsupportedMediaType => "415 Unsupported Media Type",
|
||||||
|
R::RangeNotSatisfiable => "416 Range Not Satisfiable",
|
||||||
|
R::ExpectationFailed => "417 Expectation Failed",
|
||||||
|
R::IAmTeapot => "418 I'm a teapot",
|
||||||
|
R::MisdirectedRequest => "421 Misdirected Request",
|
||||||
|
R::UnprocessableContent => "422 Unprocessable Content",
|
||||||
|
R::Locked => "423 Locked",
|
||||||
|
R::FailedDependency => "424 Failed Dependency",
|
||||||
|
R::TooEarly => "425 Too Early",
|
||||||
|
R::UpgradeRequired => "426 Upgrade Required",
|
||||||
|
R::PreconditionRequired => "428 Precondition Required",
|
||||||
|
R::TooManyRequests => "429 Too Many Requests",
|
||||||
|
R::RequestHeaderFieldsTooLarge => "431 Request Header Fields Too Large",
|
||||||
|
R::UnavailableForLegalReasons => "451 Unavailable For Legal Reasons",
|
||||||
|
R::InternalServerError => "500 Internal Server Error",
|
||||||
|
R::NotImplemented => "501 Not Implemented",
|
||||||
|
R::BadGateway => "502 Bad Gateway",
|
||||||
|
R::ServiceUnavailable => "503 Service Unavailable",
|
||||||
|
R::GatewayTimeout => "504 Gateway Timeout",
|
||||||
|
R::HTTPVersionNotSupported => "505 HTTP Version Not Supported",
|
||||||
|
R::VariantAlsoNegotiates => "506 Variant Also Negotiates",
|
||||||
|
R::InsufficientStorage => "507 Insufficient Storage",
|
||||||
|
R::LoopDetected => "508 Loop Detected",
|
||||||
|
R::NotExtended => "510 Not Extended",
|
||||||
|
R::NetworkAuthenticationRequired => "511 Network Authentication Required",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ResponseHeader {
|
pub enum ResponseHeader {
|
||||||
ContentLength(u32),
|
ContentLength(u32),
|
||||||
ContentType(ContentType),
|
ContentType(Content),
|
||||||
CacheControl(CacheControl),
|
CacheControl(CacheControl),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ResponseHeader {
|
||||||
|
fn to_str(&self) -> Box<str> {
|
||||||
|
type R = ResponseHeader;
|
||||||
|
match self {
|
||||||
|
R::ContentLength(length) => format!("Content-Length: {length}").into_boxed_str(),
|
||||||
|
R::ContentType(content) => {
|
||||||
|
format!("Content-Type: {}", content.to_str()).into_boxed_str()
|
||||||
|
}
|
||||||
|
R::CacheControl(c) => format!("Cache-Control: {}", c.to_str()).into_boxed_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum CacheControl {
|
pub enum CacheControl {
|
||||||
NoStore,
|
NoStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CacheControl {
|
||||||
|
fn to_str(&self) -> &'static str {
|
||||||
|
type C = CacheControl;
|
||||||
|
match self {
|
||||||
|
C::NoStore => "no-store",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,26 @@
|
||||||
use std::{io, str::FromStr};
|
use std::{io, str::FromStr};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ContentTypeType {
|
pub enum ContentType {
|
||||||
Text(TextType),
|
Text(TextType),
|
||||||
Aplication(ApplicationType),
|
Aplication(ApplicationType),
|
||||||
Image(Image),
|
Image(Image),
|
||||||
Any,
|
Any,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ContentType {
|
||||||
|
fn to_str(&self) -> Box<str> {
|
||||||
|
match self {
|
||||||
|
ContentType::Text(text) => format!("text/{}", text.to_str()).into_boxed_str(),
|
||||||
|
ContentType::Aplication(app) => {
|
||||||
|
format!("application/{}", app.to_str()).into_boxed_str()
|
||||||
|
}
|
||||||
|
ContentType::Image(img) => format!("image/{}", img.to_str()).into_boxed_str(),
|
||||||
|
ContentType::Any => "*/*".to_string().into_boxed_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Parameter {
|
pub enum Parameter {
|
||||||
Preference(f32),
|
Preference(f32),
|
||||||
|
|
@ -15,40 +28,158 @@ pub enum Parameter {
|
||||||
Other(Box<str>, Box<str>),
|
Other(Box<str>, Box<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Parameter {
|
||||||
|
fn to_str(&self) -> Box<str> {
|
||||||
|
match &self {
|
||||||
|
Parameter::Preference(val) => format!("q={val}").into_boxed_str(),
|
||||||
|
Parameter::Charset(ch) => format!("charset={}", ch.to_str()).into_boxed_str(),
|
||||||
|
Parameter::Other(p, v) => format!("{p}={v}").into_boxed_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Charset {
|
pub enum Charset {
|
||||||
UTF8,
|
UTF8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl Charset {
|
||||||
pub struct ContentType {
|
fn to_str(&self) -> &'static str {
|
||||||
pub content_type: ContentTypeType,
|
match self {
|
||||||
pub parameter: Option<Parameter>,
|
Charset::UTF8 => "utf-8",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn InvalidDataError(error: &str) -> io::Error {
|
#[derive(Debug)]
|
||||||
|
pub struct Content {
|
||||||
|
pub content_type: ContentType,
|
||||||
|
pub parameter: Option<Vec<Parameter>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content {
|
||||||
|
pub fn to_str(&self) -> Box<str> {
|
||||||
|
match &self.parameter {
|
||||||
|
Some(p) => format!(
|
||||||
|
"{}; {}",
|
||||||
|
self.content_type.to_str(),
|
||||||
|
p.iter()
|
||||||
|
.map(|par| par.to_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("; ")
|
||||||
|
)
|
||||||
|
.into_boxed_str(),
|
||||||
|
None => self.content_type.to_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Content {
|
||||||
|
pub fn new(content_type: ContentType) -> Self {
|
||||||
|
Self {
|
||||||
|
content_type,
|
||||||
|
parameter: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_params(content_type: ContentType, params: Vec<Parameter>) -> Self {
|
||||||
|
Self {
|
||||||
|
content_type,
|
||||||
|
parameter: Some(params),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_parameter(&mut self, param: Parameter) {
|
||||||
|
match &mut self.parameter {
|
||||||
|
Some(params) => params.push(param),
|
||||||
|
None => self.parameter = Some(vec![param]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quality(&self) -> Option<f32> {
|
||||||
|
self.parameter.as_ref()?.iter().find_map(|p| {
|
||||||
|
if let Parameter::Preference(q) = p {
|
||||||
|
Some(*q)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn charset(&self) -> Option<&Charset> {
|
||||||
|
self.parameter.as_ref()?.iter().find_map(|p| {
|
||||||
|
if let Parameter::Charset(cs) = p {
|
||||||
|
Some(cs)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matches(&self, other: &ContentType) -> bool {
|
||||||
|
type C = ContentType;
|
||||||
|
match (&self.content_type, other) {
|
||||||
|
(C::Any, _) | (_, C::Any) => true,
|
||||||
|
(C::Text(TextType::Any), C::Text(_)) => true,
|
||||||
|
(C::Text(_), C::Text(TextType::Any)) => true,
|
||||||
|
(C::Aplication(ApplicationType::Any), C::Aplication(_)) => true,
|
||||||
|
(C::Aplication(_), C::Aplication(ApplicationType::Any)) => true,
|
||||||
|
(C::Image(Image::Any), C::Image(_)) => true,
|
||||||
|
(C::Image(_), C::Image(Image::Any)) => true,
|
||||||
|
(a, b) => std::mem::discriminant(a) == std::mem::discriminant(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_text(&self) -> bool {
|
||||||
|
matches!(self.content_type, ContentType::Text(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_application(&self) -> bool {
|
||||||
|
matches!(self.content_type, ContentType::Aplication(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_image(&self) -> bool {
|
||||||
|
matches!(self.content_type, ContentType::Image(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn html_utf8() -> Self {
|
||||||
|
Self::with_params(
|
||||||
|
ContentType::Text(TextType::Html),
|
||||||
|
vec![Parameter::Charset(Charset::UTF8)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn json_utf8() -> Self {
|
||||||
|
Self::with_params(
|
||||||
|
ContentType::Aplication(ApplicationType::Json),
|
||||||
|
vec![Parameter::Charset(Charset::UTF8)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invalid_data_error(error: &str) -> io::Error {
|
||||||
io::Error::new(io::ErrorKind::InvalidData, error)
|
io::Error::new(io::ErrorKind::InvalidData, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ContentType {
|
impl FromStr for Content {
|
||||||
type Err = io::Error;
|
type Err = io::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s.split(';').collect::<Vec<&str>>().as_slice() {
|
match s.split(';').collect::<Vec<&str>>().as_slice() {
|
||||||
[val, par] => Ok(Self {
|
|
||||||
content_type: ContentTypeType::from_str(val)?,
|
|
||||||
parameter: Some(Parameter::from_str(par)?),
|
|
||||||
}),
|
|
||||||
|
|
||||||
[val] => Ok(Self {
|
[val] => Ok(Self {
|
||||||
content_type: ContentTypeType::from_str(val)?,
|
content_type: ContentType::from_str(val)?,
|
||||||
parameter: None,
|
parameter: None,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
_ => Err(io::Error::new(
|
[val, par @ ..] => Ok(Self {
|
||||||
io::ErrorKind::InvalidData,
|
content_type: ContentType::from_str(val)?,
|
||||||
"Invalid content-type",
|
parameter: Some(
|
||||||
)),
|
par.iter()
|
||||||
|
.map(|p| Parameter::from_str(p))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
_ => Err(invalid_data_error("Invalid content-type")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +192,7 @@ impl FromStr for Parameter {
|
||||||
["q", value] => {
|
["q", value] => {
|
||||||
let pref_val = match value.parse::<f32>() {
|
let pref_val = match value.parse::<f32>() {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
Err(_) => Err(InvalidDataError("Invalid preference")),
|
Err(_) => Err(invalid_data_error("Invalid preference")),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
Ok(Parameter::Preference(pref_val))
|
Ok(Parameter::Preference(pref_val))
|
||||||
|
|
@ -82,39 +213,34 @@ impl FromStr for Parameter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for ContentTypeType {
|
impl FromStr for ContentType {
|
||||||
type Err = io::Error;
|
type Err = io::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let parts: Vec<&str> = s.split("/").collect();
|
let parts: Vec<&str> = s.split("/").collect();
|
||||||
|
|
||||||
match parts.as_slice() {
|
match parts.as_slice() {
|
||||||
["*", "*"] => Ok(ContentTypeType::Any),
|
["*", "*"] => Ok(ContentType::Any),
|
||||||
["text", "*"] => Ok(ContentTypeType::Text(TextType::Any)),
|
["text", "*"] => Ok(ContentType::Text(TextType::Any)),
|
||||||
["text", "html"] => Ok(ContentTypeType::Text(TextType::Html)),
|
["text", "html"] => Ok(ContentType::Text(TextType::Html)),
|
||||||
["text", "css"] => Ok(ContentTypeType::Text(TextType::Css)),
|
["text", "css"] => Ok(ContentType::Text(TextType::Css)),
|
||||||
["text", "javascript"] => Ok(ContentTypeType::Text(TextType::Javascript)),
|
["text", "javascript"] => Ok(ContentType::Text(TextType::Javascript)),
|
||||||
|
|
||||||
["application", "json"] => Ok(ContentTypeType::Aplication(ApplicationType::Json)),
|
["application", "json"] => Ok(ContentType::Aplication(ApplicationType::Json)),
|
||||||
["application", "xhtml+xml"] => {
|
["application", "xhtml+xml"] => Ok(ContentType::Aplication(ApplicationType::XhtmlXml)),
|
||||||
Ok(ContentTypeType::Aplication(ApplicationType::XhtmlXml))
|
["application", "xml"] => Ok(ContentType::Aplication(ApplicationType::Xml)),
|
||||||
}
|
["application", "*"] => Ok(ContentType::Aplication(ApplicationType::Any)),
|
||||||
["application", "xml"] => Ok(ContentTypeType::Aplication(ApplicationType::Xml)),
|
|
||||||
["application", "*"] => Ok(ContentTypeType::Aplication(ApplicationType::Any)),
|
|
||||||
|
|
||||||
["image", "png"] => Ok(ContentTypeType::Image(Image::Png)),
|
["image", "png"] => Ok(ContentType::Image(Image::Png)),
|
||||||
["image", "jpeg"] | ["image", "jpg"] => Ok(ContentTypeType::Image(Image::Jpeg)),
|
["image", "jpeg"] | ["image", "jpg"] => Ok(ContentType::Image(Image::Jpeg)),
|
||||||
["image", "avif"] => Ok(ContentTypeType::Image(Image::Avif)),
|
["image", "avif"] => Ok(ContentType::Image(Image::Avif)),
|
||||||
["image", "webp"] => Ok(ContentTypeType::Image(Image::Webp)),
|
["image", "webp"] => Ok(ContentType::Image(Image::Webp)),
|
||||||
["image", "svg"] | ["image", "svg+xml"] => Ok(ContentTypeType::Image(Image::Svg)),
|
["image", "svg"] | ["image", "svg+xml"] => Ok(ContentType::Image(Image::Svg)),
|
||||||
["image", "*"] => Ok(ContentTypeType::Image(Image::Any)),
|
["image", "*"] => Ok(ContentType::Image(Image::Any)),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
println!("{parts:?}");
|
println!("{parts:?}");
|
||||||
Err(io::Error::new(
|
Err(invalid_data_error("Invalid content-type-type"))
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
"Invalid content-type-type",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +256,19 @@ pub enum Image {
|
||||||
Any,
|
Any,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
fn to_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Image::Png => "png",
|
||||||
|
Image::Avif => "avif",
|
||||||
|
Image::Jpeg => "jpeg",
|
||||||
|
Image::Webp => "webp",
|
||||||
|
Image::Svg => "svg",
|
||||||
|
Image::Any => "*",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TextType {
|
pub enum TextType {
|
||||||
Html,
|
Html,
|
||||||
|
|
@ -138,6 +277,17 @@ pub enum TextType {
|
||||||
Any,
|
Any,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextType {
|
||||||
|
fn to_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
TextType::Html => "html",
|
||||||
|
TextType::Css => "css",
|
||||||
|
TextType::Javascript => "javascript",
|
||||||
|
TextType::Any => "*",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ApplicationType {
|
pub enum ApplicationType {
|
||||||
Json,
|
Json,
|
||||||
|
|
@ -145,3 +295,14 @@ pub enum ApplicationType {
|
||||||
XhtmlXml,
|
XhtmlXml,
|
||||||
Xml,
|
Xml,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ApplicationType {
|
||||||
|
fn to_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ApplicationType::Json => "json",
|
||||||
|
ApplicationType::Any => "*",
|
||||||
|
ApplicationType::XhtmlXml => "xhtml+xml",
|
||||||
|
ApplicationType::Xml => "xml",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue