use std::{error::Error, fmt, str};
use http::header::{HeaderMap, CONTENT_TYPE};
use serde::Deserialize;
use crate::Event;
fn parse_body<'a, T, F>(f: F, body: &'a str) -> Result<Event, ParseError>
where
T: Deserialize<'a>,
F: Fn(T) -> Event,
{
serde_json::from_str(body)
.map(f)
.map_err(|_| ParseError::ParseBodyFailed)
}
#[derive(Debug, Clone)]
pub struct RequestParser {
verification_token: String,
}
impl RequestParser {
pub fn new(verification_token: &str) -> Self {
Self {
verification_token: verification_token.to_string(),
}
}
fn parse_headers(&self, headers: HeaderMap) -> Result<String, ParseError> {
let content_type = headers
.get(CONTENT_TYPE)
.ok_or(ParseError::ContentTypeNotFound)?
.to_str()
.map_err(|_| ParseError::ReadContentTypeFailed)?;
if !content_type.starts_with("application/json") {
return Err(ParseError::ContentTypeMismatch);
}
let token = headers
.get("X-TRAQ-BOT-TOKEN")
.ok_or(ParseError::BotTokenNotFound)?
.to_str()
.map_err(|_| ParseError::ReadBotTokenFailed)?;
if token != self.verification_token {
return Err(ParseError::BotTokenMismatch);
}
let event = headers
.get("X-TRAQ-BOT-EVENT")
.ok_or(ParseError::BotEventNotFound)?
.to_str()
.map_err(|_| ParseError::ReadBotEventFailed)?;
Ok(event.to_string())
}
pub fn parse(&self, headers: HeaderMap, body: &[u8]) -> Result<Event, ParseError> {
let event = self.parse_headers(headers)?;
let body = str::from_utf8(body).map_err(|_| ParseError::ReadBodyFailed)?;
match event.as_str() {
"PING" => parse_body(Event::Ping, body),
"JOINED" => parse_body(Event::Joined, body),
"LEFT" => parse_body(Event::Left, body),
"MESSAGE_CREATED" => parse_body(Event::MessageCreated, body),
"MESSAGE_DELETED" => parse_body(Event::MessageDeleted, body),
"MESSAGE_UPDATED" => parse_body(Event::MessageUpdated, body),
"DIRECT_MESSAGE_CREATED" => parse_body(Event::DirectMessageCreated, body),
"DIRECT_MESSAGE_DELETED" => parse_body(Event::DirectMessageDeleted, body),
"DIRECT_MESSAGE_UPDATED" => parse_body(Event::DirectMessageUpdated, body),
"BOT_MESSAGE_STAMPS_UPDATED" => parse_body(Event::BotMessageStampsUpdated, body),
"CHANNEL_CREATED" => parse_body(Event::ChannelCreated, body),
"CHANNEL_TOPIC_CHANGED" => parse_body(Event::ChannelTopicChanged, body),
"USER_CREATED" => parse_body(Event::UserCreated, body),
"STAMP_CREATED" => parse_body(Event::StampCreated, body),
"TAG_ADDED" => parse_body(Event::TagAdded, body),
"TAG_REMOVED" => parse_body(Event::TagRemoved, body),
_ => Err(ParseError::BotEventMismatch),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
ContentTypeNotFound,
ReadContentTypeFailed,
ContentTypeMismatch,
BotTokenNotFound,
ReadBotTokenFailed,
BotTokenMismatch,
BotEventNotFound,
ReadBotEventFailed,
BotEventMismatch,
ReadBodyFailed,
ParseBodyFailed,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use ParseError::*;
let message = match self {
ContentTypeNotFound => "Content-Type is not set",
ReadContentTypeFailed => "Failed to read Content-Type value",
ContentTypeMismatch => "Content-Type value is wrong; it must be application/json",
BotTokenNotFound => "X-TRAQ-BOT-TOKEN is not set",
ReadBotTokenFailed => "Failed to read X-TRAQ-BOT-TOKEN value",
BotTokenMismatch => "X-TRAQ-BOT-TOKEN value is wrong",
BotEventNotFound => "X-TRAQ-BOT-EVENT is not set",
ReadBotEventFailed => "Failed to read X-TRAQ-BOT-EVENT value",
BotEventMismatch => "X-TRAQ-BOT-EVENT value is wrong",
ReadBodyFailed => "Failed to read request body",
ParseBodyFailed => "Failed to parse request body",
};
write!(f, "{message}")
}
}
impl Error for ParseError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::payloads::*;
use crate::test_utils::*;
use std::fs::read_to_string;
#[test]
fn request_parser_new() {
let verification_token = "verification_token";
let parser = RequestParser::new(verification_token);
println!("{:?}", parser);
}
#[test]
fn parse_error_derives() {
for variant in PARSE_ERROR_VARIANTS.iter() {
let error = variant.clone();
assert_eq!(variant, &error);
assert_eq!(format!("{:?}", variant), format!("{:?}", error));
}
}
#[test]
fn parse_failure() {
let parser = make_parser();
let mut headers = HeaderMap::new();
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::ContentTypeNotFound)
);
headers.insert(CONTENT_TYPE, "text/plain".parse().unwrap());
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::ContentTypeMismatch)
);
headers.insert(CONTENT_TYPE, "application/json".parse().unwrap());
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::BotTokenNotFound)
);
headers.insert("X-TRAQ-BOT-TOKEN", "invalid token".parse().unwrap());
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::ReadBotTokenFailed)
);
headers.insert("X-TRAQ-BOT-TOKEN", "invalid_token".parse().unwrap());
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::BotTokenMismatch)
);
headers.insert(
"X-TRAQ-BOT-TOKEN",
"traqbotverificationtoken".parse().unwrap(),
);
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::BotEventNotFound)
);
headers.insert("X-TRAQ-BOT-EVENT", "invalid event".parse().unwrap());
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::ReadBotEventFailed)
);
headers.insert("X-TRAQ-BOT-EVENT", "invalid_event".parse().unwrap());
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::BotEventMismatch)
);
headers.insert("X-TRAQ-BOT-EVENT", "PING".parse().unwrap());
assert_eq!(
parser.parse(headers.clone(), &[0, 159, 146, 150]),
Err(ParseError::ReadBodyFailed)
);
assert_eq!(
parser.parse(headers.clone(), b""),
Err(ParseError::ParseBodyFailed)
);
}
#[test]
fn err_display() {
use ParseError::*;
let pairs = [
(ContentTypeNotFound, "Content-Type is not set"),
(ReadContentTypeFailed, "Failed to read Content-Type value"),
(
ContentTypeMismatch,
"Content-Type value is wrong; it must be application/json",
),
(BotTokenNotFound, "X-TRAQ-BOT-TOKEN is not set"),
(ReadBotTokenFailed, "Failed to read X-TRAQ-BOT-TOKEN value"),
(BotTokenMismatch, "X-TRAQ-BOT-TOKEN value is wrong"),
(BotEventNotFound, "X-TRAQ-BOT-EVENT is not set"),
(ReadBotEventFailed, "Failed to read X-TRAQ-BOT-EVENT value"),
(BotEventMismatch, "X-TRAQ-BOT-EVENT value is wrong"),
(ReadBodyFailed, "Failed to read request body"),
(ParseBodyFailed, "Failed to parse request body"),
];
for (err, msg) in pairs.iter() {
assert_eq!(err.to_string(), *msg);
}
}
#[test]
fn parse_ping() {
let parser = make_parser();
let headers = make_headers("PING");
let body = read_to_string("testdata/system/ping.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<PingPayload>(&body).unwrap();
assert_eq!(event, Event::Ping(payload));
}
#[test]
fn parse_joined() {
let parser = make_parser();
let headers = make_headers("JOINED");
let body = read_to_string("testdata/system/joined.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<JoinedPayload>(&body).unwrap();
assert_eq!(event, Event::Joined(payload));
}
#[test]
fn parse_left() {
let parser = make_parser();
let headers = make_headers("LEFT");
let body = read_to_string("testdata/system/left.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<LeftPayload>(&body).unwrap();
assert_eq!(event, Event::Left(payload));
}
#[test]
fn parse_message_created() {
let parser = make_parser();
let headers = make_headers("MESSAGE_CREATED");
let body = read_to_string("testdata/message/message_created.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<MessageCreatedPayload>(&body).unwrap();
assert_eq!(event, Event::MessageCreated(payload));
}
#[test]
fn parse_message_deleted() {
let parser = make_parser();
let headers = make_headers("MESSAGE_DELETED");
let body = read_to_string("testdata/message/message_deleted.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<MessageDeletedPayload>(&body).unwrap();
assert_eq!(event, Event::MessageDeleted(payload));
}
#[test]
fn parse_message_updated() {
let parser = make_parser();
let headers = make_headers("MESSAGE_UPDATED");
let body = read_to_string("testdata/message/message_updated.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<MessageUpdatedPayload>(&body).unwrap();
assert_eq!(event, Event::MessageUpdated(payload));
}
#[test]
fn parse_direct_message_created() {
let parser = make_parser();
let headers = make_headers("DIRECT_MESSAGE_CREATED");
let body = read_to_string("testdata/message/direct_message_created.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<DirectMessageCreatedPayload>(&body).unwrap();
assert_eq!(event, Event::DirectMessageCreated(payload));
}
#[test]
fn parse_direct_message_deleted() {
let parser = make_parser();
let headers = make_headers("DIRECT_MESSAGE_DELETED");
let body = read_to_string("testdata/message/direct_message_deleted.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<DirectMessageDeletedPayload>(&body).unwrap();
assert_eq!(event, Event::DirectMessageDeleted(payload));
}
#[test]
fn parse_direct_message_updated() {
let parser = make_parser();
let headers = make_headers("DIRECT_MESSAGE_UPDATED");
let body = read_to_string("testdata/message/direct_message_updated.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<DirectMessageUpdatedPayload>(&body).unwrap();
assert_eq!(event, Event::DirectMessageUpdated(payload));
}
#[test]
fn parse_bot_message_stamps_updated() {
let parser = make_parser();
let headers = make_headers("BOT_MESSAGE_STAMPS_UPDATED");
let body = read_to_string("testdata/message/bot_message_stamps_updated.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<BotMessageStampsUpdatedPayload>(&body).unwrap();
assert_eq!(event, Event::BotMessageStampsUpdated(payload));
}
#[test]
fn parse_channel_created() {
let parser = make_parser();
let headers = make_headers("CHANNEL_CREATED");
let body = read_to_string("testdata/channel/channel_created.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<ChannelCreatedPayload>(&body).unwrap();
assert_eq!(event, Event::ChannelCreated(payload));
}
#[test]
fn parse_channel_topic_changed() {
let parser = make_parser();
let headers = make_headers("CHANNEL_TOPIC_CHANGED");
let body = read_to_string("testdata/channel/channel_topic_changed.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<ChannelTopicChangedPayload>(&body).unwrap();
assert_eq!(event, Event::ChannelTopicChanged(payload));
}
#[test]
fn parse_user_created() {
let parser = make_parser();
let headers = make_headers("USER_CREATED");
let body = read_to_string("testdata/user/user_created.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<UserCreatedPayload>(&body).unwrap();
assert_eq!(event, Event::UserCreated(payload));
}
#[test]
fn parse_tag_added() {
let parser = make_parser();
let headers = make_headers("TAG_ADDED");
let body = read_to_string("testdata/tag/tag_added.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<TagAddedPayload>(&body).unwrap();
assert_eq!(event, Event::TagAdded(payload));
}
#[test]
fn parse_tag_removed() {
let parser = make_parser();
let headers = make_headers("TAG_REMOVED");
let body = read_to_string("testdata/tag/tag_removed.json").unwrap();
let event = parser.parse(headers, body.as_bytes()).unwrap();
let payload = serde_json::from_str::<TagRemovedPayload>(&body).unwrap();
assert_eq!(event, Event::TagRemoved(payload));
}
}