Merge branch 'dev' into unbox_send_future

This commit is contained in:
Waffle Lapkin 2019-09-28 15:45:54 +03:00 committed by GitHub
commit 8b1a032b55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 529 additions and 52 deletions

View file

@ -16,3 +16,4 @@ tokio = "0.2.0-alpha.4"
bytes = "0.4.12" bytes = "0.4.12"
futures-preview = "0.3.0-alpha.18" futures-preview = "0.3.0-alpha.18"
async-trait = "0.1.13" async-trait = "0.1.13"
libc = "0.2.62"

View file

@ -1,12 +1,9 @@
use crate::{ use crate::{
bot::Bot, bot::Bot,
requests::{ requests::{
edit_message_live_location::EditMessageLiveLocation, ChatId, EditMessageLiveLocation, ForwardMessage, GetFile, GetMe,
forward_message::ForwardMessage, get_file::GetFile, get_me::GetMe, SendAudio, SendLocation, SendMediaGroup, SendMessage, SendPhoto,
send_audio::SendAudio, send_location::SendLocation, StopMessageLiveLocation,
send_media_group::SendMediaGroup, send_message::SendMessage,
send_photo::SendPhoto,
stop_message_live_location::StopMessageLiveLocation, ChatId,
}, },
types::{InputFile, InputMedia}, types::{InputFile, InputMedia},
}; };

View file

@ -1,12 +1,14 @@
#![feature(termination_trait_lib)]
#[macro_use] #[macro_use]
extern crate derive_more; extern crate derive_more;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
mod network; mod network;
mod errors;
pub mod bot; pub mod bot;
pub mod errors;
pub mod requests; pub mod requests;
pub mod types; pub mod types;

View file

@ -1,10 +1,10 @@
use bytes::Buf;
use futures::StreamExt; use futures::StreamExt;
use reqwest::r#async::{Chunk, Client}; use reqwest::r#async::{Chunk, Client};
use tokio::{ use tokio::{
io::{AsyncWrite, AsyncWriteExt}, io::{AsyncWrite, AsyncWriteExt},
stream::Stream, stream::Stream,
}; };
use bytes::Buf;
use crate::{ use crate::{
network::{file_url, TELEGRAM_API_URL}, network::{file_url, TELEGRAM_API_URL},
@ -23,7 +23,8 @@ where
let mut stream = download_file_stream(client, token, path).await?; let mut stream = download_file_stream(client, token, path).await?;
while let Some(chunk) = stream.next().await { while let Some(chunk) = stream.next().await {
destination.write_all(chunk?.bytes()).await?; let chunk = chunk?;
destination.write_all(chunk.bytes()).await?;
} }
Ok(()) Ok(())

View file

@ -3,7 +3,7 @@ use reqwest::r#async::{multipart::Form, Client, Response};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use crate::{ use crate::{
network::{method_url, TELEGRAM_API_URL, TelegramResponse}, network::{method_url, TelegramResponse, TELEGRAM_API_URL},
requests::ResponseResult, requests::ResponseResult,
RequestError, RequestError,
}; };

View file

