new request structure with extensions

This commit is contained in:
Ritesh Chitlangi 2021-11-25 18:58:12 +08:00
parent 898f8a2a2f
commit 35f14973e3
16 changed files with 227 additions and 103 deletions

View File

@ -51,59 +51,3 @@ pub fn element_name_derive(input: TokenStream) -> TokenStream {
element_name_macro(&ast) 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<syn::Ident> = 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<Self::Output, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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)
}

View File

@ -160,21 +160,14 @@ impl EppClient {
pub async fn transact_new<T: EppRequest + Debug>( pub async fn transact_new<T: EppRequest + Debug>(
&mut self, &mut self,
request: &T, request: T,
) -> Result<<T as EppRequest>::Output, error::Error> { ) -> Result<<<T as EppRequest>::Response as EppXml>::Output, error::Error> {
let epp_xml = request.serialize_request()?; let epp_xml = request.serialize()?;
println!("{}", epp_xml);
let response = self.connection.transact(&epp_xml).await?; let response = self.connection.transact(&epp_xml).await?;
let status = EppCommandResponse::deserialize(&response)?; T::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 /// Fetches the username used in the registry connection

View File

@ -1,17 +1,116 @@
use epp_client_macros::*; use epp_client_macros::*;
use crate::epp::request::domain::check::EppDomainCheck; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::epp::request::EppRequest; 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::response::domain::check::EppDomainCheckResponse;
use crate::epp::xml::EppXml; use crate::epp::xml::EPP_DOMAIN_XMLNS;
#[derive(EppRequest, Debug)] type EppDomainCheck<E> = EppObject<CommandWithExtension<DomainCheck, E>>;
#[response(EppDomainCheckResponse)]
pub struct Request(EppDomainCheck);
impl Request { /// Type that represents the &lt;epp&gt; request for domain &lt;check&gt; command
/// Creates a new Request for a domain check ///
pub fn new(domains: Vec<&str>, client_tr_id: &str) -> Request { /// ## Usage
Request(EppDomainCheck::new(domains, client_tr_id)) ///
/// ```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<String, EppClientConnection> = 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::<NoExtension>::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<E: ElementName + Serialize + DeserializeOwned + Debug>(EppDomainCheck<E>);
impl<E: ElementName + Serialize + DeserializeOwned + Debug> EppRequest for Request<E> {
type Request = EppDomainCheck<E>;
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 &lt;check&gt; 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 &lt;name&gt; elements under the domain &lt;check&gt; 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<StringValue>,
}
impl<E: ElementName + Serialize + DeserializeOwned + Debug> Request<E> {
pub fn new(
domains: impl IntoIterator<Item = impl AsRef<str>>,
extension: Option<E>,
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::<Vec<StringValue>>(),
},
},
extension: extension.map(|ext| Extension { data: ext }),
client_tr_id: client_tr_id.into(),
}))
} }
} }

View File

@ -1,5 +1,6 @@
//! Types for EPP requests and responses //! Types for EPP requests and responses
pub mod ext;
pub mod object; pub mod object;
pub mod request; pub mod request;
pub mod response; pub mod response;

View File

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

View File

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

View File

@ -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(),
}
}
}

View File

@ -0,0 +1 @@
pub const EPP_DOMAIN_NAMESTORE_EXT_XMLNS: &str = "http://www.verisign-grs.com/epp/namestoreExt-1.1";

View File

