added macro for serializer element name for generic objects

This commit is contained in:
Ritesh Chitlangi 2021-07-22 18:35:20 +08:00
parent 7851602ef5
commit fd72c80023
29 changed files with 383 additions and 119 deletions

5
.gitignore vendored
View File

@ -1,3 +1,4 @@
/target /target
/misc **/target
Cargo.lock /epp-client/misc
Cargo.lock

View File

@ -1,25 +1,6 @@
[package] [workspace]
name = "epp-client"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html members = [
'epp-client-macros',
[[example]] 'epp-client'
name = "example-client" ]
path = "examples/client.rs"
[dependencies]
bytes = "1"
confy = "0.4"
futures = "0.3"
lazy_static = "1.4"
quick-xml = { version = "0.22", features = [ "serialize" ] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = [ "full" ] }
tokio-rustls = "0.22"
webpki = "0.22"
webpki-roots = "0.21"
[dev-dependencies]
tokio-test = "*"

2
epp-client-macros/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

View File

@ -0,0 +1,13 @@
[package]
name = "epp-client-macros"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = { version = "1", features = ["full", "fold"] }
quote = "1"

View File

@ -0,0 +1,86 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
fn element_name_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let mut elem_name = ast.ident.to_string();
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
if ast.attrs.len() > 0 {
let attribute = &ast.attrs[0];
let meta = match attribute.parse_meta() {
Ok(syn::Meta::List(meta)) => {
if meta.nested.len() > 0 {
elem_name = match &meta.nested[0] {
syn::NestedMeta::Meta(syn::Meta::NameValue(v)) => match &v.lit {
syn::Lit::Str(lit) => lit.value(),
_ => panic!("Invalid element_name attribute"),
},
_ => panic!("Invalid element_name attribute"),
};
} else {
panic!("Invalid element_name attribute");
}
}
_ => panic!("Invalid element_name attribute"),
};
}
let implement = quote! {
impl #impl_generics ElementName for #name #type_generics {
fn element_name(&self) -> &'static str {
#elem_name
}
}
};
implement.into()
}
#[proc_macro_derive(ElementName, attributes(element_name))]
pub fn element_name_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).expect("Error while parsing ElementName macro input");
element_name_macro(&ast)
}
// #[proc_macro_attribute]
// pub fn epp_client_command_response(_metadat: TokenStream, input: TokenStream) -> TokenStream {
// let mut ast = parse_macro_input!(input as DeriveInput);
// match &mut ast.data {
// syn::Data::Struct(ref mut data) => {
// match &mut data.fields {
// syn::Fields::Named(fields) => {
// fields.named.push(
// syn::Field::parse_named
// .parse2(quote! {
// pub result: EppResult
// })
// .unwrap()
// );
// fields.named.push(
// syn::Field::parse_named
// .parse2(quote! {
// pub tr_ids: ResponseTRID
// })
// .unwrap()
// );
// }
// _ => (),
// }
// return quote! { #ast }.into();
// }
// _ => panic!("Failed to parse CommandResponse macro input"),
// }
// }
// #[cfg(test)]
// mod tests {
// #[test]
// fn it_works() {
// assert_eq!(2 + 2, 4);
// }
// }

26
epp-client/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "epp-client"
version = "0.1.0"
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]
epp-client-macros = { path = "../epp-client-macros" }
bytes = "1"
confy = "0.4"
futures = "0.3"
lazy_static = "1.4"
quick-xml = { version = "0.22", features = [ "serialize" ] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = [ "full" ] }
tokio-rustls = "0.22"
webpki = "0.22"
webpki-roots = "0.21"
[dev-dependencies]
tokio-test = "*"

View File

