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,
|
||||
};
|
||||
|
||||
use crate::request::RequestHeader;
|
||||
use crate::{
|
||||
response::{Response, ResponseCode, ResponseHeader},
|
||||
shared_enums::Content,
|
||||
};
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let listener = TcpListener::bind("127.0.0.1:8080")?;
|
||||
|
|
@ -22,7 +25,13 @@ fn main() -> std::io::Result<()> {
|
|||
|
||||
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()?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use crate::shared_enums::ContentType;
|
||||
use std::{
|
||||
io::{self, BufRead, BufReader, Read, Take},
|
||||
net::TcpStream,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::shared_enums::Content;
|
||||
|
||||
const MAX_LINE_WIDTH: u64 = 4096; // 4 KiB
|
||||
const MAX_BODY_LENGTH: u64 = 8388608; // 5 MiB
|
||||
const MAX_HEADER_COUNT: u64 = 512;
|
||||
|
|
@ -22,8 +23,8 @@ pub struct Request {
|
|||
pub enum RequestHeader {
|
||||
Host(String),
|
||||
UserAgent(String),
|
||||
ContentType(ContentType),
|
||||
Accept(Vec<ContentType>),
|
||||
ContentType(Content),
|
||||
Accept(Vec<Content>),
|
||||
Other(Box<str>, Box<str>),
|
||||
}
|
||||
|
||||
|
|
@ -37,12 +38,12 @@ impl FromStr for RequestHeader {
|
|||
[header_type, value] => match *header_type {
|
||||
"Host" => Ok(RequestHeader::Host(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(
|
||||
value
|
||||
.split(',')
|
||||
.map(ContentType::from_str)
|
||||
.collect::<Result<Vec<ContentType>, io::Error>>()?,
|
||||
.map(Content::from_str)
|
||||
.collect::<Result<Vec<Content>, io::Error>>()?,
|
||||
)),
|
||||
_ => Ok(RequestHeader::Other(
|
||||
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 {
|
||||
http_version: String,
|
||||
http_version: Box<str>,
|
||||
code: ResponseCode,
|
||||
headers: Vec<ResponseHeader>,
|
||||
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 {
|
||||
Continue,
|
||||
SwitchingProtocols,
|
||||
Processing,
|
||||
EarlyHints,
|
||||
OK,
|
||||
Ok,
|
||||
Created,
|
||||
Accepted,
|
||||
NonAuthoritativeInformation,
|
||||
|
|
@ -72,23 +146,102 @@ pub enum ResponseCode {
|
|||
}
|
||||
|
||||
impl ResponseCode {
|
||||
fn to_code(&self) -> u32 {
|
||||
fn to_code(&self) -> &'static str {
|
||||
type R = ResponseCode;
|
||||
match self {
|
||||
ResponseCode::Continue => 100,
|
||||
ResponseCode::SwitchingProtocols => 101,
|
||||
ResponseCode::Processing => 102,
|
||||
ResponseCode::EarlyHints => 103,
|
||||
ResponseCode::OK => 200,
|
||||
R::Continue => "100 Continue",
|
||||
R::SwitchingProtocols => "101 Switching Protocols",
|
||||
R::Processing => "102 Processing",
|
||||
R::EarlyHints => "103 Early Hints",
|
||||
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 {
|
||||
ContentLength(u32),
|
||||
ContentType(ContentType),
|
||||
ContentType(Content),
|
||||
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 {
|
||||
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};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ContentTypeType {
|
||||
pub enum ContentType {
|
||||
Text(TextType),
|
||||
Aplication(ApplicationType),
|
||||
Image(Image),
|
||||
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)]
|
||||
pub enum Parameter {
|
||||
Preference(f32),
|
||||
|
|
@ -15,40 +28,158 @@ pub enum Parameter {
|
|||
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)]
|
||||
pub enum Charset {
|
||||
UTF8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContentType {
|
||||
pub content_type: ContentTypeType,
|
||||
pub parameter: Option<Parameter>,
|
||||
impl Charset {
|
||||
fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
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)
|
||||
}
|
||||
|
||||
impl FromStr for ContentType {
|
||||
impl FromStr for Content {
|
||||
type Err = io::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
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 {
|
||||
content_type: ContentTypeType::from_str(val)?,
|
||||
content_type: ContentType::from_str(val)?,
|
||||
parameter: None,
|
||||
}),
|
||||
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid content-type",
|
||||
)),
|
||||
[val, par @ ..] => Ok(Self {
|
||||
content_type: ContentType::from_str(val)?,
|
||||
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] => {
|
||||
let pref_val = match value.parse::<f32>() {
|
||||
Ok(v) => Ok(v),
|
||||
Err(_) => Err(InvalidDataError("Invalid preference")),
|
||||
Err(_) => Err(invalid_data_error("Invalid preference")),
|
||||
}?;
|
||||
|
||||
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;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split("/").collect();
|
||||
|
||||
match parts.as_slice() {
|
||||
["*", "*"] => Ok(ContentTypeType::Any),
|
||||
["text", "*"] => Ok(ContentTypeType::Text(TextType::Any)),
|
||||
["text", "html"] => Ok(ContentTypeType::Text(TextType::Html)),
|
||||
["text", "css"] => Ok(ContentTypeType::Text(TextType::Css)),
|
||||
["text", "javascript"] => Ok(ContentTypeType::Text(TextType::Javascript)),
|
||||
["*", "*"] => Ok(ContentType::Any),
|
||||
["text", "*"] => Ok(ContentType::Text(TextType::Any)),
|
||||
["text", "html"] => Ok(ContentType::Text(TextType::Html)),
|
||||
["text", "css"] => Ok(ContentType::Text(TextType::Css)),
|
||||
["text", "javascript"] => Ok(ContentType::Text(TextType::Javascript)),
|
||||
|
||||
["application", "json"] => Ok(ContentTypeType::Aplication(ApplicationType::Json)),
|
||||
["application", "xhtml+xml"] => {
|
||||
Ok(ContentTypeType::Aplication(ApplicationType::XhtmlXml))
|
||||
}
|
||||
["application", "xml"] => Ok(ContentTypeType::Aplication(ApplicationType::Xml)),
|
||||
["application", "*"] => Ok(ContentTypeType::Aplication(ApplicationType::Any)),
|
||||
["application", "json"] => Ok(ContentType::Aplication(ApplicationType::Json)),
|
||||
["application", "xhtml+xml"] => Ok(ContentType::Aplication(ApplicationType::XhtmlXml)),
|
||||
["application", "xml"] => Ok(ContentType::Aplication(ApplicationType::Xml)),
|
||||
["application", "*"] => Ok(ContentType::Aplication(ApplicationType::Any)),
|
||||
|
||||
["image", "png"] => Ok(ContentTypeType::Image(Image::Png)),
|
||||
["image", "jpeg"] | ["image", "jpg"] => Ok(ContentTypeType::Image(Image::Jpeg)),
|
||||
["image", "avif"] => Ok(ContentTypeType::Image(Image::Avif)),
|
||||
["image", "webp"] => Ok(ContentTypeType::Image(Image::Webp)),
|
||||
["image", "svg"] | ["image", "svg+xml"] => Ok(ContentTypeType::Image(Image::Svg)),
|
||||
["image", "*"] => Ok(ContentTypeType::Image(Image::Any)),
|
||||
["image", "png"] => Ok(ContentType::Image(Image::Png)),
|
||||
["image", "jpeg"] | ["image", "jpg"] => Ok(ContentType::Image(Image::Jpeg)),
|
||||
["image", "avif"] => Ok(ContentType::Image(Image::Avif)),
|
||||
["image", "webp"] => Ok(ContentType::Image(Image::Webp)),
|
||||
["image", "svg"] | ["image", "svg+xml"] => Ok(ContentType::Image(Image::Svg)),
|
||||
["image", "*"] => Ok(ContentType::Image(Image::Any)),
|
||||
|
||||
_ => {
|
||||
println!("{parts:?}");
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid content-type-type",
|
||||
))
|
||||
Err(invalid_data_error("Invalid content-type-type"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -130,6 +256,19 @@ pub enum Image {
|
|||
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)]
|
||||
pub enum TextType {
|
||||
Html,
|
||||
|
|
@ -138,6 +277,17 @@ pub enum TextType {
|
|||
Any,
|
||||
}
|
||||
|
||||
impl TextType {
|
||||
fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
TextType::Html => "html",
|
||||
TextType::Css => "css",
|
||||
TextType::Javascript => "javascript",
|
||||
TextType::Any => "*",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ApplicationType {
|
||||
Json,
|
||||
|
|
@ -145,3 +295,14 @@ pub enum ApplicationType {
|
|||
XhtmlXml,
|
||||
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