@ -3,6 +3,7 @@ use async_trait::async_trait;
use crate::{ use crate::{
network, network,
requests::{Request, RequestContext, ResponseResult}, requests::{Request, RequestContext, ResponseResult},
types::True
}; };
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
@ -35,7 +36,7 @@ pub struct AnswerPreCheckoutQuery<'a> {
#[async_trait] #[async_trait]
impl Request for AnswerPreCheckoutQuery<'_> { impl Request for AnswerPreCheckoutQuery<'_> {
type ReturnValue = bool; type ReturnValue = True;
async fn send_boxed(self) -> ResponseResult<Self::ReturnValue> { async fn send_boxed(self) -> ResponseResult<Self::ReturnValue> {
self.send().await self.send().await
@ -43,7 +44,7 @@ impl Request for AnswerPreCheckoutQuery<'_> {
} }
impl AnswerPreCheckoutQuery<'_> { impl AnswerPreCheckoutQuery<'_> {
pub async fn send(self) -> ResponseResult<bool> { pub async fn send(self) -> ResponseResult<True> {
network::request_json( network::request_json(
&self.ctx.client, &self.ctx.client,
&self.ctx.token, &self.ctx.token,

View file

@ -3,7 +3,7 @@ use async_trait::async_trait;
use crate::{ use crate::{
network, network,
requests::{Request, RequestContext, ResponseResult}, requests::{Request, RequestContext, ResponseResult},
types::ShippingOption, types::{ShippingOption, True},
}; };
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
@ -37,7 +37,7 @@ pub struct AnswerShippingQuery<'a> {
#[async_trait] #[async_trait]
impl Request for AnswerShippingQuery<'_> { impl Request for AnswerShippingQuery<'_> {
type ReturnValue = bool; type ReturnValue = True;
async fn send_boxed(self) -> ResponseResult<Self::ReturnValue> { async fn send_boxed(self) -> ResponseResult<Self::ReturnValue> {
self.send().await self.send().await

View file

@ -1,19 +1,73 @@
use crate::requests::RequestContext; use crate::network;
use crate::requests::{Request, RequestContext, RequestFuture, ResponseResult};
use crate::types::UserProfilePhotos;
//TODO: complete implementation after user_profile_fotos will be added to
// types/mod.rs
///Use this method to get a list of profile pictures for a user. Returns a ///Use this method to get a list of profile pictures for a user. Returns a
/// UserProfilePhotos object. /// UserProfilePhotos object.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct GetUserProfilePhotos<'a> { pub struct GetUserProfilePhotos<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
/// Unique identifier of the target user /// Unique identifier of the target user
user_id: i32, pub user_id: i32,
/// Sequential number of the first photo to be returned. By default, all /// Sequential number of the first photo to be returned. By default, all
/// photos are returned. /// photos are returned.
offset: Option<i64>, #[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<i64>,
///Limits the number of photos to be retrieved. Values between 1—100 are ///Limits the number of photos to be retrieved. Values between 1—100 are
/// accepted. Defaults to 100. /// accepted. Defaults to 100.
limit: Option<i64>, #[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<i64>,
}
impl<'a> Request<'a> for GetUserProfilePhotos<'a> {
type ReturnValue = UserProfilePhotos;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"getUserProfilePhotos",
&self,
)
.await
})
}
}
impl<'a> GetUserProfilePhotos<'a> {
pub fn new(ctx: RequestContext<'a>, user_id: i32) -> Self {
Self {
ctx,
user_id,
offset: None,
limit: None,
}
}
pub fn user_id<T>(mut self, user_id: T) -> Self
where
T: Into<i32>,
{
self.user_id = user_id.into();
self
}
pub fn offset<T>(mut self, offset: T) -> Self
where
T: Into<i64>,
{
self.offset = Some(offset.into());
self
}
pub fn limit<T>(mut self, limit: T) -> Self
where
T: Into<i64>,
{
self.limit = Some(limit.into());
self
}
} }

View file

@ -1,12 +1,72 @@
use crate::requests::RequestContext; use crate::network;
//TODO:: need implementation use crate::requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
};
use crate::types::True;
/// Use this method to kick a user from a group, a supergroup or a channel. In /// Use this method to kick a user from a group, a supergroup or a channel. In
/// the case of supergroups and channels, the user will not be able to return to /// the case of supergroups and channels, the user will not be able to return to
/// the group on their own using invite links, etc., unless unbanned first. The /// the group on their own using invite links, etc., unless unbanned first. The
/// bot must be an administrator in the chat for this to work and must have the /// bot must be an administrator in the chat for this to work and must have the
/// appropriate admin rights. Returns True on success. /// appropriate admin rights. Returns True on success.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct KickChatMember<'a> { pub struct KickChatMember<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
///Unique identifier for the target group or username of the target
/// supergroup or channel (in the format @channelusername)
pub chat_id: ChatId,
/// Unique identifier of the target user
pub user_id: i32,
///Date when the user will be unbanned, unix time. If user is banned for
/// more than 366 days or less than 30 seconds from the current time they
/// are considered to be banned forever
#[serde(skip_serializing_if = "Option::is_none")]
pub until_date: Option<u64>,
}
impl<'a> Request<'a> for KickChatMember<'a> {
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
self.ctx.client,
self.ctx.token,
"kickChatMember",
&self,
)
.await
})
}
}
impl<'a> KickChatMember<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
chat_id: ChatId,
user_id: i32,
) -> Self {
Self {
ctx,
chat_id,
user_id,
until_date: None,
}
}
pub fn chat_id<T: Into<ChatId>>(mut self, chat_id: T) -> Self {
self.chat_id = chat_id.into();
self
}
pub fn user_id<T: Into<i32>>(mut self, user_id: T) -> Self {
self.user_id = user_id.into();
self
}
pub fn until_date<T: Into<u64>>(mut self, until_date: T) -> Self {
self.until_date = Some(until_date.into());
self
}
} }