@ -0,0 +1,57 @@
use epp_client::{epp::request::generate_client_tr_id, connection::EppClient, connection, epp::xml::EppXml, epp::response::EppGreeting};
use epp_client::epp::request::domain::check::EppDomainCheck;
use epp_client::epp::response::domain::check::EppDomainCheckResponse;
use epp_client::epp::request::contact::check::EppContactCheck;
use epp_client::epp::response::contact::check::EppContactCheckResponse;
use epp_client::epp::object::data::{PostalInfo, Address, Phone};
use epp_client::epp::request::contact::create::EppContactCreate;
async fn check_domains(client: &mut EppClient) {
let domains = vec!["eppdev.com", "hexonet.net"];
let domain_check = EppDomainCheck::new(domains, generate_client_tr_id("eppdev").unwrap().as_str());
client.transact::<EppDomainCheck, EppDomainCheckResponse>(&domain_check).await.unwrap();
}
async fn check_contacts(client: &mut EppClient) {
let contacts = vec!["eppdev-contact-1", "eppdev-contact-2"];
let contact_check = EppContactCheck::new(contacts, generate_client_tr_id("eppdev").unwrap().as_str());
client.transact::<_, EppContactCheckResponse>(&contact_check).await.unwrap();
}
async fn create_contact() {
let street = vec!["58", "Orchid Road"];
let address = Address::new(street, "Paris", "Paris", "392374", "FR");
let postal_info = PostalInfo::new("int", "John Doe", "Acme Widgets", address);
let mut voice = Phone::new("+47.47237942");
voice.set_extension("123");
let mut fax = Phone::new("+47.86698799");
fax.set_extension("677");
let mut contact_create = EppContactCreate::new("eppdev-contact-1", "contact@eppdev.net", postal_info, voice, "eppdev-387323", generate_client_tr_id("eppdev").unwrap().as_str());
contact_create.set_fax(fax);
println!("xml: {}", contact_create.serialize().unwrap());
//client.transact::<EppContactCheck, EppContactCheckResponse>(&contact_check).await.unwrap();
}
#[tokio::main]
async fn main() {
let mut client = match connection::connect("hexonet").await {
Ok(client) => {
let greeting = client.greeting();
let greeting_object = EppGreeting::deserialize(&greeting).unwrap();
println!("{:?}", greeting_object);
client
},
Err(e) => panic!("Error: {}", e)
};
// check_domains(&mut client).await;
check_contacts(&mut client).await;
// create_contact().await;
}

View File

@ -10,7 +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::request::{generate_client_tr_id, Login, EppLogin, Logout, EppLogout}; use crate::epp::request::{generate_client_tr_id, EppLogin, EppLogout};
use crate::epp::response::EppCommandResponse; use crate::epp::response::EppCommandResponse;
use crate::epp::xml::EppXml; use crate::epp::xml::EppXml;
@ -160,6 +160,8 @@ impl EppClient {
println!("Response:\r\n{}", response); println!("Response:\r\n{}", response);
// let result_object = EppCommandResponse::deserialize(&response);
let response_obj = E::deserialize(&response)?; let response_obj = E::deserialize(&response)?;
println!("Response:\r\n{:?}", response_obj); println!("Response:\r\n{:?}", response_obj);

View File

@ -4,4 +4,3 @@ pub mod quick_xml;
pub mod request; pub mod request;
pub mod response; pub mod response;
pub mod xml; pub mod xml;
pub use request::domain;

View File

@ -1,3 +1,5 @@
pub mod data;
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer}; 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};

View File

