more intuitive serialization and object structure, and domain check command added

This commit is contained in:
Ritesh Chitlangi 2021-07-21 00:10:56 +08:00
parent 4de42f1951
commit e879ce5d9a
10 changed files with 215 additions and 71 deletions

View File

@ -1,4 +1,5 @@
use epp_client::{epp::request, connection, epp::xml::EppXml, epp::response::EppGreeting}; use epp_client::{epp::request, epp::request::generate_client_tr_id, epp::request::domain, connection, epp::xml::EppXml, epp::response::EppGreeting, epp::object::ElementName};
use epp_client::epp::response;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -12,7 +13,8 @@ async fn main() {
Err(e) => panic!("Error: {}", e) Err(e) => panic!("Error: {}", e)
}; };
let epp_hello = request::Hello::new(); let domains = vec!["eppdev.com", "hexonet.net"];
let domain_check = domain::DomainCheck::epp_new(domains, generate_client_tr_id("eppdev").unwrap().as_str());
client.transact::<EppGreeting>(&epp_hello).await.unwrap(); let response = client.transact::<domain::EppDomainCheck, response::domain::EppDomainCheckResponse>(&domain_check).await.unwrap();
} }

View File

@ -10,8 +10,7 @@ use tokio::{net::TcpStream, io::AsyncWriteExt, io::AsyncReadExt, io::split, io::
use crate::config::{CONFIG, EppClientConnection}; use crate::config::{CONFIG, EppClientConnection};
use crate::error; use crate::error;
use crate::epp::object::EppObject; use crate::epp::request::{generate_client_tr_id, Login, EppLogin, Logout, EppLogout};
use crate::epp::request::{EppRequest, Login, Logout};
use crate::epp::response::EppCommandResponse; use crate::epp::response::EppCommandResponse;
use crate::epp::xml::EppXml; use crate::epp::xml::EppXml;
@ -144,15 +143,15 @@ impl EppClient {
credentials: credentials credentials: credentials
}; };
let client_tr_id = EppRequest::generate_client_tr_id(&client.credentials.0)?; let client_tr_id = generate_client_tr_id(&client.credentials.0)?;
let login_request = Login::new(&client.credentials.0, &client.credentials.1, client_tr_id.as_str()); let login_request = Login::epp_new(&client.credentials.0, &client.credentials.1, client_tr_id.as_str());
client.transact::<EppCommandResponse>(&login_request).await?; client.transact::<EppLogin, EppCommandResponse>(&login_request).await?;
Ok(client) Ok(client)
} }
pub async fn transact<E: EppXml + Debug>(&mut self, request: &EppRequest) -> Result<E::Output, Box<dyn Error>> { pub async fn transact<T: EppXml + Debug, E: EppXml + Debug>(&mut self, request: &T) -> Result<E::Output, Box<dyn Error>> {
let epp_xml = request.serialize()?; let epp_xml = request.serialize()?;
println!("Request:\r\n{}", epp_xml); println!("Request:\r\n{}", epp_xml);
@ -177,10 +176,10 @@ impl EppClient {
} }
pub async fn logout(&mut self) { pub async fn logout(&mut self) {
let client_tr_id = EppRequest::generate_client_tr_id(&self.credentials.0).unwrap(); let client_tr_id = generate_client_tr_id(&self.credentials.0).unwrap();
let epp_logout = Logout::new(client_tr_id.as_str()); let epp_logout = Logout::epp_new(client_tr_id.as_str());
self.transact::<EppCommandResponse>(&epp_logout).await; self.transact::<EppLogout, EppCommandResponse>(&epp_logout).await;
} }
} }

View File

@ -1,5 +1,7 @@
pub mod command;
pub mod object; pub mod object;
pub mod quick_xml; pub mod quick_xml;
pub mod request; pub mod request;
pub mod xml;
pub mod response; pub mod response;
pub mod xml;
pub use request::domain;

29
src/epp/command.rs Normal file
View File

@ -0,0 +1,29 @@
use crate::epp::object::{ElementName, StringValue};
use serde::ser::{SerializeStruct, Serializer};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug, PartialEq)]
pub struct Command<T: ElementName> {
pub command: T,
#[serde(rename = "clTRID")]
pub client_tr_id: StringValue,
}
impl<T: ElementName> ElementName for Command<T> {
fn element_name(&self) -> &'static str {
"command"
}
}
impl<T: ElementName + Serialize> Serialize for Command<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let command_name = self.command.element_name();
let mut state = serializer.serialize_struct("command", 2)?;
state.serialize_field(command_name, &self.command)?;
state.serialize_field("clTRID", &self.client_tr_id)?;
state.end()
}
}

View File