View file

@ -7,6 +7,23 @@ use serde::de::DeserializeOwned;
use crate::RequestError; use crate::RequestError;
pub use self::{
answer_pre_checkout_query::AnswerPreCheckoutQuery,
answer_shipping_query::AnswerShippingQuery,
edit_message_live_location::EditMessageLiveLocation,
forward_message::ForwardMessage, get_chat::GetChat, get_file::GetFile,
get_me::GetMe, get_updates::GetUpdates,
get_user_profile_photos::GetUserProfilePhotos,
kick_chat_member::KickChatMember, pin_chat_message::PinChatMessage,
restrict_chat_member::RestrictChatMember,
send_audio::SendAudio, send_chat_action::SendChatAction,
send_contact::SendContact, send_location::SendLocation,
send_media_group::SendMediaGroup, send_message::SendMessage,
send_photo::SendPhoto, send_poll::SendPoll, send_venue::SendVenue,
stop_message_live_location::StopMessageLiveLocation,
unban_chat_member::UnbanChatMember,
};
pub type ResponseResult<T> = Result<T, RequestError>; pub type ResponseResult<T> = Result<T, RequestError>;
/// Request that can be sent to telegram. /// Request that can be sent to telegram.
@ -62,25 +79,26 @@ mod tests {
} }
} }
pub mod answer_pre_checkout_query; mod answer_pre_checkout_query;
pub mod answer_shipping_query; mod answer_shipping_query;
pub mod edit_message_live_location; mod edit_message_live_location;
pub mod forward_message; mod forward_message;
pub mod get_chat; mod get_chat;
pub mod get_file; mod get_file;
pub mod get_me; mod get_me;
pub mod get_updates; mod get_updates;
pub mod get_user_profile_photos; mod get_user_profile_photos;
pub mod kick_chat_member; mod kick_chat_member;
pub mod restrict_chat_member; mod pin_chat_message;
pub mod send_audio; mod restrict_chat_member;
pub mod send_chat_action; mod send_audio;
pub mod send_contact; mod send_chat_action;
pub mod send_location; mod send_contact;
pub mod send_media_group; mod send_location;
pub mod send_message; mod send_media_group;
pub mod send_photo; mod send_message;
pub mod send_poll; mod send_photo;
pub mod send_venue; mod send_poll;
pub mod stop_message_live_location; mod send_venue;
pub mod unban_chat_member; mod stop_message_live_location;
mod unban_chat_member;

View file

@ -0,0 +1,50 @@
use crate::{
requests::{ChatId, RequestContext, RequestFuture, ResponseResult, Request},
types::True
};
use crate::network;
/// Use this method to get up to date information about the chat
/// (current name of the user for one-on-one conversations,
/// current username of a user, group or channel, etc.).
/// Returns a Chat object on success.
#[derive(Debug, Clone, Serialize)]
pub struct PinChatMessage<'a> {
#[serde(skip_serializing)]
ctx: RequestContext<'a>,
/// Unique identifier for the target chat or username
/// of the target supergroup or channel (in the format @channelusername)
pub chat_id: ChatId,
pub message_id: i32,
pub disable_notification: Option<bool>
}
impl<'a> PinChatMessage<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>, chat_id: ChatId, message_id: i32
) -> Self {
Self { ctx, chat_id, message_id, disable_notification: None }
}
pub fn disable_notification<T>(mut self, val: T) -> Self
where T: Into<bool>
{
self.disable_notification = Some(val.into());
self
}
}
impl<'a> Request<'a> for PinChatMessage<'a> {
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"pinChatMessage",
&self,
).await
})
}
}

View file