@ -0,0 +1,94 @@
use crate::epp::object::{StringValue, StringValueTrait};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct Phone {
#[serde(rename = "$value")]
pub number: String,
#[serde(rename = "x")]
extension: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Address {
street: Vec<StringValue>,
city: StringValue,
#[serde(rename = "sp")]
province: StringValue,
#[serde(rename = "pc")]
postal_code: StringValue,
#[serde(rename = "cc")]
country_code: StringValue,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PostalInfo {
#[serde(rename = "type")]
info_type: String,
name: StringValue,
#[serde(rename = "org")]
organization: StringValue,
#[serde(rename = "addr")]
address: Address,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthInfo {
#[serde(rename = "pw")]
password: StringValue,
}
impl Phone {
pub fn new(number: &str) -> Phone {
Phone {
extension: None,
number: number.to_string(),
}
}
pub fn set_extension(&mut self, ext: &str) {
self.extension = Some(ext.to_string());
}
}
impl AuthInfo {
pub fn new(password: &str) -> AuthInfo {
AuthInfo {
password: password.to_string_value(),
}
}
}
impl Address {
pub fn new(
street: Vec<&str>,
city: &str,
province: &str,
postal_code: &str,
country_code: &str,
) -> Address {
let street = street
.iter()
.filter_map(|s| Some(s.to_string_value()))
.collect::<Vec<StringValue>>();
Address {
street: street,
city: city.to_string_value(),
province: province.to_string_value(),
postal_code: postal_code.to_string_value(),
country_code: country_code.to_string_value(),
}
}
}
impl PostalInfo {
pub fn new(info_type: &str, name: &str, organization: &str, address: Address) -> PostalInfo {
PostalInfo {
info_type: info_type.to_string(),
name: name.to_string_value(),
organization: organization.to_string_value(),
address: address,
}
}
}

View File

@ -5,11 +5,12 @@ 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::command::Command;
use crate::epp::object::{ use crate::epp::object::{
ElementName, EppObject, Options, ServiceExtension, Services, StringValue, StringValueTrait, ElementName, EppObject, Options, ServiceExtension, Services, StringValue, StringValueTrait,
}; };
use crate::epp::xml::{EPP_LANG, EPP_VERSION}; use crate::epp::xml::{EPP_LANG, EPP_VERSION};
use epp_client_macros::*;
pub type EppHello = EppObject<Hello>; pub type EppHello = EppObject<Hello>;
pub type EppLogin = EppObject<Command<Login>>; pub type EppLogin = EppObject<Command<Login>>;
@ -20,24 +21,20 @@ pub fn generate_client_tr_id(username: &str) -> Result<String, Box<dyn Error>> {
Ok(format!("{}:{}", username, timestamp.as_secs())) Ok(format!("{}:{}", username, timestamp.as_secs()))
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq, ElementName)]
#[serde(rename = "hello")] #[serde(rename = "hello")]
#[element_name(name = "hello")]
pub struct Hello; pub struct Hello;
impl ElementName for Hello {
fn element_name(&self) -> &'static str {
"hello"
}
}
impl EppHello { impl EppHello {
pub fn new() -> EppHello { pub fn new() -> EppHello {
EppObject::build(Hello {}) EppObject::build(Hello {})
} }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq, ElementName)]
#[serde(rename = "login")] #[serde(rename = "login")]
#[element_name(name = "login")]
pub struct Login { pub struct Login {
#[serde(rename(serialize = "clID", deserialize = "clID"))] #[serde(rename(serialize = "clID", deserialize = "clID"))]
username: StringValue, username: StringValue,
@ -48,12 +45,6 @@ pub struct Login {
services: Services, services: Services,
} }
impl ElementName for Login {
fn element_name(&self) -> &'static str {
"login"
}
}
impl EppLogin { impl EppLogin {
pub fn new(username: &str, password: &str, client_tr_id: &str) -> EppLogin { pub fn new(username: &str, password: &str, client_tr_id: &str) -> EppLogin {
let login = Login { let login = Login {
@ -92,8 +83,8 @@ impl EppLogin {
} }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq, ElementName)]
#[serde(rename_all = "lowercase")] #[element_name(name = "logout")]
pub struct Logout; pub struct Logout;
impl EppLogout { impl EppLogout {
@ -104,9 +95,3 @@ impl EppLogout {
}) })
} }
} }
impl ElementName for Logout {
fn element_name(&self) -> &'static str {
"logout"
}
}

View File

@ -0,0 +1,2 @@
pub mod check;
pub mod create;

