diff --git a/.gitignore b/.gitignore index 73e23a9..806b35e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /config /epp-client/examples Cargo.lock +**/secrets diff --git a/epp-client-macros/src/lib.rs b/epp-client-macros/src/lib.rs index 4d17c6b..e97daf5 100644 --- a/epp-client-macros/src/lib.rs +++ b/epp-client-macros/src/lib.rs @@ -51,3 +51,59 @@ pub fn element_name_derive(input: TokenStream) -> TokenStream { element_name_macro(&ast) } + +fn epp_request_macro(ast: &syn::DeriveInput) -> proc_macro::TokenStream { + let name = &ast.ident; + let attr = &ast.attrs[0]; + let mut response_type: Option = None; + let (impl_generics, type_generics, _) = &ast.generics.split_for_impl(); + + if attr.path.is_ident("response") { + match attr.parse_meta() { + Ok(syn::Meta::List(meta)) => { + let item = &meta.nested[0]; + match item { + syn::NestedMeta::Meta(syn::Meta::Path(p)) => { + response_type = Some(p.get_ident().unwrap().clone()); + } + _ => panic!("Failed to parse args for epp_types"), + } + } + _ => panic!("Failed to parse args for epp_types"), + }; + } + + if let Some(resp) = response_type { + let implement = quote::quote! { + impl #impl_generics EppRequest for #name #type_generics { + type Output = #resp; + + fn deserialize_response(&self, epp_xml: &str) -> Result> { + match Self::Output::deserialize(epp_xml) { + Ok(v) => Ok(v), + Err(e) => Err(format!("epp-client: Deserialization error: {}", e).into()), + } + } + + fn serialize_request(&self) -> Result> { + match &self.0.serialize() { + Ok(serialized) => Ok(serialized.to_string()), + Err(e) => Err(format!("epp-client: Serialization error: {}", e).into()), + } + } + } + }; + implement.into() + } else { + panic!( + "response() needs 1 argument, a response type that implements epp_client::epp::xml::EppXml" + ); + } +} + +#[proc_macro_derive(EppRequest, attributes(response))] +pub fn epp_request_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse(input).expect("Error while parsing EppTransaction macro input"); + + epp_request_macro(&ast) +} diff --git a/epp-client/Cargo.toml b/epp-client/Cargo.toml index d113b7e..fa1715d 100644 --- a/epp-client/Cargo.toml +++ b/epp-client/Cargo.toml @@ -26,3 +26,4 @@ webpki-roots = "0.22.1" [dev-dependencies] tokio-test = "0.4" regex = "1.5" +toml = "0.5" diff --git a/epp-client/src/connection/client.rs b/epp-client/src/connection/client.rs index 8cf3bb9..90b307e 100644 --- a/epp-client/src/connection/client.rs +++ b/epp-client/src/connection/client.rs @@ -51,7 +51,7 @@ use std::{error::Error, fmt::Debug}; use crate::config::EppClientConfig; use crate::connection::registry::{epp_connect, EppConnection}; -use crate::epp::request::{generate_client_tr_id, EppHello, EppLogin, EppLogout}; +use crate::epp::request::{generate_client_tr_id, EppHello, EppLogin, EppLogout, EppRequest}; use crate::epp::response::{ EppCommandResponse, EppCommandResponseError, EppGreeting, EppLoginResponse, EppLogoutResponse, }; @@ -158,6 +158,25 @@ impl EppClient { } } + pub async fn transact_new( + &mut self, + request: &T, + ) -> Result<::Output, error::Error> { + let epp_xml = request.serialize_request()?; + + let response = self.connection.transact(&epp_xml).await?; + + let status = EppCommandResponse::deserialize(&response)?; + + if status.data.result.code < 2000 { + let response = request.deserialize_response(&response)?; + Ok(response) + } else { + let epp_error = EppCommandResponseError::deserialize(&response)?; + Err(error::Error::EppCommandError(epp_error)) + } + } + /// Fetches the username used in the registry connection pub fn username(&self) -> String { self.credentials.0.to_string() diff --git a/epp-client/src/domain.rs b/epp-client/src/domain.rs new file mode 100644 index 0000000..3e8ff0f --- /dev/null +++ b/epp-client/src/domain.rs @@ -0,0 +1 @@ +pub mod check; diff --git a/epp-client/src/domain/check.rs b/epp-client/src/domain/check.rs new file mode 100644 index 0000000..3fb5819 --- /dev/null +++ b/epp-client/src/domain/check.rs @@ -0,0 +1,17 @@ +use epp_client_macros::*; + +use crate::epp::request::domain::check::EppDomainCheck; +use crate::epp::request::EppRequest; +use crate::epp::response::domain::check::EppDomainCheckResponse; +use crate::epp::xml::EppXml; + +#[derive(EppRequest, Debug)] +#[response(EppDomainCheckResponse)] +pub struct Request(EppDomainCheck); + +impl Request { + /// Creates a new Request for a domain check + pub fn new(domains: Vec<&str>, client_tr_id: &str) -> Request { + Request(EppDomainCheck::new(domains, client_tr_id)) + } +} diff --git a/epp-client/src/epp/request.rs b/epp-client/src/epp/request.rs index f819c32..e8c1bd9 100644 --- a/epp-client/src/epp/request.rs +++ b/epp-client/src/epp/request.rs @@ -5,15 +5,31 @@ pub mod domain; pub mod host; pub mod message; +use epp_client_macros::*; + use serde::{ser::SerializeStruct, ser::Serializer, Deserialize, Serialize}; use std::error::Error; +use std::fmt::Debug; use std::time::SystemTime; use crate::epp::object::{ ElementName, EmptyTag, EppObject, Extension, Options, ServiceExtension, Services, StringValue, }; -use crate::epp::xml::{EPP_CONTACT_XMLNS, EPP_DOMAIN_XMLNS, EPP_HOST_XMLNS, EPP_LANG, EPP_VERSION}; -use epp_client_macros::*; +use crate::epp::xml::{ + EppXml, EPP_CONTACT_XMLNS, EPP_DOMAIN_XMLNS, EPP_HOST_XMLNS, EPP_LANG, EPP_VERSION, +}; + +/// Trait to set correct value for xml tags when tags are being generated from generic types +pub trait EppRequest { + type Output: EppXml + Debug; + + fn deserialize_response( + &self, + epp_xml: &str, + ) -> Result>; + + fn serialize_request(&self) -> Result>; +} /// Type corresponding to the <command> tag in an EPP XML request /// without an <extension> tag diff --git a/epp-client/src/lib.rs b/epp-client/src/lib.rs index 39e64be..1344e55 100644 --- a/epp-client/src/lib.rs +++ b/epp-client/src/lib.rs @@ -102,6 +102,7 @@ extern crate log; pub mod config; pub mod connection; +pub mod domain; pub mod epp; pub mod error; pub use connection::client::EppClient; diff --git a/epp-client/src/tests/int.rs b/epp-client/src/tests/int.rs new file mode 100644 index 0000000..387d5fe --- /dev/null +++ b/epp-client/src/tests/int.rs @@ -0,0 +1,30 @@ +use super::CLTRID; +use super::RESOURCES_DIR; +use crate::config::EppClientConfig; +use crate::domain; +use crate::EppClient; +use std::fs; +use std::path::Path; +use toml; + +#[tokio::test] +#[ignore] +async fn domain_check() { + let config_file = format!("{}/{}", RESOURCES_DIR, "secrets/epp-client.toml"); + let config_path = Path::new(config_file.as_str()); + let contents = &fs::read_to_string(config_path).unwrap(); + + let config: EppClientConfig = toml::from_str(contents).unwrap(); + + let mut client = match EppClient::new(&config, "testing").await { + Ok(client) => client, + Err(e) => panic!("Failed to create EppClient: {}", e), + }; + + let domains = vec!["eppdev.com", "eppdev.net"]; + let request = domain::check::Request::new(domains, CLTRID); + + client.transact_new(&request).await.unwrap(); + + client.logout().await.unwrap(); +} diff --git a/epp-client/src/tests/mod.rs b/epp-client/src/tests/mod.rs index 57d9bed..9a5aa16 100644 --- a/epp-client/src/tests/mod.rs +++ b/epp-client/src/tests/mod.rs @@ -1,6 +1,7 @@ //! Module for automated tests pub mod de; +pub mod int; pub mod se; use regex::Regex; diff --git a/epp-client/src/tests/se.rs b/epp-client/src/tests/se.rs index 3be9da9..8850584 100644 --- a/epp-client/src/tests/se.rs +++ b/epp-client/src/tests/se.rs @@ -3,11 +3,13 @@ mod request { use super::super::get_xml; use super::super::CLTRID; + use crate::domain; use crate::epp::object::data::{ Address, ContactStatus, DomainAuthInfo, DomainContact, DomainStatus, HostAddr, HostAttr, HostStatus, Phone, PostalInfo, }; - use crate::epp::request::{EppHello, EppLogin, EppLogout}; + use crate::epp::object::StringValueTrait; + use crate::epp::request::{EppHello, EppLogin, EppLogout, EppRequest}; use crate::epp::xml::EppXml; use crate::epp::*; use chrono::{DateTime, NaiveDate}; @@ -139,6 +141,17 @@ mod request { assert_eq!(xml, serialized); } + #[test] + fn wrapped_domain_check() { + let xml = get_xml("request/domain/check.xml").unwrap(); + + let object = domain::check::Request::new(vec!["eppdev.com", "eppdev.net"], CLTRID); + + let serialized = object.serialize_request().unwrap(); + + assert_eq!(xml, serialized); + } + #[test] fn domain_create() { let xml = get_xml("request/domain/create.xml").unwrap();