@ -1,8 +1,92 @@
use crate::requests::RequestContext; use crate::network;
//TODO:: need implementation use crate::requests::{
ChatId, Request, RequestContext, RequestFuture, ResponseResult,
};
use crate::types::{ChatPermissions, True};
/// Use this method to restrict a user in a supergroup. The bot must be an
/// administrator in the supergroup for this to work and must have the
/// appropriate admin rights. Pass True for all permissions to lift restrictions
/// from a user. Returns True on success.
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct RestrictChatMember<'a> { pub struct RestrictChatMember<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
///Unique identifier for the target chat or username of the target
/// supergroup (in the format @supergroupusername)
pub chat_id: ChatId,
///Unique identifier of the target user
pub user_id: i32,
///New user permissions
pub permissions: ChatPermissions,
///Date when restrictions will be lifted for the user, unix time. If user
/// is restricted for more than 366 days or less than 30 seconds from the
/// current time, they are considered to be restricted forever
#[serde(skip_serializing_if = "Option::is_none")]
pub until_date: Option<u64>,
}
impl<'a> Request<'a> for RestrictChatMember<'a> {
type ReturnValue = True;
fn send(self) -> RequestFuture<'a, ResponseResult<Self::ReturnValue>> {
Box::pin(async move {
network::request_json(
&self.ctx.client,
&self.ctx.token,
"restrictChatMember",
&self,
)
.await
})
}
}
impl<'a> RestrictChatMember<'a> {
pub(crate) fn new(
ctx: RequestContext<'a>,
chat_id: ChatId,
user_id: i32,
permissions: ChatPermissions,
) -> Self {
Self {
ctx,
chat_id,
user_id,
permissions,
until_date: None,
}
}
pub fn chat_id<T>(mut self, chat_id: T) -> Self
where
T: Into<ChatId>,
{
self.chat_id = chat_id.into();
self
}
pub fn user_id<T>(mut self, user_id: T) -> Self
where
T: Into<i32>,
{
self.user_id = user_id.into();
self
}
pub fn permissions<T>(mut self, permissions: T) -> Self
where
T: Into<ChatPermissions>,
{
self.permissions = permissions.into();
self
}
pub fn until_date<T>(mut self, until_date: T) -> Self
where
T: Into<u64>,
{
self.until_date = Some(until_date.into());
self
}
} }

View file

