diff --git a/src/core/mod.rs b/src/core/mod.rs index cd8bb2d5..8fd20ac7 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,3 @@ -pub mod network; +mod network; pub mod requests; pub mod types; diff --git a/src/core/network/mod.rs b/src/core/network/mod.rs index c1d5eba0..da42bd36 100644 --- a/src/core/network/mod.rs +++ b/src/core/network/mod.rs @@ -1,17 +1,16 @@ +use apply::Apply; use futures::compat::Future01CompatExt; +use reqwest::r#async::{multipart::Form, Client}; use serde::de::DeserializeOwned; use serde_json::Value; -use reqwest::{ - r#async::{Client, multipart::Form}, - StatusCode, -}; -use apply::Apply; +use crate::core::requests::{RequestError, ResponseResult}; const TELEGRAM_API_URL: &str = "https://api.telegram.org"; - -/// Create url for macking requests, see [telegram docs](https://core.telegram.org/bots/api#making-requests) +/// Creates URL for making HTTPS requests. See the [Telegram documentation]. +/// +/// [Telegram documentation]: https://core.telegram.org/bots/api#making-requests fn method_url(base: &str, token: &str, method_name: &str) -> String { format!( "{url}/bot{token}/{method}", @@ -21,7 +20,9 @@ fn method_url(base: &str, token: &str, method_name: &str) -> String { ) } -/// Create url for downloading file, see [telegram docs](https://core.telegram.org/bots/api#file) +/// Creates URL for downloading a file. See the [Telegram documentation]. +/// +/// [Telegram documentation] (https://core.telegram.org/bots/api#file) fn file_url(base: &str, token: &str, file_path: &str) -> String { format!( "{url}/file/bot{token}/{file}", @@ -31,33 +32,6 @@ fn file_url(base: &str, token: &str, file_path: &str) -> String { ) } -#[derive(Debug, Display)] -pub enum RequestError { - #[display(fmt = "Telegram error #{}: {}", status_code, description)] - ApiError { - status_code: StatusCode, - description: String, - }, - - #[display(fmt = "Network error: {err}", err = _0)] - NetworkError(reqwest::Error), - - #[display(fmt = "InvalidJson error caused by: {err}", err = _0)] - InvalidJson(serde_json::Error), -} - -impl std::error::Error for RequestError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - RequestError::ApiError { .. } => None, - RequestError::NetworkError(err) => Some(err), - RequestError::InvalidJson(err) => Some(err), - } - } -} - -pub type ResponseResult = Result; - pub async fn request( client: &Client, token: &str, @@ -66,12 +40,9 @@ pub async fn request( ) -> ResponseResult { let mut response = client .post(&method_url(TELEGRAM_API_URL, token, method_name)) - .apply(|request_builder| { - if let Some(params) = params { - request_builder.multipart(params) - } else { - request_builder - } + .apply(|request_builder| match params { + Some(params) => request_builder.multipart(params), + None => request_builder, }) .send() .compat() diff --git a/src/core/parse_mode.rs b/src/core/parse_mode.rs deleted file mode 100644 index 3a4f7054..00000000 --- a/src/core/parse_mode.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub enum ParseMode { - HTML, - Markdown, -} \ No newline at end of file diff --git a/src/core/requests/form_builder.rs b/src/core/requests/form_builder.rs index ce4a3d39..ba38bc19 100644 --- a/src/core/requests/form_builder.rs +++ b/src/core/requests/form_builder.rs @@ -38,8 +38,8 @@ impl FormBuilder { form: self.form.text( name.to_owned(), serde_json::to_string(value).expect("serde_json::to_string failed"), - ) - } + ), + }, } } diff --git a/src/core/requests/get_me.rs b/src/core/requests/get_me.rs index 464c85bf..7c9c4c3c 100644 --- a/src/core/requests/get_me.rs +++ b/src/core/requests/get_me.rs @@ -1,25 +1,18 @@ -use crate::core::{ - types::User, - network::{ - request, ResponseResult, - }, - requests::{ - Request, RequestInfo, RequestFuture, - } -}; - +use crate::core::network; +use crate::core::requests::{Request, RequestFuture, RequestInfo, ResponseResult}; +use crate::core::types::User; #[derive(Debug, Constructor)] pub struct GetMe { - info: RequestInfo + info: RequestInfo, } impl Request for GetMe { type ReturnValue = User; fn send(self) -> RequestFuture> { - Box::new(async move { - request(&self.info.client, &self.info.token, "getMe", None).await + Box::pin(async move { + network::request(&self.info.client, &self.info.token, "getMe", None).await }) } } diff --git a/src/core/requests/mod.rs b/src/core/requests/mod.rs index 1c88f1c6..dd0ba59e 100644 --- a/src/core/requests/mod.rs +++ b/src/core/requests/mod.rs @@ -1,13 +1,38 @@ use std::future::Future; +use std::pin::Pin; -use crate::core::network::ResponseResult; - -use serde::de::DeserializeOwned; use reqwest::r#async::Client; - +use reqwest::StatusCode; +use serde::de::DeserializeOwned; mod form_builder; +#[derive(Debug, Display)] +pub enum RequestError { + #[display(fmt = "Telegram error #{}: {}", status_code, description)] + ApiError { + status_code: StatusCode, + description: String, + }, + + #[display(fmt = "Network error: {err}", err = _0)] + NetworkError(reqwest::Error), + + #[display(fmt = "InvalidJson error caused by: {err}", err = _0)] + InvalidJson(serde_json::Error), +} + +impl std::error::Error for RequestError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + RequestError::ApiError { .. } => None, + RequestError::NetworkError(err) => Some(err), + RequestError::InvalidJson(err) => Some(err), + } + } +} + +pub type ResponseResult = Result; /// Request that can be sent to telegram. /// `ReturnValue` - a type that will be returned from Telegram. @@ -18,7 +43,9 @@ pub trait Request { fn send(self) -> RequestFuture>; } -pub type RequestFuture = Box>; +pub type RequestFuture = Pin + Send>>; + + // todo: better name? #[derive(Debug)] @@ -41,3 +68,7 @@ pub enum ChatId { pub mod get_me; pub mod send_message; + +fn a() { + use futures; +} \ No newline at end of file diff --git a/src/core/requests/send_message.rs b/src/core/requests/send_message.rs index 95cdb319..6244cce7 100644 --- a/src/core/requests/send_message.rs +++ b/src/core/requests/send_message.rs @@ -1,17 +1,6 @@ -use crate::core::{ - types::Message, - network::{ - request, ResponseResult, - }, - requests::{ - form_builder::FormBuilder, - ChatId, - Request, - RequestInfo, - RequestFuture, - } -}; - +use crate::core::requests::form_builder::FormBuilder; +use crate::core::requests::{ChatId, Request, RequestFuture, RequestInfo, ResponseResult}; +use crate::core::{network, types::Message}; #[derive(Debug, TypedBuilder)] pub struct SendMessage { @@ -35,9 +24,8 @@ pub struct SendMessage { impl Request for SendMessage { type ReturnValue = Message; - fn send(self) -> RequestFuture> { - Box::new(async move { + Box::pin(async move { let params = FormBuilder::new() .add("chat_id", &self.chat_id) .add("text", &self.text) @@ -50,7 +38,13 @@ impl Request for SendMessage { .add_if_some("reply_to_message_id", self.reply_to_message_id.as_ref()) .build(); - request(&self.info.client, &self.info.token, "sendMessage", Some(params)).await + network::request( + &self.info.client, + &self.info.token, + "sendMessage", + Some(params), + ) + .await }) } } diff --git a/src/core/types/chosen_inline_result.rs b/src/core/types/chosen_inline_result.rs index bd479fa5..489acfaa 100644 --- a/src/core/types/chosen_inline_result.rs +++ b/src/core/types/chosen_inline_result.rs @@ -1,4 +1,5 @@ use serde::Deserialize; + use crate::core::types::user::User; #[derive(Debug, Deserealize)] diff --git a/src/core/types/input_file.rs b/src/core/types/input_file.rs index 5475c572..0c3110f1 100644 --- a/src/core/types/input_file.rs +++ b/src/core/types/input_file.rs @@ -1,8 +1,25 @@ use serde::Deserialize; -#[derive(Debug, Deserialize, Hash, PartialEq, Eq)] +#[derive(Debug, Hash, PartialEq, Eq)] pub enum InputFile { - File(std::fs::File), + File(std::path::PathBuf), Url(String), FileId(String), } + +impl serde::Serialize for InputFile { + fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { + match self { + InputFile::File(path) => { + // NOTE: file should be actually attached with multipart/form-data + serializer.serialize_str( + // TODO: remove unwrap (?) + &format!("attach://{}", path.file_name().unwrap().to_string_lossy()) + ) + }, + InputFile::Url(url) => serializer.serialize_str(url), + InputFile::FileId(id) => serializer.serialize_str(id), + } + } + +} diff --git a/src/core/types/input_media.rs b/src/core/types/input_media.rs index 435fb48f..e9cdc5fd 100644 --- a/src/core/types/input_media.rs +++ b/src/core/types/input_media.rs @@ -1,69 +1,245 @@ use serde::Deserialize; -use crate::core::types::InputFile; +use crate::core::types::{InputFile, ParseMode}; +// TODO: should variants use new-type? +#[derive(Debug, Serialize, PartialEq, Eq)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +/// This object represents the content of a media message to be sent. +/// [More](https://core.telegram.org/bots/api#inputmedia) pub enum InputMedia { - InputMediaPhoto(InputMediaPhoto), - InputMediaVideo(InputMediaVideo), - InputMediaAnimation(InputMediaAnimation), - InputMediaAudio(InputMediaAudiotype), - InputMediaDocument(InputMediaDocument), + /// Represents a photo to be sent. + Photo { + /// File to send. + media: InputFile, + /// Caption of the photo to be sent, 0-1024 characters + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + }, + Video { + /// File to send.File to send. + media: InputFile, + #[serde(skip_serializing_if = "Option::is_none")] + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is + /// supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. + /// A thumbnail‘s width and height should not exceed 320. + /// Ignored if the file is not uploaded using [InputFile::File]. + /// + /// [InputFile::File]: crate::core::types::InputFile::File + thumb: Option, + /// Caption of the video to be sent, 0-1024 characters. + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + /// Video width + #[serde(skip_serializing_if = "Option::is_none")] + width: Option, + /// Video height + #[serde(skip_serializing_if = "Option::is_none")] + height: Option, + /// Video duration + #[serde(skip_serializing_if = "Option::is_none")] + duration: Option, + /// Pass `true`, if the uploaded video is suitable for streaming + #[serde(skip_serializing_if = "Option::is_none")] + supports_streaming: Option, + }, + /// Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. + Animation { + /// File to send. + media: InputFile, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is + /// supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. + /// A thumbnail‘s width and height should not exceed 320. + /// Ignored if the file is not uploaded using [InputFile::File]. + /// + /// [InputFile::File]: crate::core::types::InputFile::File + #[serde(skip_serializing_if = "Option::is_none")] + thumb: Option, + /// Caption of the animation to be sent, 0-1024 characters + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + /// Animation width + #[serde(skip_serializing_if = "Option::is_none")] + width: Option, + /// Animation height + #[serde(skip_serializing_if = "Option::is_none")] + height: Option, + /// Animation duration + #[serde(skip_serializing_if = "Option::is_none")] + duration: Option, + }, + /// Represents an audio file to be treated as music to be sent. + Audio { + /// File to send, + media: InputFile, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is + /// supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. + /// A thumbnail‘s width and height should not exceed 320. + /// Ignored if the file is not uploaded using [InputFile::File]. + /// + /// [InputFile::File]: crate::core::types::InputFile::File + #[serde(skip_serializing_if = "Option::is_none")] + thumb: Option, + /// Caption of the audio to be sent, 0-1024 characters + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + /// Duration of the audio in seconds + #[serde(skip_serializing_if = "Option::is_none")] + duration: Option, + /// Performer of the audio + #[serde(skip_serializing_if = "Option::is_none")] + performer: Option, + /// Title of the audio + #[serde(skip_serializing_if = "Option::is_none")] + title: Option + }, + /// Represents a general file to be sent. + Document { + /// File to send. + media: InputFile, + /// Thumbnail of the file sent; can be ignored if thumbnail generation for the file is + /// supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. + /// A thumbnail‘s width and height should not exceed 320. + /// Ignored if the file is not uploaded using [InputFile::File]. + /// + /// [InputFile::File]: crate::core::types::InputFile::File + #[serde(skip_serializing_if = "Option::is_none")] + thumb: Option, + /// Caption of the document to be sent, 0-1024 characters + #[serde(skip_serializing_if = "Option::is_none")] + caption: Option, + /// Send [Markdown] or [HTML], + /// if you want Telegram apps to show [bold, italic, fixed-width text or inline URLs] + /// in the media caption. + /// + /// [Markdown]: crate::core::types::ParseMode::Markdown + /// [Html]: crate::core::types::ParseMode::Html + /// [bold, italic, fixed-width text or inline URLs]: crate::core::types::ParseMode + #[serde(skip_serializing_if = "Option::is_none")] + parse_mode: Option, + }, } -pub enum ThumbKind { - InputFile, - String, -} +#[cfg(test)] +mod tests { + use super::*; -#[derive(Debug, Serialize, Deserialize)] -pub struct InputMediaPhoto { - type_: String, - media: String, - caption: Option, - parse_mode: Option, -} + #[test] + fn photo_serialize() { + let expected_json = r#"{"type":"photo","media":"123456"}"#; + let photo = InputMedia::Photo { + media: InputFile::FileId(String::from("123456")), + caption: None, + parse_mode: None, + }; -#[derive(Debug, Serialize), Deserialize] -pub struct InputMediaVideo { - type_: String, - media: String, - thumb: ThumbKind, - caption: Option, - parse_mode: Option, - width: Option, - height: Option, - duration: Option, - supports_streaming: Option, -} + let actual_json = serde_json::to_string(&photo).unwrap(); + assert_eq!(expected_json, actual_json); + } -#[derive(Debug, Serialize, Deserialize)] -pub struct InputMediaAnimation { - type_: String, - media: String, - thumb: Option, - caption: Option, - parse_mode: Option, - width: Option, - height: Option, - duration: Option, -} + #[test] + fn video_serialize() { + let expected_json = r#"{"type":"video","media":"123456"}"#; + let video = InputMedia::Video { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + width: None, + height: None, + duration: None, + supports_streaming: None, + }; -#[derive(Debug, Serialize, Deserialize)] -pub struct InputMediaAudio { - type_: String, - media: String, - thumb: Option, - caption: Option, - parse_mode: Option, - duration: Option, - performer: Option, - title: Option -} + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } -#[derive(Debug, Serialize, Deserialize)] -pub struct InputMediaDocument { - type_: String, - media: String, - thumb: Option, - caption: Option, - parse_mode: parse_mode, -} \ No newline at end of file + #[test] + fn animation_serialize() { + let expected_json = r#"{"type":"animation","media":"123456"}"#; + let video = InputMedia::Animation { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + width: None, + height: None, + duration: None, + }; + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn audio_serialize() { + let expected_json = r#"{"type":"audio","media":"123456"}"#; + let video = InputMedia::Audio { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + duration: None, + performer: None, + title: None + }; + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn document_serialize() { + let expected_json = r#"{"type":"document","media":"123456"}"#; + let video = InputMedia::Document { + media: InputFile::FileId(String::from("123456")), + thumb: None, + caption: None, + parse_mode: None, + }; + + let actual_json = serde_json::to_string(&video).unwrap(); + assert_eq!(expected_json, actual_json); + } +} diff --git a/src/core/types/input_message_content.rs b/src/core/types/input_message_content.rs index 14e4b142..cf4c6873 100644 --- a/src/core/types/input_message_content.rs +++ b/src/core/types/input_message_content.rs @@ -1,4 +1,5 @@ use serde::Serialize; + use crate::core::parse_mode::ParseMode; pub enum InputMessageContent { diff --git a/src/core/types/message.rs b/src/core/types/message.rs index 2a3e3dc3..f53bb5a6 100644 --- a/src/core/types/message.rs +++ b/src/core/types/message.rs @@ -1,11 +1,9 @@ // use serde::Deserialize; use crate::core::types::{ - Animation, Audio, Chat, Contact, - Document, Game, Invoice, InlineKeyboardMarkup, - PhotoSize, MessageEntity, Location, PassportData, Poll, - Sticker, SuccessfulPayment, - User, Video, VideoNote, Venue, Voice, + Animation, Audio, Chat, Contact, Document, Game, InlineKeyboardMarkup, Invoice, Location, + MessageEntity, PassportData, PhotoSize, Poll, Sticker, SuccessfulPayment, User, Venue, Video, + VideoNote, Voice, }; #[derive(Debug, Deserialize, Hash, PartialEq, Eq)] diff --git a/src/core/types/message_entity.rs b/src/core/types/message_entity.rs index 0763727f..9235bd2f 100644 --- a/src/core/types/message_entity.rs +++ b/src/core/types/message_entity.rs @@ -1,6 +1,5 @@ use crate::core::types::User; - #[derive(Deserialize, Debug, PartialEq, Hash, Eq)] pub struct MessageEntity { #[serde(flatten)] @@ -13,10 +12,19 @@ pub struct MessageEntity { #[serde(rename_all = "snake_case")] #[serde(tag = "type")] pub enum MessageEntityKind { - Mention, Hashtag, Cashtag, BotCommand, Url, Email, PhoneNumber, - Bold, Italic, Code, Pre, + Mention, + Hashtag, + Cashtag, + BotCommand, + Url, + Email, + PhoneNumber, + Bold, + Italic, + Code, + Pre, TextLink { url: String }, - TextMention { user: User } + TextMention { user: User }, } #[test] @@ -25,12 +33,13 @@ fn recursive_kind() { assert_eq!( MessageEntity { - kind: MessageEntityKind::TextLink { url: "ya.ru".into() }, + kind: MessageEntityKind::TextLink { + url: "ya.ru".into() + }, offset: 1, length: 2 }, - from_str::( - r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"# - ).unwrap() + from_str::(r#"{"type":"text_link","url":"ya.ru","offset":1,"length":2}"#) + .unwrap() ); } diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index 1df58800..6af2d1dc 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -1,4 +1,3 @@ -mod not_implemented_types; use self::not_implemented_types::*; @@ -28,6 +27,9 @@ pub use self::{ sticker::Sticker, successful_payment::SuccessfulPayment, user::User, + input_file::InputFile, + input_media::InputMedia, + parse_mode::ParseMode, }; mod answer_pre_checkout_query; @@ -42,6 +44,7 @@ mod invoice; mod label_price; mod message; mod message_entity; +mod not_implemented_types; mod order_info; mod pre_checkout_query; mod send_invoice; @@ -51,3 +54,6 @@ mod shipping_query; mod sticker; mod successful_payment; mod user; +mod input_file; +mod input_media; +mod parse_mode; diff --git a/src/core/types/parse_mode.rs b/src/core/types/parse_mode.rs new file mode 100644 index 00000000..5c885cfe --- /dev/null +++ b/src/core/types/parse_mode.rs @@ -0,0 +1,63 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +/// ## Formatting options +/// The Bot API supports basic formatting for messages. +/// You can use **bold** and *italic* text, as well as [inline links](https://example.com) and `pre-formatted code` in +/// your bots' messages. Telegram clients will render them accordingly. You can use either +/// markdown-style or HTML-style formatting. +/// +/// Note that Telegram clients will display an alert to the user before opening an inline link +/// (‘Open this link?’ together with the full URL). +/// +/// Links `tg://user?id=` can be used to mention a user by their id without using a username. +/// Please note: +/// +/// - These links will work only if they are used inside an inline link. +/// For example, they will not work, when used in an inline keyboard button or in a message text. +/// - The mentions are only guaranteed to work if: **A**. the user is a member in the group where he +/// was mentioned or **B**. the user has contacted the bot in the past or has sent a callback +/// query to the bot via inline button and has not restricted linking to their account in +/// `Settings > Privacy & Security > Forwarded Messages`. +/// +/// ## Markdown style +/// To use this mode, pass [Markdown] in the `parse_mode` field when using [SendMessage] (or other methods). +/// +/// Use the following syntax in your message: +/// +/// ```ignore +/// *bold text* +/// _italic text_ +/// [inline URL](http://www.example.com/) +/// [inline mention of a user](tg://user?id=123456789) +/// `inline fixed-width code` +/// ```block_language +/// pre-formatted fixed-width code block +/// ``` +/// ``` +/// +/// ## HTML style +/// To use this mode, pass [HTML] in the `parse_mode` field when using [SendMessage] (or other methods). +/// +/// The following tags are currently supported: +/// +/// ```ignore +/// bold, bold +/// italic, italic +/// inline URL +/// inline mention of a user +/// inline fixed-width code +///
pre-formatted fixed-width code block
+/// ``` +/// +/// Please note: +/// +/// - Only the tags mentioned above are currently supported. +/// - Tags must not be nested. +/// - All `<`, `>` and `&` symbols that are not a part of a tag or an HTML entity must be replaced with the corresponding HTML entities (`<` with `<`, `>` with `>` and `&` with `&`). +/// - All numerical HTML entities are supported. +/// - The API currently supports only the following named HTML entities: `<`, `>`, `&` and `"`. +pub enum ParseMode { + HTML, + Markdown, +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 4f1e44c6..c9bdacec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,4 +5,4 @@ extern crate serde; #[macro_use] extern crate typed_builder; -mod core; +pub mod core;