From 85ae3d601cb3e4c676aff0871a221a6a5dbbc5e4 Mon Sep 17 00:00:00 2001 From: Ritesh Chitlangi Date: Tue, 20 Jul 2021 15:45:01 +0800 Subject: [PATCH] deserialization boilerplate --- Cargo.toml | 5 ++ src/main.rs => examples/client.rs | 13 +--- src/epp.rs | 1 + src/epp/object.rs | 66 ++++++++++++++++- src/epp/quick_xml.rs | 14 +++- src/epp/request.rs | 82 +++------------------ src/epp/response.rs | 117 ++++++++++++++++++++++++++++++ src/epp/xml.rs | 12 ++- src/lib.rs | 58 +++++++-------- 9 files changed, 254 insertions(+), 114 deletions(-) rename src/main.rs => examples/client.rs (53%) diff --git a/Cargo.toml b/Cargo.toml index 2641d51..518afa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[example]] +name = "example-client" +path = "examples/client.rs" + [dependencies] bytes = "1" +chrono = "0.4" confy = "0.4" futures = "0.3" lazy_static = "1.4" diff --git a/src/main.rs b/examples/client.rs similarity index 53% rename from src/main.rs rename to examples/client.rs index 4dc9d89..e72054e 100644 --- a/src/main.rs +++ b/examples/client.rs @@ -1,17 +1,12 @@ -pub mod config; -pub mod connection; -pub mod epp; -pub mod error; - -use std::time::SystemTime; -use tokio::time::{sleep, Duration}; -use crate::{epp::request}; +use epp_client::{epp::request, connection, epp::xml::EppXml, epp::response::EppResponse}; #[tokio::main] async fn main() { let mut client = match connection::connect("hexonet").await { Ok(client) => { - println!("{}", client.greeting()); + let greeting = client.greeting(); + let greeting_object = EppResponse::deserialize(&greeting).unwrap(); + println!("{:?}", greeting_object); client }, Err(e) => panic!("Error: {}", e) diff --git a/src/epp.rs b/src/epp.rs index fae8a6c..9c77af7 100644 --- a/src/epp.rs +++ b/src/epp.rs @@ -2,3 +2,4 @@ pub mod object; pub mod quick_xml; pub mod request; pub mod xml; +pub mod response; diff --git a/src/epp/object.rs b/src/epp/object.rs index 073125c..ecfee58 100644 --- a/src/epp/object.rs +++ b/src/epp/object.rs @@ -1,5 +1,26 @@ use serde::{Deserialize, Serialize}; +use crate::epp::xml::{EPP_XMLNS, EPP_XMLNS_XSI, EPP_XSI_SCHEMA_LOCATION}; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct StringValue(String); + +impl Default for StringValue { + fn default() -> Self { + Self(String::from("")) + } +} + +pub trait StringValueTrait { + fn to_string_value(&self) -> StringValue; +} + +impl StringValueTrait for &str { + fn to_string_value(&self) -> StringValue { + StringValue(self.to_string()) + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename = "epp")] pub struct EppObject { @@ -7,6 +28,49 @@ pub struct EppObject { #[serde(rename = "xmlns:xsi")] pub xmlns_xsi: String, #[serde(rename = "xsi:schemaLocation")] - pub xsi_schema_location: String, + pub xsi_schema_location: String, + #[serde(alias = "greeting")] pub data: T, } + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename = "options")] +pub struct Options { + pub version: StringValue, + pub lang: StringValue, +} + +impl Options { + pub fn build(version: &str, lang: &str) -> Options { + Options { + version: version.to_string_value(), + lang: lang.to_string_value(), + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename = "svcExtension")] +pub struct ServiceExtension { + #[serde(rename = "extURI")] + pub ext_uris: Option>, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Services { + #[serde(rename = "objURI")] + pub obj_uris: Vec, + #[serde(rename = "svcExtension")] + pub svc_ext: Option, +} + +impl EppObject { + pub fn new(data: T) -> EppObject { + EppObject { + data: data, + xmlns: EPP_XMLNS.to_string(), + xmlns_xsi: EPP_XMLNS_XSI.to_string(), + xsi_schema_location: EPP_XSI_SCHEMA_LOCATION.to_string(), + } + } +} diff --git a/src/epp/quick_xml.rs b/src/epp/quick_xml.rs index 360d9ba..db266c6 100644 --- a/src/epp/quick_xml.rs +++ b/src/epp/quick_xml.rs @@ -1,14 +1,24 @@ +use quick_xml::de::from_str; use quick_xml::se; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; use std::error::Error; use crate::epp::object::EppObject; use crate::epp::xml::{EppXml, EPP_XML_HEADER}; -impl EppXml for EppObject { +impl EppXml for EppObject { + type Object = EppObject; + fn serialize(&self) -> Result> { let epp_xml = format!("{}\r\n{}", EPP_XML_HEADER, se::to_string(self)?); Ok(epp_xml) } + + fn deserialize(epp_xml: &str) -> Result> { + match from_str(epp_xml) { + Ok(v) => Ok(v), + Err(e) => Err(format!("epp-client Deserialization Error: {}", e).into()), + } + } } diff --git a/src/epp/request.rs b/src/epp/request.rs index 44df971..e484c25 100644 --- a/src/epp/request.rs +++ b/src/epp/request.rs @@ -2,39 +2,17 @@ use serde::{Deserialize, Serialize}; use std::error::Error; use std::time::SystemTime; -use crate::epp::object::EppObject; - -const EPP_XMLNS: &str = "urn:ietf:params:xml:ns:epp-1.0"; -const EPP_XMLNS_XSI: &str = "http://www.w3.org/2001/XMLSchema-instance"; -const EPP_XSI_SCHEMA_LOCATION: &str = "urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd"; - -const EPP_VERSION: &str = "1.0"; -const EPP_LANG: &str = "en"; - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct StringValue(String); - -impl Default for StringValue { - fn default() -> Self { - Self(String::from("")) - } -} - -pub trait StringValueTrait { - fn to_string_value(&self) -> StringValue; -} - -impl StringValueTrait for &str { - fn to_string_value(&self) -> StringValue { - StringValue(self.to_string()) - } -} +use crate::epp::object::{ + EppObject, Options, ServiceExtension, Services, StringValue, StringValueTrait, +}; +use crate::epp::xml::{EPP_LANG, EPP_VERSION, EPP_XMLNS, EPP_XMLNS_XSI, EPP_XSI_SCHEMA_LOCATION}; #[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "lowercase")] pub enum RequestType { Hello, - Command { + #[serde(rename = "command")] + CommandLogin { login: Login, #[serde(rename = "clTRID")] client_tr_id: StringValue, @@ -48,15 +26,6 @@ pub enum RequestType { } impl EppObject { - pub fn new(data: RequestType) -> EppObject { - EppObject { - data: data, - xmlns: EPP_XMLNS.to_string(), - xmlns_xsi: EPP_XMLNS_XSI.to_string(), - xsi_schema_location: EPP_XSI_SCHEMA_LOCATION.to_string(), - } - } - pub fn generate_client_tr_id(username: &str) -> Result> { let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; Ok(format!("{}:{}", username, timestamp.as_secs())) @@ -75,37 +44,6 @@ impl Hello { } } -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename = "options")] -pub struct LoginOptions { - version: StringValue, - lang: StringValue, -} - -impl LoginOptions { - pub fn build(version: &str, lang: &str) -> LoginOptions { - LoginOptions { - version: version.to_string_value(), - lang: lang.to_string_value(), - } - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -#[serde(rename = "svcExtension")] -pub struct ServiceExtension { - #[serde(rename = "extURI")] - ext_uris: Option>, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq)] -pub struct Services { - #[serde(rename = "objURI")] - obj_uris: Vec, - #[serde(rename = "svcExtension")] - svc_ext: Option, -} - #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct Command { login: Login, @@ -120,7 +58,7 @@ pub struct Login { username: StringValue, #[serde(rename = "pw", default)] password: StringValue, - options: LoginOptions, + options: Options, #[serde(rename = "svcs")] services: Services, } @@ -130,7 +68,7 @@ impl Login { let login = Login { username: username.to_string_value(), password: password.to_string_value(), - options: LoginOptions { + options: Options { version: EPP_VERSION.to_string_value(), lang: EPP_LANG.to_string_value(), }, @@ -148,13 +86,13 @@ impl Login { }, }; - EppRequest::new(RequestType::Command { + EppRequest::new(RequestType::CommandLogin { login: login, client_tr_id: client_tr_id.to_string_value(), }) } - pub fn set_options(&mut self, options: LoginOptions) { + pub fn set_options(&mut self, options: Options) { self.options = options; } diff --git a/src/epp/response.rs b/src/epp/response.rs index 8b13789..827563b 100644 --- a/src/epp/response.rs +++ b/src/epp/response.rs @@ -1 +1,118 @@ +// use chrono::{DateTime, Utc}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::error::Error; +use crate::epp::object::{EppObject, Options, ServiceExtension, Services, StringValue}; +use crate::epp::xml::{EPP_XMLNS, EPP_XMLNS_XSI, EPP_XSI_SCHEMA_LOCATION}; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum ResponseType { + #[serde(rename = "greeting")] + Greeting(Greeting), +} + +pub type EppResponse = EppObject; + +#[derive(Serialize, Debug, PartialEq)] +pub struct ServiceMenu { + pub options: Options, + pub services: Services, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +struct FlattenedServiceMenu { + pub version: StringValue, + pub lang: StringValue, + #[serde(rename = "objURI")] + pub obj_uris: Vec, + #[serde(rename = "svcExtension")] + pub svc_ext: Option, +} + +impl<'de> Deserialize<'de> for ServiceMenu { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let flattened_svc_menu = FlattenedServiceMenu::deserialize(deserializer)?; + + let svc_menu = ServiceMenu { + options: Options { + version: flattened_svc_menu.version, + lang: flattened_svc_menu.lang, + }, + services: Services { + obj_uris: flattened_svc_menu.obj_uris, + svc_ext: flattened_svc_menu.svc_ext, + }, + }; + + Ok(svc_menu) + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct All; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Access { + all: All, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Admin; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Prov; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Purpose { + admin: Admin, + prov: Prov, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Ours; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Public; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Recipient { + ours: Ours, + public: Public, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Stated; + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Retention { + stated: Stated, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Statement { + purpose: Purpose, + recipient: Recipient, + retention: Retention, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Dcp { + access: Access, + statement: Statement, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "lowercase")] +pub struct Greeting { + #[serde(rename = "svID")] + service_id: String, + #[serde(rename = "svDate")] + service_date: String, + #[serde(rename = "svcMenu")] + svc_menu: ServiceMenu, + dcp: Dcp, +} diff --git a/src/epp/xml.rs b/src/epp/xml.rs index e78f9ba..24bf2ba 100644 --- a/src/epp/xml.rs +++ b/src/epp/xml.rs @@ -1,8 +1,18 @@ use std::error::Error; +// use crate::epp::object::EppObject; + pub const EPP_XML_HEADER: &str = r#""#; +pub const EPP_XMLNS: &str = "urn:ietf:params:xml:ns:epp-1.0"; +pub const EPP_XMLNS_XSI: &str = "http://www.w3.org/2001/XMLSchema-instance"; +pub const EPP_XSI_SCHEMA_LOCATION: &str = "urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd"; + +pub const EPP_VERSION: &str = "1.0"; +pub const EPP_LANG: &str = "en"; pub trait EppXml { + type Object; + fn serialize(&self) -> Result>; - // fn deserialize(&self) -> Result>; + fn deserialize(epp_xml: &str) -> Result>; } diff --git a/src/lib.rs b/src/lib.rs index 19cc4dd..49e35a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,34 +1,34 @@ -// pub mod config; -// pub mod connection; -// pub mod epp; -// pub mod error; +pub mod config; +pub mod connection; +pub mod epp; +pub mod error; -// #[cfg(test)] -// mod tests { -// use super::config; -// use super::connection; +#[cfg(test)] +mod tests { + use super::config; + use super::connection; -// #[test] -// fn config() { -// let servers = &config::CONFIG.servers; + #[test] + fn config() { + let servers = &config::CONFIG.servers; -// () -// } + () + } -// macro_rules! aw { -// ($e:expr) => { -// tokio_test::block_on($e) -// }; -// } + macro_rules! aw { + ($e:expr) => { + tokio_test::block_on($e) + }; + } -// #[test] -// fn connect() { -// let mut client = match aw!(connection::connect("hexonet")) { -// Ok(client) => { -// println!("{}", client.greeting()); -// client -// }, -// Err(e) => panic!("Error: {}", e) -// }; -// } -// } + #[test] + fn connect() { + let mut client = match aw!(connection::connect("hexonet")) { + Ok(client) => { + println!("{}", client.greeting()); + client + }, + Err(e) => panic!("Error: {}", e) + }; + } +}