@ -3,6 +3,7 @@ use async_trait::async_trait;
use crate::{ use crate::{
network, network,
requests::{ChatId, Request, RequestContext, ResponseResult}, requests::{ChatId, Request, RequestContext, ResponseResult},
types::True
}; };
///Use this method when you need to tell the user that something is happening ///Use this method when you need to tell the user that something is happening
@ -41,7 +42,7 @@ pub enum ChatAction {
#[async_trait] #[async_trait]
impl Request for SendChatAction<'_> { impl Request for SendChatAction<'_> {
type ReturnValue = bool; type ReturnValue = True;
async fn send_boxed(self) -> ResponseResult<Self::ReturnValue> { async fn send_boxed(self) -> ResponseResult<Self::ReturnValue> {
self.send().await self.send().await

View file

@ -2,7 +2,7 @@ use crate::requests::RequestContext;
//TODO:: need implementation //TODO:: need implementation
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
struct UnbanChatMember<'a> { pub struct UnbanChatMember<'a> {
#[serde(skip_serializing)] #[serde(skip_serializing)]
ctx: RequestContext<'a>, ctx: RequestContext<'a>,
} }

View file

@ -0,0 +1,35 @@
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct EncryptedCredentials {
// TODO: check base64 type
pub data: String,
pub hash: String,
pub secret: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn must_serialize_encrypted_credentials_to_json() {
// given
let expected_json = r#"
{
"data":"someData",
"hash":"1122",
"secret":"secret"
}"#
.replace("\n", "")
.replace(" ", "");
let encrypted_credentials = EncryptedCredentials {
data: "someData".to_string(),
hash: "1122".to_string(),
secret: "secret".to_string(),
};
// when
let actual_json =
serde_json::to_string(&encrypted_credentials).unwrap();
//then
assert_eq!(actual_json, expected_json)
}
}

View file

@ -0,0 +1,67 @@
use super::passport_file::PassportFile;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct EncryptedPassportElement {
pub hash: String,
#[serde(flatten)]
pub kind: EncryptedPassportElementKind
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EncryptedPassportElementKind {
PersonalDetails {
data: String
},
Passport {
data: String,
front_side: PassportFile,
selfie: PassportFile,
translation: Option<Vec<PassportFile>>
},
DriverLicense {
data: String,
front_side: PassportFile,
reverse_side: PassportFile,
selfie: PassportFile,
translation: Option<Vec<PassportFile>>
},
IdentityCard {
data: String,
front_side: PassportFile,
reverse_side: PassportFile,
selfie: PassportFile,
translation: Option<Vec<PassportFile>>
},
InternalPassport {
data: String,
front_side: PassportFile,
selfie: PassportFile,
translation: Option<Vec<PassportFile>>
},
Address {
data: String
},
UtilityBill {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
BankStatement {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
RentalAgreement {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
PassportRegistration {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
TemporaryRegistration {
files: Vec<PassportFile>,
translation: Option<Vec<PassportFile>>
},
PhoneNumber { phone_number: String },
Email { email: String }
}

View file

@ -15,7 +15,7 @@ pub struct Message {
} }
impl Message { impl Message {
fn text(&self) -> Option<&str> { pub fn text(&self) -> Option<&str> {
if let MessageKind::Common { if let MessageKind::Common {
media_kind: MediaKind::Text { ref text, .. }, media_kind: MediaKind::Text { ref text, .. },
.. ..

View file

@ -11,6 +11,8 @@ pub use self::{
chosen_inline_result::ChosenInlineResult, chosen_inline_result::ChosenInlineResult,
contact::Contact, contact::Contact,
document::Document, document::Document,
encrypted_credintials::EncryptedCredentials,
encrypted_passport_element::{EncryptedPassportElement, EncryptedPassportElementKind},
file::File, file::File,
force_reply::ForceReply, force_reply::ForceReply,
game::Game, game::Game,
@ -54,6 +56,8 @@ pub use self::{
message_entity::MessageEntity, message_entity::MessageEntity,
order_info::OrderInfo, order_info::OrderInfo,
parse_mode::ParseMode, parse_mode::ParseMode,
passport_data::PassportData,
passport_file::PassportFile,
photo_size::PhotoSize, photo_size::PhotoSize,
poll::{Poll, PollOption}, poll::{Poll, PollOption},
pre_checkout_query::PreCheckoutQuery, pre_checkout_query::PreCheckoutQuery,
@ -68,6 +72,7 @@ pub use self::{
sticker::Sticker, sticker::Sticker,
sticker_set::StickerSet, sticker_set::StickerSet,
successful_payment::SuccessfulPayment, successful_payment::SuccessfulPayment,
unit_true::True,
update::{Update, UpdateKind}, update::{Update, UpdateKind},
user::User, user::User,
user_profile_photos::UserProfilePhotos, user_profile_photos::UserProfilePhotos,
@ -123,6 +128,7 @@ mod shipping_query;
mod sticker; mod sticker;
mod sticker_set; mod sticker_set;
mod successful_payment; mod successful_payment;
mod unit_true;
mod update; mod update;
mod user; mod user;
mod user_profile_photos; mod user_profile_photos;
@ -154,3 +160,8 @@ mod inline_query_result_photo;
mod inline_query_result_venue; mod inline_query_result_venue;
mod inline_query_result_video; mod inline_query_result_video;
mod inline_query_result_voice; mod inline_query_result_voice;
mod encrypted_credintials;
mod encrypted_passport_element;
mod passport_data;
mod passport_file;

View file

@ -0,0 +1,8 @@
use super::encrypted_credintials::EncryptedCredentials;
use super::encrypted_passport_element::EncryptedPassportElement;
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct PassportData {
pub data: Vec<EncryptedPassportElement>,
pub credentials: EncryptedCredentials,
}

View file

@ -0,0 +1,6 @@
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Clone, Serialize)]
pub struct PassportFile {
pub file_id: String,
pub file_size: u64,
pub file_date: u64,
}

81
src/types/unit_true.rs Normal file
View file

@ -0,0 +1,81 @@
use serde::de::{self, Deserialize, Deserializer, Visitor};
use serde::ser::{Serialize, Serializer};
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct True;
impl std::process::Termination for True {
fn report(self) -> i32 {
libc::EXIT_SUCCESS
}
}
impl std::convert::TryFrom<bool> for True {
type Error = ();
fn try_from(value: bool) -> Result<Self, Self::Error> {
match value {
true => Ok(True),
false => Err(())
}
}
}
impl<'de> Deserialize<'de> for True {
fn deserialize<D>(des: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>
{
des.deserialize_bool(TrueVisitor)
}
}
struct TrueVisitor;
impl<'de> Visitor<'de> for TrueVisitor {
type Value = True;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "bool, equal to `true`")
}
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
where
E: de::Error
{
match value {
true => Ok(True),
false => Err(E::custom("expected `true`, found `false`"))
}
}
}
impl Serialize for True {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bool(true)
}
}
#[cfg(test)]
mod tests {
use super::True;
use serde_json::{from_str, to_string};
#[test]
fn unit_true_de() {
let json = "true";
let expected = True;
let actual = from_str(json).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn unit_true_se() {
let actual = to_string(&True).unwrap();
let expected = "true";
assert_eq!(expected, actual);
}
}