@ -1,4 +1,4 @@
use serde::{Deserialize, Serialize}; use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
use crate::epp::xml::{EPP_XMLNS, EPP_XMLNS_XSI, EPP_XSI_SCHEMA_LOCATION}; use crate::epp::xml::{EPP_XMLNS, EPP_XMLNS_XSI, EPP_XSI_SCHEMA_LOCATION};
@ -21,9 +21,13 @@ impl StringValueTrait for &str {
} }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] pub trait ElementName {
fn element_name(&self) -> &'static str;
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename = "epp")] #[serde(rename = "epp")]
pub struct EppObject<T> { pub struct EppObject<T: ElementName> {
pub xmlns: String, pub xmlns: String,
#[serde(rename = "xmlns:xsi")] #[serde(rename = "xmlns:xsi")]
pub xmlns_xsi: String, pub xmlns_xsi: String,
@ -33,6 +37,21 @@ pub struct EppObject<T> {
pub data: T, pub data: T,
} }
impl<T: ElementName + Serialize> Serialize for EppObject<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let data_name = self.data.element_name();
let mut state = serializer.serialize_struct("epp", 4)?;
state.serialize_field("xmlns", &self.xmlns)?;
state.serialize_field("xmlns:xsi", &self.xmlns_xsi)?;
state.serialize_field("xsi:schemaLocation", &self.xsi_schema_location)?;
state.serialize_field(data_name, &self.data)?;
state.end()
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename = "options")] #[serde(rename = "options")]
pub struct Options { pub struct Options {
@ -64,7 +83,7 @@ pub struct Services {
pub svc_ext: Option<ServiceExtension>, pub svc_ext: Option<ServiceExtension>,
} }
impl<T> EppObject<T> { impl<T: ElementName> EppObject<T> {
pub fn new(data: T) -> EppObject<T> { pub fn new(data: T) -> EppObject<T> {
EppObject { EppObject {
data: data, data: data,

View File

@ -3,10 +3,10 @@ use quick_xml::se;
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use std::{error::Error, fmt::Debug}; use std::{error::Error, fmt::Debug};
use crate::epp::object::EppObject; use crate::epp::object::{ElementName, EppObject};
use crate::epp::xml::{EppXml, EPP_XML_HEADER}; use crate::epp::xml::{EppXml, EPP_XML_HEADER};
impl<T: Serialize + DeserializeOwned + Debug> EppXml for EppObject<T> { impl<T: Serialize + DeserializeOwned + ElementName + Debug> EppXml for EppObject<T> {
type Output = EppObject<T>; type Output = EppObject<T>;
fn serialize(&self) -> Result<String, Box<dyn Error>> { fn serialize(&self) -> Result<String, Box<dyn Error>> {

View File

@ -1,54 +1,38 @@
pub mod domain;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::error::Error; use std::error::Error;
use std::time::SystemTime; use std::time::SystemTime;
pub use crate::epp::command::Command;
use crate::epp::object::{ use crate::epp::object::{
EppObject, Options, ServiceExtension, Services, StringValue, StringValueTrait, ElementName, EppObject, Options, ServiceExtension, Services, StringValue, StringValueTrait,
}; };
use crate::epp::xml::{EPP_LANG, EPP_VERSION, EPP_XMLNS, EPP_XMLNS_XSI, EPP_XSI_SCHEMA_LOCATION}; use crate::epp::xml::{EPP_LANG, EPP_VERSION};
#[derive(Serialize, Deserialize, Debug, PartialEq)] pub type EppHello = EppObject<Hello>;
#[serde(rename_all = "lowercase")] pub type EppLogin = EppObject<Command<Login>>;
pub enum RequestType { pub type EppLogout = EppObject<Command<Logout>>;
Hello,
#[serde(rename = "command")] pub fn generate_client_tr_id(username: &str) -> Result<String, Box<dyn Error>> {
CommandLogin { let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
login: Login, Ok(format!("{}:{}", username, timestamp.as_secs()))
#[serde(rename = "clTRID")]
client_tr_id: StringValue,
},
#[serde(rename = "command")]
CommandLogout {
logout: Logout,
#[serde(rename = "clTRID")]
client_tr_id: StringValue,
},
} }
impl<RequestType> EppObject<RequestType> {
pub fn generate_client_tr_id(username: &str) -> Result<String, Box<dyn Error>> {
let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?;
Ok(format!("{}:{}", username, timestamp.as_secs()))
}
}
pub type EppRequest = EppObject<RequestType>;
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "lowercase")] #[serde(rename = "hello")]
pub struct Hello; pub struct Hello;
impl Hello { impl ElementName for Hello {
pub fn new() -> EppRequest { fn element_name(&self) -> &'static str {
EppRequest::new(RequestType::Hello) "hello"
} }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] impl Hello {
pub struct Command { pub fn epp_new() -> EppHello {
login: Login, EppObject::new(Hello {})
#[serde(rename = "clTRID")] }
client_tr_id: StringValue,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
@ -63,8 +47,14 @@ pub struct Login {
services: Services, services: Services,
} }
impl ElementName for Login {
fn element_name(&self) -> &'static str {
"login"
}
}
impl Login { impl Login {
pub fn new(username: &str, password: &str, client_tr_id: &str) -> EppRequest { pub fn epp_new(username: &str, password: &str, client_tr_id: &str) -> EppLogin {
let login = Login { let login = Login {
username: username.to_string_value(), username: username.to_string_value(),
password: password.to_string_value(), password: password.to_string_value(),
@ -86,8 +76,8 @@ impl Login {
}, },
}; };
EppRequest::new(RequestType::CommandLogin { EppObject::new(Command::<Login> {
login: login, command: login,
client_tr_id: client_tr_id.to_string_value(), client_tr_id: client_tr_id.to_string_value(),
}) })
} }
@ -106,10 +96,16 @@ impl Login {
pub struct Logout; pub struct Logout;
impl Logout { impl Logout {
pub fn new(client_tr_id: &str) -> EppRequest { pub fn epp_new(client_tr_id: &str) -> EppLogout {
EppRequest::new(RequestType::CommandLogout { EppObject::new(Command::<Logout> {
logout: Logout, command: Logout,
client_tr_id: client_tr_id.to_string_value(), client_tr_id: client_tr_id.to_string_value(),
}) })
} }
} }
impl ElementName for Logout {
fn element_name(&self) -> &'static str {
"logout"
}
}

47
src/epp/request/domain.rs Normal file
View File

@ -0,0 +1,47 @@
use crate::epp::command::Command;
use crate::epp::object::{ElementName, EppObject, StringValue, StringValueTrait};
use serde::{Deserialize, Serialize};
const EPP_DOMAIN_XMLNS: &str = "urn:ietf:params:xml:ns:domain-1.0";
pub type EppDomainCheck = EppObject<Command<DomainCheck>>;
#[derive(Serialize, Deserialize, Debug)]
pub struct DomainList {
pub xmlns: String,
#[serde(rename = "name")]
pub domains: Vec<StringValue>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DomainCheck {
#[serde(rename = "check")]
list: DomainList,
}
impl ElementName for DomainCheck {
fn element_name(&self) -> &'static str {
"check"
}
}
impl DomainCheck {
pub fn epp_new(domains: Vec<&str>, client_tr_id: &str) -> EppDomainCheck {
let domains = domains
.iter()
.filter_map(|d| Some(d.to_string_value()))
.collect::<Vec<StringValue>>();
let domain_check = DomainCheck {
list: DomainList {
xmlns: EPP_DOMAIN_XMLNS.to_string(),
domains: domains,
},
};
EppObject::new(Command::<DomainCheck> {
command: domain_check,
client_tr_id: client_tr_id.to_string_value(),
})
}
}

View File

@ -1,15 +1,13 @@
pub mod domain;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use std::error::Error;
use crate::epp::object::{EppObject, Options, ServiceExtension, Services, StringValue}; use crate::epp::object::{
use crate::epp::xml::{EPP_XMLNS, EPP_XMLNS_XSI, EPP_XSI_SCHEMA_LOCATION}; ElementName, EppObject, Options, ServiceExtension, Services, StringValue,
};
#[derive(Serialize, Deserialize, Debug, PartialEq)] pub type EppGreeting = EppObject<Greeting>;
#[serde(rename_all = "lowercase")] pub type EppCommandResponse = EppObject<CommandResponse<String>>;
pub struct ResponseType<T>(T);
pub type EppGreeting = EppObject<ResponseType<Greeting>>;
pub type EppCommandResponse = EppObject<ResponseType<CommandResponse>>;
#[derive(Serialize, Debug, PartialEq)] #[derive(Serialize, Debug, PartialEq)]
pub struct ServiceMenu { pub struct ServiceMenu {
@ -131,8 +129,22 @@ pub struct ResponseTRID {
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub struct CommandResponse { pub struct CommandResponse<T> {
pub result: EppResult, pub result: EppResult,
#[serde(rename = "resData")]
pub res_data: Option<T>,
#[serde(rename = "trID")] #[serde(rename = "trID")]
pub tr_ids: ResponseTRID, pub tr_ids: ResponseTRID,
} }
impl ElementName for Greeting {
fn element_name(&self) -> &'static str {
"greeting"
}
}
impl<T> ElementName for CommandResponse<T> {
fn element_name(&self) -> &'static str {
"command"
}
}

View File

@ -0,0 +1,38 @@
use serde::{Deserialize, Serialize};
use crate::epp::object::{EppObject, StringValue};
use crate::epp::response::CommandResponse;
pub type EppDomainCheckResponse = EppObject<CommandResponse<DomainCheckResult>>;
#[derive(Serialize, Deserialize, Debug)]
pub enum Availability {
Unavailable,
Available,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DomainCheck {
#[serde(rename = "$value")]
pub name: StringValue,
#[serde(rename = "avail")]
pub avail: u16,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DomainCheckDataItem {
pub name: DomainCheck,
pub reason: Option<StringValue>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DomainCheckData {
#[serde(rename = "cd")]
pub domain_list: Vec<DomainCheckDataItem>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DomainCheckResult {
#[serde(rename = "chkData")]
pub check_data: DomainCheckData,
}