View File

@ -1,9 +1,10 @@
use epp_client_macros::*;
use crate::epp::command::Command; use crate::epp::command::Command;
use crate::epp::object::{ElementName, EppObject, StringValue, StringValueTrait}; use crate::epp::object::{ElementName, EppObject, StringValue, StringValueTrait};
use crate::epp::xml::EPP_CONTACT_XMLNS;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
const EPP_CONTACT_XMLNS: &str = "urn:ietf:params:xml:ns:contact-1.0";
pub type EppContactCheck = EppObject<Command<ContactCheck>>; pub type EppContactCheck = EppObject<Command<ContactCheck>>;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -13,18 +14,13 @@ pub struct ContactList {
pub contact_ids: Vec<StringValue>, pub contact_ids: Vec<StringValue>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, ElementName)]
#[element_name(name = "check")]
pub struct ContactCheck { pub struct ContactCheck {
#[serde(rename = "check")] #[serde(rename = "check")]
list: ContactList, list: ContactList,
} }
impl ElementName for ContactCheck {
fn element_name(&self) -> &'static str {
"check"
}
}
impl EppContactCheck { impl EppContactCheck {
pub fn new(contact_ids: Vec<&str>, client_tr_id: &str) -> EppContactCheck { pub fn new(contact_ids: Vec<&str>, client_tr_id: &str) -> EppContactCheck {
let contact_ids = contact_ids let contact_ids = contact_ids

View File

@ -0,0 +1,61 @@
use epp_client_macros::*;
use crate::epp::command::Command;
use crate::epp::object::data;
use crate::epp::object::{ElementName, EppObject, StringValue, StringValueTrait};
use crate::epp::xml::EPP_CONTACT_XMLNS;
use serde::{Deserialize, Serialize};
pub type EppContactCreate = EppObject<Command<ContactCreate>>;
#[derive(Serialize, Deserialize, Debug)]
pub struct Contact {
xmlns: String,
id: StringValue,
#[serde(rename = "postalInfo")]
postal_info: data::PostalInfo,
voice: data::Phone,
fax: Option<data::Phone>,
email: StringValue,
#[serde(rename = "authInfo")]
auth_info: data::AuthInfo,
}
#[derive(Serialize, Deserialize, Debug, ElementName)]
#[element_name(name = "create")]
pub struct ContactCreate {
#[serde(rename = "create")]
pub contact: Contact,
}
impl EppContactCreate {
pub fn new(
id: &str,
email: &str,
postal_info: data::PostalInfo,
voice: data::Phone,
auth_password: &str,
client_tr_id: &str,
) -> EppContactCreate {
let contact_create = ContactCreate {
contact: Contact {
xmlns: EPP_CONTACT_XMLNS.to_string(),
id: id.to_string_value(),
postal_info: postal_info,
voice: voice,
fax: None,
email: email.to_string_value(),
auth_info: data::AuthInfo::new(auth_password),
},
};
EppObject::build(Command::<ContactCreate> {
command: contact_create,
client_tr_id: client_tr_id.to_string_value(),
})
}
pub fn set_fax(&mut self, fax: data::Phone) {
self.data.command.contact.fax = Some(fax);
}
}

View File

@ -0,0 +1 @@
pub mod check;

View File

@ -1,9 +1,10 @@
use epp_client_macros::*;
use crate::epp::command::Command; use crate::epp::command::Command;
use crate::epp::object::{ElementName, EppObject, StringValue, StringValueTrait}; use crate::epp::object::{ElementName, EppObject, StringValue, StringValueTrait};
use crate::epp::xml::EPP_DOMAIN_XMLNS;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
const EPP_DOMAIN_XMLNS: &str = "urn:ietf:params:xml:ns:domain-1.0";
pub type EppDomainCheck = EppObject<Command<DomainCheck>>; pub type EppDomainCheck = EppObject<Command<DomainCheck>>;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -13,18 +14,13 @@ pub struct DomainList {
pub domains: Vec<StringValue>, pub domains: Vec<StringValue>,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, ElementName)]
#[element_name(name = "check")]
pub struct DomainCheck { pub struct DomainCheck {
#[serde(rename = "check")] #[serde(rename = "check")]
list: DomainList, list: DomainList,
} }
impl ElementName for DomainCheck {
fn element_name(&self) -> &'static str {
"check"
}
}
impl EppDomainCheck { impl EppDomainCheck {
pub fn new(domains: Vec<&str>, client_tr_id: &str) -> EppDomainCheck { pub fn new(domains: Vec<&str>, client_tr_id: &str) -> EppDomainCheck {
let domains = domains let domains = domains

View File

@ -1,6 +1,7 @@
pub mod contact; pub mod contact;
pub mod domain; pub mod domain;
use epp_client_macros::*;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use crate::epp::object::{ use crate::epp::object::{
@ -101,8 +102,9 @@ pub struct Dcp {
statement: Statement, statement: Statement,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq, ElementName)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
#[element_name(name = "greeting")]
pub struct Greeting { pub struct Greeting {
#[serde(rename = "svID")] #[serde(rename = "svID")]
service_id: String, service_id: String,
@ -128,8 +130,9 @@ pub struct ResponseTRID {
pub server_tr_id: StringValue, pub server_tr_id: StringValue,
} }
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq, ElementName)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
#[element_name(name = "response")]
pub struct CommandResponse<T> { pub struct CommandResponse<T> {
pub result: EppResult, pub result: EppResult,
#[serde(rename = "resData")] #[serde(rename = "resData")]
@ -137,15 +140,3 @@ pub struct CommandResponse<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 @@
pub mod check;

View File

@ -0,0 +1 @@
pub mod check;

View File

@ -7,6 +7,9 @@ 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_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_XSI_SCHEMA_LOCATION: &str = "urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd";
pub const EPP_DOMAIN_XMLNS: &str = "urn:ietf:params:xml:ns:domain-1.0";
pub const EPP_CONTACT_XMLNS: &str = "urn:ietf:params:xml:ns:contact-1.0";
pub const EPP_VERSION: &str = "1.0"; pub const EPP_VERSION: &str = "1.0";
pub const EPP_LANG: &str = "en"; pub const EPP_LANG: &str = "en";

View File

@ -1,36 +0,0 @@
use epp_client::{epp::request::generate_client_tr_id, connection::EppClient, connection, epp::xml::EppXml, epp::response::EppGreeting};
use epp_client::epp::request::domain::EppDomainCheck;
use epp_client::epp::response::domain::EppDomainCheckResponse;
use epp_client::epp::request::contact::EppContactCheck;
use epp_client::epp::response::contact::EppContactCheckResponse;
async fn check_domains(client: &mut EppClient) {
let domains = vec!["eppdev.com", "hexonet.net"];
let domain_check = EppDomainCheck::new(domains, generate_client_tr_id("eppdev").unwrap().as_str());
client.transact::<EppDomainCheck, EppDomainCheckResponse>(&domain_check).await.unwrap();
}
async fn check_contacts(client: &mut EppClient) {
let contacts = vec!["eppdev-contact-1", "eppdev-contact-2"];
let contact_check = EppContactCheck::new(contacts, generate_client_tr_id("eppdev").unwrap().as_str());
client.transact::<EppContactCheck, EppContactCheckResponse>(&contact_check).await.unwrap();
}
#[tokio::main]
async fn main() {
let mut client = match connection::connect("hexonet").await {
Ok(client) => {
let greeting = client.greeting();
let greeting_object = EppGreeting::deserialize(&greeting).unwrap();
println!("{:?}", greeting_object);
client
},
Err(e) => panic!("Error: {}", e)
};
check_domains(&mut client).await;
check_contacts(&mut client).await;
}