@ -39,7 +39,7 @@ pub trait ElementName {
#[derive(Serialize, Deserialize, Debug, PartialEq, ElementName)] #[derive(Serialize, Deserialize, Debug, PartialEq, ElementName)]
#[element_name(name = "empty")] #[element_name(name = "empty")]
/// An empty placeholder tag. To be refactored to something more compliant later. /// 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 Document that is used either as an EPP XML request or
/// an EPP XML response /// an EPP XML response

View File

@ -13,27 +13,45 @@ use std::fmt::Debug;
use std::time::SystemTime; use std::time::SystemTime;
use crate::epp::object::{ 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::{ use crate::epp::xml::{
EppXml, EPP_CONTACT_XMLNS, EPP_DOMAIN_XMLNS, EPP_HOST_XMLNS, EPP_LANG, EPP_VERSION, 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 { pub trait EppRequest {
type Output: EppXml + Debug; type Request: EppXml + Debug;
type Response: EppXml + Serialize + Debug;
fn deserialize_response( fn as_epp_object(&self) -> &Self::Request;
&self,
fn serialize(&self) -> Result<String, Box<dyn Error>> {
self.as_epp_object().serialize()
}
fn deserialize(
epp_xml: &str, epp_xml: &str,
) -> Result<Self::Output, Box<dyn std::error::Error>>; ) -> Result<<Self::Response as EppXml>::Output, crate::error::Error> {
let status = <EppCommandResponse as EppXml>::deserialize(epp_xml)?;
fn serialize_request(&self) -> Result<String, Box<dyn std::error::Error>>; 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 &lt;command&gt; tag in an EPP XML request /// Type corresponding to the &lt;command&gt; tag in an EPP XML request
/// without an &lt;extension&gt; tag /// without an &lt;extension&gt; tag
pub type Command<T> = CommandWithExtension<T, EmptyTag>; pub type Command<T> = CommandWithExtension<T, NoExtension>;
/// The EPP Hello request /// The EPP Hello request
pub type EppHello = EppObject<Hello>; pub type EppHello = EppObject<Hello>;

View File

@ -10,11 +10,12 @@ use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use crate::epp::object::{ use crate::epp::object::{
ElementName, EmptyTag, EppObject, Extension, Options, ServiceExtension, Services, StringValue, ElementName, EppObject, Extension, NoExtension, Options, ServiceExtension, Services,
StringValue,
}; };
/// Type corresponding to the &lt;response&gt; tag in an EPP response without an &lt;extension&gt; section /// Type corresponding to the &lt;response&gt; tag in an EPP response without an &lt;extension&gt; section
pub type CommandResponse<T> = CommandResponseWithExtension<T, EmptyTag>; pub type CommandResponse<T> = CommandResponseWithExtension<T, NoExtension>;
/// The EPP Greeting that is received on a successful connection and in response to an EPP hello /// The EPP Greeting that is received on a successful connection and in response to an EPP hello
pub type EppGreeting = EppObject<Greeting>; pub type EppGreeting = EppObject<Greeting>;

View File

@ -1,12 +1,12 @@
use epp_client_macros::*; use epp_client_macros::*;
use crate::epp::object::{ElementName, EmptyTag, EppObject}; use crate::epp::object::{ElementName, EppObject, NoExtension};
use crate::epp::response::CommandResponseWithExtension; use crate::epp::response::CommandResponseWithExtension;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Type that represents the &lt;epp&gt; tag for the EPP XML rgp restore request response /// Type that represents the &lt;epp&gt; tag for the EPP XML rgp restore request response
pub type EppDomainRgpRestoreRequestResponse = pub type EppDomainRgpRestoreRequestResponse =
EppObject<CommandResponseWithExtension<EmptyTag, RgpRequestResult>>; EppObject<CommandResponseWithExtension<NoExtension, RgpRequestResult>>;
/// Type that represents the &lt;rgpStatus&gt; tag for domain rgp restore request response /// Type that represents the &lt;rgpStatus&gt; tag for domain rgp restore request response
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View File

@ -1,7 +1,6 @@
//! Module for automated tests //! Module for automated tests
pub mod de; pub mod de;
pub mod int;
pub mod se; pub mod se;
use regex::Regex; use regex::Regex;

View File

@ -4,11 +4,12 @@ mod request {
use super::super::get_xml; use super::super::get_xml;
use super::super::CLTRID; use super::super::CLTRID;
use crate::domain; use crate::domain;
use crate::epp::ext::namestore::check::NamestoreCheck;
use crate::epp::object::data::{ use crate::epp::object::data::{
Address, ContactStatus, DomainAuthInfo, DomainContact, DomainStatus, HostAddr, HostAttr, Address, ContactStatus, DomainAuthInfo, DomainContact, DomainStatus, HostAddr, HostAttr,
HostStatus, Phone, PostalInfo, HostStatus, Phone, PostalInfo,
}; };
use crate::epp::object::StringValueTrait; use crate::epp::object::NoExtension;
use crate::epp::request::{EppHello, EppLogin, EppLogout, EppRequest}; use crate::epp::request::{EppHello, EppLogin, EppLogout, EppRequest};
use crate::epp::xml::EppXml; use crate::epp::xml::EppXml;
use crate::epp::*; use crate::epp::*;
@ -145,9 +146,30 @@ mod request {
fn wrapped_domain_check() { fn wrapped_domain_check() {
let xml = get_xml("request/domain/check.xml").unwrap(); 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::<NoExtension>::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::<NamestoreCheck>::new(
vec!["eppdev.com", "eppdev.net"],
Some(ext),
CLTRID,
);
let serialized = object.serialize().unwrap();
assert_eq!(xml, serialized); assert_eq!(xml, serialized);
} }

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>eppdev.com</domain:name>
<domain:name>eppdev.net</domain:name>
</domain:check>
</check>
<extension>
<namestoreExt:namestoreExt xmlns:namestoreExt="http://www.verisign-grs.com/epp/namestoreExt-1.1">
<namestoreExt:subProduct>dotCC</namestoreExt:subProduct>
</namestoreExt:namestoreExt>
</extension>
<clTRID>cltrid:1626454866</clTRID>
</command>
</epp>

View File

@ -1,11 +1,12 @@
use super::CLTRID; use epp_client::config::EppClientConfig;
use super::RESOURCES_DIR; use epp_client::domain;
use crate::config::EppClientConfig; use epp_client::epp::object::NoExtension;
use crate::domain; use epp_client::EppClient;
use crate::EppClient;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use toml;
const RESOURCES_DIR: &str = "./test/resources";
const CLTRID: &str = "cltrid:1626454866";
#[tokio::test] #[tokio::test]
#[ignore] #[ignore]
@ -22,9 +23,11 @@ async fn domain_check() {
}; };
let domains = vec!["eppdev.com", "eppdev.net"]; let domains = vec!["eppdev.com", "eppdev.net"];
let request = domain::check::Request::new(domains, CLTRID); let request = domain::check::Request::<NoExtension>::new(domains, None, CLTRID);
client.transact_new(&request).await.unwrap(); let response = client.transact_new(request).await.unwrap();
println!("{:?}", response);
client.logout().await.unwrap(); client.logout().await.unwrap();
} }