diff --git a/epp-client-macros/src/lib.rs b/epp-client-macros/src/lib.rs index e97daf5..4d17c6b 100644 --- a/epp-client-macros/src/lib.rs +++ b/epp-client-macros/src/lib.rs @@ -51,59 +51,3 @@ 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/src/connection/client.rs b/epp-client/src/connection/client.rs index 90b307e..4227a5c 100644 --- a/epp-client/src/connection/client.rs +++ b/epp-client/src/connection/client.rs @@ -160,21 +160,14 @@ impl EppClient { pub async fn transact_new( &mut self, - request: &T, - ) -> Result<::Output, error::Error> { - let epp_xml = request.serialize_request()?; + request: T, + ) -> Result<<::Response as EppXml>::Output, error::Error> { + let epp_xml = request.serialize()?; + println!("{}", epp_xml); 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)) - } + T::deserialize(&response) } /// Fetches the username used in the registry connection diff --git a/epp-client/src/domain/check.rs b/epp-client/src/domain/check.rs index 3fb5819..438ca94 100644 --- a/epp-client/src/domain/check.rs +++ b/epp-client/src/domain/check.rs @@ -1,17 +1,116 @@ use epp_client_macros::*; -use crate::epp::request::domain::check::EppDomainCheck; -use crate::epp::request::EppRequest; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::fmt::Debug; + +use crate::epp::object::{ElementName, EppObject, Extension, StringValue}; +use crate::epp::request::{CommandWithExtension, EppRequest}; use crate::epp::response::domain::check::EppDomainCheckResponse; -use crate::epp::xml::EppXml; +use crate::epp::xml::EPP_DOMAIN_XMLNS; -#[derive(EppRequest, Debug)] -#[response(EppDomainCheckResponse)] -pub struct Request(EppDomainCheck); +type EppDomainCheck = EppObject>; -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)) +/// Type that represents the <epp> request for domain <check> command +/// +/// ## Usage +/// +/// ```no_run +/// use std::collections::HashMap; +/// +/// use epp_client::config::{EppClientConfig, EppClientConnection}; +/// use epp_client::EppClient; +/// use epp_client::epp::object::NoExtension; +/// use epp_client::domain; +/// use epp_client::epp::generate_client_tr_id; +/// +/// #[tokio::main] +/// async fn main() { +/// // Create a config +/// let mut registry: HashMap = HashMap::new(); +/// registry.insert( +/// "registry_name".to_owned(), +/// EppClientConnection { +/// host: "example.com".to_owned(), +/// port: 700, +/// username: "username".to_owned(), +/// password: "password".to_owned(), +/// ext_uris: None, +/// tls_files: None, +/// }, +/// ); +/// let config = EppClientConfig { registry }; +/// +/// // Create an instance of EppClient, passing the config and the registry you want to connect to +/// let mut client = match EppClient::new(&config, "registry_name").await { +/// Ok(client) => client, +/// Err(e) => panic!("Failed to create EppClient: {}", e) +/// }; +/// +/// // Create an epp_client::domain::check::Request instance +/// let domain_check = domain::check::Request::::new( +/// vec!["eppdev.com", "eppdev.net"], +/// None, +/// generate_client_tr_id(&client).as_str(), +/// ); +/// +/// // send it to the registry and receive a response of type EppDomainCheckResponse +/// let response = client.transact_new(domain_check).await.unwrap(); +/// +/// println!("{:?}", response); +/// +/// client.logout().await.unwrap(); +/// } +/// ``` +#[derive(Debug)] +pub struct Request(EppDomainCheck); + +impl EppRequest for Request { + type Request = EppDomainCheck; + type Response = EppDomainCheckResponse; + + fn as_epp_object(&self) -> &Self::Request { + &self.0 + } +} + +#[derive(Serialize, Deserialize, Debug, ElementName)] +#[element_name(name = "check")] +/// Type for EPP XML <check> command for domains +pub struct DomainCheck { + /// The object holding the list of domains to be checked + #[serde(rename = "domain:check", alias = "check")] + list: DomainList, +} + +/// Type for <name> elements under the domain <check> tag +#[derive(Serialize, Deserialize, Debug)] +pub struct DomainList { + #[serde(rename = "xmlns:domain", alias = "xmlns")] + /// XML namespace for domain commands + xmlns: String, + #[serde(rename = "domain:name", alias = "name")] + /// List of domains to be checked for availability + domains: Vec, +} + +impl Request { + pub fn new( + domains: impl IntoIterator>, + extension: Option, + client_tr_id: &str, + ) -> Self { + Self(EppObject::build(CommandWithExtension::<_, _> { + command: DomainCheck { + list: DomainList { + xmlns: EPP_DOMAIN_XMLNS.to_string(), + domains: domains + .into_iter() + .map(|d| d.as_ref().into()) + .collect::>(), + }, + }, + extension: extension.map(|ext| Extension { data: ext }), + client_tr_id: client_tr_id.into(), + })) } } diff --git a/epp-client/src/epp.rs b/epp-client/src/epp.rs index 63b7046..cf156ca 100644 --- a/epp-client/src/epp.rs +++ b/epp-client/src/epp.rs @@ -1,5 +1,6 @@ //! Types for EPP requests and responses +pub mod ext; pub mod object; pub mod request; pub mod response; diff --git a/epp-client/src/epp/ext.rs b/epp-client/src/epp/ext.rs new file mode 100644 index 0000000..f765556 --- /dev/null +++ b/epp-client/src/epp/ext.rs @@ -0,0 +1 @@ +pub mod namestore; diff --git a/epp-client/src/epp/ext/namestore.rs b/epp-client/src/epp/ext/namestore.rs new file mode 100644 index 0000000..66576a7 --- /dev/null +++ b/epp-client/src/epp/ext/namestore.rs @@ -0,0 +1,2 @@ +pub mod check; +pub mod xml; diff --git a/epp-client/src/epp/ext/namestore/check.rs b/epp-client/src/epp/ext/namestore/check.rs new file mode 100644 index 0000000..7d78b56 --- /dev/null +++ b/epp-client/src/epp/ext/namestore/check.rs @@ -0,0 +1,23 @@ +use epp_client_macros::*; + +use crate::epp::ext::namestore::xml::EPP_DOMAIN_NAMESTORE_EXT_XMLNS; +use crate::epp::object::{ElementName, StringValue}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, ElementName)] +#[element_name(name = "namestoreExt:namestoreExt")] +pub struct NamestoreCheck { + #[serde(rename = "xmlns:namestoreExt", alias = "xmlns")] + xmlns: String, + #[serde(rename = "namestoreExt:subProduct", alias = "subProduct")] + sub_product: StringValue, +} + +impl NamestoreCheck { + pub fn new(sub_product: &str) -> Self { + Self { + xmlns: EPP_DOMAIN_NAMESTORE_EXT_XMLNS.into(), + sub_product: sub_product.into(), + } + } +} diff --git a/epp-client/src/epp/ext/namestore/xml.rs b/epp-client/src/epp/ext/namestore/xml.rs new file mode 100644 index 0000000..cac5fd1 --- /dev/null +++ b/epp-client/src/epp/ext/namestore/xml.rs @@ -0,0 +1 @@ +pub const EPP_DOMAIN_NAMESTORE_EXT_XMLNS: &str = "http://www.verisign-grs.com/epp/namestoreExt-1.1"; diff --git a/epp-client/src/epp/object.rs b/epp-client/src/epp/object.rs index 477c9c5..7c43565 100644 --- a/epp-client/src/epp/object.rs +++ b/epp-client/src/epp/object.rs @@ -39,7 +39,7 @@ pub trait ElementName { #[derive(Serialize, Deserialize, Debug, PartialEq, ElementName)] #[element_name(name = "empty")] /// An empty placeholder tag. To be refactored to something more compliant later. -pub struct EmptyTag; +pub struct NoExtension; /// An EPP XML Document that is used either as an EPP XML request or /// an EPP XML response diff --git a/epp-client/src/epp/request.rs b/epp-client/src/epp/request.rs index e8c1bd9..2a75677 100644 --- a/epp-client/src/epp/request.rs +++ b/epp-client/src/epp/request.rs @@ -13,27 +13,45 @@ use std::fmt::Debug; use std::time::SystemTime; use crate::epp::object::{ - ElementName, EmptyTag, EppObject, Extension, Options, ServiceExtension, Services, StringValue, + ElementName, EppObject, Extension, NoExtension, Options, ServiceExtension, Services, + StringValue, }; +use crate::epp::response::{CommandResponseStatus, EppCommandResponse}; 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 +/// Trait to handle serialization and de serialization to required request and response types pub trait EppRequest { - type Output: EppXml + Debug; + type Request: EppXml + Debug; + type Response: EppXml + Serialize + Debug; - fn deserialize_response( - &self, + fn as_epp_object(&self) -> &Self::Request; + + fn serialize(&self) -> Result> { + self.as_epp_object().serialize() + } + + fn deserialize( epp_xml: &str, - ) -> Result>; + ) -> Result<::Output, crate::error::Error> { + let status = ::deserialize(epp_xml)?; - fn serialize_request(&self) -> Result>; + match status.data.result.code { + 0..=2000 => Self::Response::deserialize(epp_xml), + _ => Err(crate::error::Error::EppCommandError(EppObject::build( + CommandResponseStatus { + result: status.data.result, + tr_ids: status.data.tr_ids, + }, + ))), + } + } } /// Type corresponding to the <command> tag in an EPP XML request /// without an <extension> tag -pub type Command = CommandWithExtension; +pub type Command = CommandWithExtension; /// The EPP Hello request pub type EppHello = EppObject; diff --git a/epp-client/src/epp/response.rs b/epp-client/src/epp/response.rs index 25bde4a..378b200 100644 --- a/epp-client/src/epp/response.rs +++ b/epp-client/src/epp/response.rs @@ -10,11 +10,12 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::fmt::Debug; use crate::epp::object::{ - ElementName, EmptyTag, EppObject, Extension, Options, ServiceExtension, Services, StringValue, + ElementName, EppObject, Extension, NoExtension, Options, ServiceExtension, Services, + StringValue, }; /// Type corresponding to the <response> tag in an EPP response without an <extension> section -pub type CommandResponse = CommandResponseWithExtension; +pub type CommandResponse = CommandResponseWithExtension; /// The EPP Greeting that is received on a successful connection and in response to an EPP hello pub type EppGreeting = EppObject; diff --git a/epp-client/src/epp/response/domain/rgp/request.rs b/epp-client/src/epp/response/domain/rgp/request.rs index 3582897..e888b30 100644 --- a/epp-client/src/epp/response/domain/rgp/request.rs +++ b/epp-client/src/epp/response/domain/rgp/request.rs @@ -1,12 +1,12 @@ use epp_client_macros::*; -use crate::epp::object::{ElementName, EmptyTag, EppObject}; +use crate::epp::object::{ElementName, EppObject, NoExtension}; use crate::epp::response::CommandResponseWithExtension; use serde::{Deserialize, Serialize}; /// Type that represents the <epp> tag for the EPP XML rgp restore request response pub type EppDomainRgpRestoreRequestResponse = - EppObject>; + EppObject>; /// Type that represents the <rgpStatus> tag for domain rgp restore request response #[derive(Serialize, Deserialize, Debug)] diff --git a/epp-client/src/tests/mod.rs b/epp-client/src/tests/mod.rs index 9a5aa16..57d9bed 100644 --- a/epp-client/src/tests/mod.rs +++ b/epp-client/src/tests/mod.rs @@ -1,7 +1,6 @@ //! 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 8850584..2896176 100644 --- a/epp-client/src/tests/se.rs +++ b/epp-client/src/tests/se.rs @@ -4,11 +4,12 @@ mod request { use super::super::get_xml; use super::super::CLTRID; use crate::domain; + use crate::epp::ext::namestore::check::NamestoreCheck; use crate::epp::object::data::{ Address, ContactStatus, DomainAuthInfo, DomainContact, DomainStatus, HostAddr, HostAttr, HostStatus, Phone, PostalInfo, }; - use crate::epp::object::StringValueTrait; + use crate::epp::object::NoExtension; use crate::epp::request::{EppHello, EppLogin, EppLogout, EppRequest}; use crate::epp::xml::EppXml; use crate::epp::*; @@ -145,9 +146,30 @@ mod request { 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 object = domain::check::Request::::new( + vec!["eppdev.com", "eppdev.net"], + None, + CLTRID, + ); - let serialized = object.serialize_request().unwrap(); + let serialized = object.serialize().unwrap(); + + assert_eq!(xml, serialized); + } + + #[test] + fn domain_check_namestore() { + let xml = get_xml("request/domain/ext/namestore/check.xml").unwrap(); + + let ext = NamestoreCheck::new("dotCC"); + + let object = domain::check::Request::::new( + vec!["eppdev.com", "eppdev.net"], + Some(ext), + CLTRID, + ); + + let serialized = object.serialize().unwrap(); assert_eq!(xml, serialized); } diff --git a/epp-client/test/resources/request/domain/ext/namestore/check.xml b/epp-client/test/resources/request/domain/ext/namestore/check.xml new file mode 100644 index 0000000..669a757 --- /dev/null +++ b/epp-client/test/resources/request/domain/ext/namestore/check.xml @@ -0,0 +1,17 @@ + + + + + + eppdev.com + eppdev.net + + + + + dotCC + + + cltrid:1626454866 + + \ No newline at end of file diff --git a/epp-client/src/tests/int.rs b/epp-client/tests/integration_test.rs similarity index 60% rename from epp-client/src/tests/int.rs rename to epp-client/tests/integration_test.rs index 387d5fe..d3af059 100644 --- a/epp-client/src/tests/int.rs +++ b/epp-client/tests/integration_test.rs @@ -1,11 +1,12 @@ -use super::CLTRID; -use super::RESOURCES_DIR; -use crate::config::EppClientConfig; -use crate::domain; -use crate::EppClient; +use epp_client::config::EppClientConfig; +use epp_client::domain; +use epp_client::epp::object::NoExtension; +use epp_client::EppClient; use std::fs; use std::path::Path; -use toml; + +const RESOURCES_DIR: &str = "./test/resources"; +const CLTRID: &str = "cltrid:1626454866"; #[tokio::test] #[ignore] @@ -22,9 +23,11 @@ async fn domain_check() { }; let domains = vec!["eppdev.com", "eppdev.net"]; - let request = domain::check::Request::new(domains, CLTRID); + let request = domain::check::Request::::new(domains, None, CLTRID); - client.transact_new(&request).await.unwrap(); + let response = client.transact_new(request).await.unwrap(); + + println!("{:?}", response); client.logout().await.unwrap(); }