Skip to main content

icann_rdap_srv/storage/
data.rs

1use std::{
2    net::IpAddr,
3    path::PathBuf,
4    time::{Duration, SystemTime},
5};
6
7use {
8    buildstructor::Builder,
9    icann_rdap_common::{
10        prelude::Numberish,
11        response::{
12            Autnum, Cidr0Cidr, Domain, Entity, GetSelfLink, Nameserver, Network, RdapResponse,
13            SelfLink,
14        },
15    },
16    ipnet::{IpNet, Ipv4Subnets, Ipv6Subnets},
17    serde::{Deserialize, Serialize},
18    serde_json::Value,
19    tokio::time::sleep,
20    tracing::{debug, info, warn},
21};
22
23use strum::Display as EnumDisplay;
24
25use crate::{
26    config::ServiceConfig,
27    error::RdapServerError,
28    storage::{StoreOps, TxHandle},
29};
30
31pub const UPDATE: &str = "update";
32pub const RELOAD: &str = "reload";
33
34#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, EnumDisplay)]
35#[serde(untagged)]
36pub enum Template {
37    Domain {
38        domain: DomainOrError,
39        ids: Vec<DomainId>,
40    },
41    Entity {
42        entity: EntityOrError,
43        ids: Vec<EntityId>,
44    },
45    Nameserver {
46        nameserver: NameserverOrError,
47        ids: Vec<NameserverId>,
48    },
49    Autnum {
50        autnum: AutnumOrError,
51        ids: Vec<AutnumId>,
52    },
53    Network {
54        network: NetworkOrError,
55        ids: Vec<NetworkId>,
56    },
57}
58
59#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
60pub enum DomainOrError {
61    #[serde(rename = "object")]
62    DomainObject(Box<Domain>),
63    #[serde(rename = "error")]
64    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
65}
66
67#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
68pub enum EntityOrError {
69    #[serde(rename = "object")]
70    EntityObject(Box<Entity>),
71    #[serde(rename = "error")]
72    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
73}
74
75#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
76pub enum NameserverOrError {
77    #[serde(rename = "object")]
78    NameserverObject(Box<Nameserver>),
79    #[serde(rename = "error")]
80    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
81}
82
83#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
84pub enum AutnumOrError {
85    #[serde(rename = "object")]
86    AutnumObject(Box<Autnum>),
87    #[serde(rename = "error")]
88    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
89}
90
91#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
92pub enum NetworkOrError {
93    #[serde(rename = "object")]
94    NetworkObject(Box<Network>),
95    #[serde(rename = "error")]
96    ErrorResponse(icann_rdap_common::response::Rfc9083Error),
97}
98
99#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
100pub struct DomainId {
101    #[serde(rename = "ldhName")]
102    pub ldh_name: String,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    #[serde(rename = "unicodeName")]
105    pub unicode_name: Option<String>,
106}
107
108#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
109pub struct EntityId {
110    pub handle: String,
111}
112
113#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
114pub struct NameserverId {
115    #[serde(rename = "ldhName")]
116    pub ldh_name: String,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    #[serde(rename = "unicodeName")]
119    pub unicode_name: Option<String>,
120}
121
122#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
123pub struct AutnumId {
124    #[serde(rename = "startAutnum")]
125    pub start_autnum: u32,
126    #[serde(rename = "endAutnum")]
127    pub end_autnum: u32,
128}
129
130#[derive(Clone, Serialize, Deserialize, Builder, Debug, PartialEq, Eq)]
131pub struct NetworkId {
132    #[serde(rename = "networkId")]
133    pub network_id: NetworkIdType,
134}
135
136#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
137#[serde(untagged)]
138pub enum NetworkIdType {
139    Cidr(IpNet),
140    Range {
141        #[serde(rename = "startAddress")]
142        start_address: String,
143        #[serde(rename = "endAddress")]
144        end_address: String,
145    },
146}
147
148/// Loads files from the data directory into memory.
149///
150/// There are 2 types of files that will be selected. Files ending with a `.json` extension
151/// are considered to be JSON files holding one RDAP response each.
152///
153/// Files ending with a `.template` extension are a means to quickly create RDAP objects using
154/// a template. Templates follow a pattern of a set of IDs paired with an RDAP object:
155///
156/// ```json
157/// {
158///   "domain":
159///     {
160///       "object":
161///         {
162///           "objectClassName":"domain",
163///           "ldhName":"example"
164///         }
165///     },
166///   "ids":
167///     [
168///       {"ldhName":"bar.example"},
169///       {"ldhName":"foo.example"}
170///     ]
171/// }
172/// ```
173/// In this example, 2 domains will be created for "foo.example" and "bar.example" using
174/// the template.
175pub async fn load_data(
176    config: &ServiceConfig,
177    store: &dyn StoreOps,
178    truncate: bool,
179) -> Result<(), RdapServerError> {
180    let mut json_count: usize = 0;
181    let mut template_count: usize = 0;
182    let mut srvhelp_count: usize = 0;
183    let mut tx = if truncate {
184        store.new_truncate_tx().await?
185    } else {
186        store.new_tx().await?
187    };
188    let path = PathBuf::from(&config.data_dir);
189    if !path.exists() || !path.is_dir() {
190        warn!(
191            "Directory {} does not exist or is not a directory. Server has no content to serve.",
192            path.to_string_lossy()
193        );
194        return Ok(());
195    }
196
197    let mut entries = tokio::fs::read_dir(path).await?;
198    while let Some(entry) = entries.next_entry().await? {
199        let entry_path = entry.path();
200        let contents = tokio::fs::read_to_string(&entry_path).await?;
201        if entry_path.extension().is_some_and(|ext| ext == "template") {
202            load_rdap_template(&contents, &entry_path.to_string_lossy(), &mut tx).await?;
203            template_count += 1;
204        } else if entry_path.extension().is_some_and(|ext| ext == "json") {
205            load_rdap(&contents, &entry_path.to_string_lossy(), &mut tx).await?;
206            json_count += 1;
207        } else if entry_path.extension().is_some_and(|ext| ext == "help") {
208            load_srvhelp(
209                &contents,
210                &entry_path.to_string_lossy(),
211                &entry.file_name().to_string_lossy(),
212                &mut tx,
213            )
214            .await?;
215            srvhelp_count += 1;
216        }
217    }
218
219    info!("{json_count} RDAP JSON files loaded.");
220    info!("{template_count} RDAP template files loaded.");
221    info!("{srvhelp_count} RDAP server help files loaded.");
222    if json_count == 0 && template_count == 0 && srvhelp_count == 0 {
223        warn!("No data loaded. Server has no content to serve.");
224    }
225    tx.commit().await?;
226    Ok(())
227}
228
229/// Loads the RDAP JSON files and puts them in storage.
230async fn load_rdap(
231    contents: &str,
232    path_name: &str,
233    tx: &mut Box<dyn TxHandle>,
234) -> Result<(), RdapServerError> {
235    debug!("loading {path_name} into storage");
236    let json = serde_json::from_str::<Value>(contents);
237    let Ok(value) = json else {
238        return Err(RdapServerError::NonJsonFile(path_name.to_owned()));
239    };
240    let rdap = RdapResponse::try_from(value);
241    let Ok(rdap) = rdap else {
242        return Err(RdapServerError::NonRdapJsonFile(path_name.to_owned()));
243    };
244    match rdap {
245        RdapResponse::Entity(entity) => tx.add_entity(&entity).await,
246        RdapResponse::Domain(domain) => tx.add_domain(&domain).await,
247        RdapResponse::Nameserver(nameserver) => tx.add_nameserver(&nameserver).await,
248        RdapResponse::Autnum(autnum) => tx.add_autnum(&autnum).await,
249        RdapResponse::Network(network) => tx.add_network(&network).await,
250        _ => return Err(RdapServerError::NonRdapJsonFile(path_name.to_owned())),
251    }?;
252    Ok(())
253}
254
255/// Loads the RDAP HELP files and puts them in storage.
256async fn load_srvhelp(
257    contents: &str,
258    path_name: &str,
259    file_name: &str,
260    tx: &mut Box<dyn TxHandle>,
261) -> Result<(), RdapServerError> {
262    debug!("loading {path_name} into storage");
263    let Some(host) = file_name.strip_suffix(".help") else {
264        return Err(RdapServerError::NonRdapJsonFile(path_name.to_string()));
265    };
266    let host = host.replace('_', ".");
267    let json = serde_json::from_str::<Value>(contents);
268    let Ok(value) = json else {
269        return Err(RdapServerError::NonJsonFile(path_name.to_owned()));
270    };
271    let rdap = RdapResponse::try_from(value);
272    let Ok(rdap) = rdap else {
273        return Err(RdapServerError::NonRdapJsonFile(path_name.to_owned()));
274    };
275    match rdap {
276        RdapResponse::Help(srvhelp) => tx.add_srv_help(&srvhelp, Some(&host)).await,
277        _ => return Err(RdapServerError::NonRdapJsonFile(path_name.to_owned())),
278    }?;
279    Ok(())
280}
281
282/// Loads the template files, creates RDAP objects from the templates, and puts them
283/// into storage.
284async fn load_rdap_template(
285    contents: &str,
286    path_name: &str,
287    tx: &mut Box<dyn TxHandle>,
288) -> Result<(), RdapServerError> {
289    debug!("processing {path_name} template");
290    let json = serde_json::from_str::<Template>(contents);
291    if let Ok(value) = json {
292        match value {
293            Template::Domain { domain, ids } => {
294                for id in ids {
295                    debug!("adding domain from template for {id:?}");
296                    match &domain {
297                        DomainOrError::DomainObject(domain) => {
298                            let domain = make_domain_from_template(domain, id);
299                            tx.add_domain(&domain).await?;
300                        }
301                        DomainOrError::ErrorResponse(error) => {
302                            tx.add_domain_err(&id, error).await?;
303                        }
304                    };
305                }
306            }
307            Template::Entity { entity, ids } => {
308                for id in ids {
309                    debug!("adding entity from template for {id:?}");
310                    match &entity {
311                        EntityOrError::EntityObject(entity) => {
312                            let entity = make_entity_from_template(entity, id);
313                            tx.add_entity(&entity).await?;
314                        }
315                        EntityOrError::ErrorResponse(error) => {
316                            tx.add_entity_err(&id, error).await?;
317                        }
318                    };
319                }
320            }
321            Template::Nameserver { nameserver, ids } => {
322                for id in ids {
323                    debug!("adding nameserver from template for {id:?}");
324                    match &nameserver {
325                        NameserverOrError::NameserverObject(nameserver) => {
326                            let nameserver = make_nameserver_from_template(nameserver, id);
327                            tx.add_nameserver(&nameserver).await?;
328                        }
329                        NameserverOrError::ErrorResponse(error) => {
330                            tx.add_nameserver_err(&id, error).await?;
331                        }
332                    };
333                }
334            }
335            Template::Autnum { autnum, ids } => {
336                for id in ids {
337                    debug!("adding autnum from template for {id:?}");
338                    match &autnum {
339                        AutnumOrError::AutnumObject(autnum) => {
340                            let autnum = make_autnum_from_template(autnum, id);
341                            tx.add_autnum(&autnum).await?;
342                        }
343                        AutnumOrError::ErrorResponse(error) => {
344                            tx.add_autnum_err(&id, error).await?;
345                        }
346                    };
347                }
348            }
349            Template::Network { network, ids } => {
350                for id in ids {
351                    debug!("adding network from template for {id:?}");
352                    match &network {
353                        NetworkOrError::NetworkObject(network) => {
354                            let network = make_network_from_template(network, id)?;
355                            tx.add_network(&network).await?;
356                        }
357                        NetworkOrError::ErrorResponse(error) => {
358                            tx.add_network_err(&id, error).await?;
359                        }
360                    };
361                }
362            }
363        };
364    } else {
365        return Err(RdapServerError::NonJsonFile(path_name.to_owned()));
366    }
367    Ok(())
368}
369
370pub(crate) async fn reload_data(
371    store: Box<dyn StoreOps>,
372    config: ServiceConfig,
373) -> Result<(), RdapServerError> {
374    let update_path = PathBuf::from(&config.data_dir);
375    let update_path = update_path.join(UPDATE);
376    let reload_path = PathBuf::from(&config.data_dir);
377    let reload_path = reload_path.join(RELOAD);
378    let mut last_time = SystemTime::now();
379    loop {
380        sleep(Duration::from_millis(1000)).await;
381        let update_meta = tokio::fs::metadata(&update_path).await;
382        if let Ok(update_meta) = update_meta {
383            let modified = update_meta.modified()?;
384            if modified > last_time {
385                last_time = modified;
386                info!("Data being updated.");
387                load_data(&config, &*store, false).await?;
388            }
389        };
390        let reload_meta = tokio::fs::metadata(&reload_path).await;
391        if let Ok(reload_meta) = reload_meta {
392            let modified = reload_meta.modified()?;
393            if modified > last_time {
394                last_time = modified;
395                info!("Data being reloaded.");
396                load_data(&config, &*store, true).await?;
397            }
398        };
399    }
400}
401
402pub async fn trigger_reload(data_dir: &str) -> Result<(), RdapServerError> {
403    let reload_path = PathBuf::from(&data_dir);
404    let reload_path = reload_path.join(RELOAD);
405    tokio::fs::File::create(reload_path).await?;
406    Ok(())
407}
408
409pub async fn trigger_update(data_dir: &str) -> Result<(), RdapServerError> {
410    let update_path = PathBuf::from(&data_dir);
411    let update_path = update_path.join(UPDATE);
412    tokio::fs::File::create(update_path).await?;
413    Ok(())
414}
415
416fn change_self_link<T: GetSelfLink + SelfLink>(mut object: T, segment: &str, id: &str) -> T {
417    if let Some(self_link) = object.self_link() {
418        if let Some(self_href) = &self_link.href {
419            if let Some(self_href_split) = self_href.rsplit_once(segment) {
420                let mut new_self_link = self_link.clone();
421                new_self_link.href = Some(format!("{}{segment}/{}", self_href_split.0, id));
422                object = object.with_self_link(new_self_link);
423            } else {
424                warn!("Unable to rewrite self link for {segment} {}", id);
425            }
426        } else {
427            warn!("Unable to use self link because it has not href")
428        }
429    } else {
430        warn!("No self link for {segment} {}", id);
431    }
432    object
433}
434
435fn make_domain_from_template(domain: &Domain, id: DomainId) -> Domain {
436    let mut domain = domain.clone();
437    domain = change_self_link(domain, "domain", &id.ldh_name);
438    domain.ldh_name = Some(id.ldh_name);
439    if let Some(unicode_name) = id.unicode_name {
440        domain.unicode_name = Some(unicode_name);
441    };
442    domain
443}
444
445fn make_entity_from_template(entity: &Entity, id: EntityId) -> Entity {
446    let mut entity = entity.clone();
447    entity = change_self_link(entity, "entity", &id.handle);
448    entity.object_common.handle = Some(id.handle.into());
449    entity
450}
451
452fn make_nameserver_from_template(nameserver: &Nameserver, id: NameserverId) -> Nameserver {
453    let mut nameserver = nameserver.clone();
454    nameserver = change_self_link(nameserver, "nameserver", &id.ldh_name);
455    nameserver.ldh_name = Some(id.ldh_name);
456    if let Some(unicode_name) = id.unicode_name {
457        nameserver.unicode_name = Some(unicode_name);
458    };
459    nameserver
460}
461
462fn make_autnum_from_template(autnum: &Autnum, id: AutnumId) -> Autnum {
463    let mut autnum = autnum.clone();
464    autnum = change_self_link(autnum, "autnum", &id.start_autnum.to_string());
465    autnum.start_autnum = Some(Numberish::<u32>::from(id.start_autnum));
466    autnum.end_autnum = Some(Numberish::<u32>::from(id.end_autnum));
467    autnum
468}
469
470fn make_network_from_template(
471    network: &Network,
472    id: NetworkId,
473) -> Result<Network, RdapServerError> {
474    let mut network = network.clone();
475    match id.network_id {
476        NetworkIdType::Cidr(cidr) => match cidr {
477            IpNet::V4(v4) => {
478                network.start_address = Some(v4.network().to_string());
479                network.end_address = Some(v4.broadcast().to_string());
480                network.ip_version = Some("v4".to_string().into());
481                network.cidr0_cidrs = Some(vec![
482                    Cidr0Cidr::v4()
483                        .prefix(v4.network().to_string())
484                        .length(v4.prefix_len())
485                        .build(),
486                ])
487            }
488            IpNet::V6(v6) => {
489                network.start_address = Some(v6.network().to_string());
490                network.end_address = Some(v6.broadcast().to_string());
491                network.ip_version = Some("v6".to_string().into());
492                network.cidr0_cidrs = Some(vec![
493                    Cidr0Cidr::v6()
494                        .prefix(v6.network().to_string())
495                        .length(v6.prefix_len())
496                        .build(),
497                ]);
498            }
499        },
500        NetworkIdType::Range {
501            start_address,
502            end_address,
503        } => {
504            let addr: IpAddr = start_address.parse()?;
505            if addr.is_ipv4() {
506                network.ip_version = Some("v4".to_string().into());
507                network.cidr0_cidrs = Some(
508                    Ipv4Subnets::new(start_address.parse()?, end_address.parse()?, 0)
509                        .map(|net| {
510                            Cidr0Cidr::v4()
511                                .prefix(net.network().to_string())
512                                .length(net.prefix_len())
513                                .build()
514                        })
515                        .collect::<Vec<Cidr0Cidr>>(),
516                );
517            } else {
518                network.ip_version = Some("v6".to_string().into());
519                network.cidr0_cidrs = Some(
520                    Ipv6Subnets::new(start_address.parse()?, end_address.parse()?, 0)
521                        .map(|net| {
522                            Cidr0Cidr::v6()
523                                .prefix(net.network().to_string())
524                                .length(net.prefix_len())
525                                .build()
526                        })
527                        .collect::<Vec<Cidr0Cidr>>(),
528                );
529            }
530            network.start_address = Some(start_address);
531            network.end_address = Some(end_address);
532        }
533    }
534    let first_cidr = network
535        .cidr0_cidrs
536        .as_ref()
537        .expect("cidrs should be on network")
538        .first()
539        .map(|cidr| cidr.to_string())
540        .expect("cidrs on network are empty");
541    network = change_self_link(network, "ip", &first_cidr);
542    Ok(network)
543}
544
545#[cfg(test)]
546mod tests {
547
548    use icann_rdap_common::{
549        prelude::Stringish,
550        response::{Domain, Link},
551    };
552
553    use super::*;
554
555    #[test]
556    fn test_template_domain_serialize() {
557        // GIVEN
558        let template = Template::Domain {
559            domain: DomainOrError::DomainObject(Box::new(
560                Domain::response_obj().ldh_name("foo.example").build(),
561            )),
562            ids: vec![DomainId::builder().ldh_name("bar.example").build()],
563        };
564
565        // WHEN
566        let actual = serde_json::to_string(&template).expect("serializing template");
567
568        // THEN
569        assert_eq!(
570            actual,
571            r#"{"domain":{"object":{"rdapConformance":["rdap_level_0"],"objectClassName":"domain","ldhName":"foo.example"}},"ids":[{"ldhName":"bar.example"}]}"#
572        );
573    }
574
575    #[test]
576    fn test_template_domain_text_deserialize() {
577        // GIVEN
578        let json_text = r#"{"domain":{"object":{"rdapConformance":["rdap_level_0"],"objectClassName":"domain","ldhName":"foo.example"}},"ids":[{"ldhName":"bar.example"}]}"#;
579
580        // WHEN
581        let actual: Template = serde_json::from_str(json_text).expect("deserializing template");
582
583        // THEN
584        let expected = Template::Domain {
585            domain: DomainOrError::DomainObject(Box::new(
586                Domain::response_obj().ldh_name("foo.example").build(),
587            )),
588            ids: vec![DomainId::builder().ldh_name("bar.example").build()],
589        };
590        assert_eq!(actual, expected);
591    }
592
593    #[test]
594    fn test_template_network_with_cidr_serialize() {
595        // GIVEN
596        let template = Template::Network {
597            network: NetworkOrError::NetworkObject(Box::new(
598                Network::response_obj()
599                    .cidr("10.0.0.0/24")
600                    .build()
601                    .expect("cidr parsing"),
602            )),
603            ids: vec![
604                NetworkId::builder()
605                    .network_id(NetworkIdType::Cidr(
606                        "10.0.0.0/24".parse().expect("ipnet parsing"),
607                    ))
608                    .build(),
609            ],
610        };
611
612        // WHEN
613        let actual = serde_json::to_string(&template).expect("serializing template");
614        let actual: Template = serde_json::from_str(&actual).expect("deserialize network template");
615
616        // THEN
617        let expected = r#"
618        {
619            "network":{
620                "object":{
621                    "rdapConformance":["cidr0","rdap_level_0"],
622                    "objectClassName":"ip network",
623                    "startAddress":"10.0.0.0",
624                    "endAddress":"10.0.0.255",
625                    "ipVersion":"v4",
626                    "cidr0_cidrs":[{"v4prefix":"10.0.0.0","length":24}]
627                }
628            },
629            "ids":[
630                {"networkId":"10.0.0.0/24"}
631            ]
632        }"#;
633        let expected: Template = serde_json::from_str(expected).expect("deserialize expected");
634        assert_eq!(actual, expected);
635    }
636
637    #[test]
638    fn test_template_network_with_start_and_end_serialize() {
639        // GIVEN
640        let template = Template::Network {
641            network: NetworkOrError::NetworkObject(Box::new(
642                Network::response_obj()
643                    .cidr("10.0.0.0/24")
644                    .build()
645                    .expect("cidr parsing"),
646            )),
647            ids: vec![
648                NetworkId::builder()
649                    .network_id(NetworkIdType::Range {
650                        start_address: "10.0.0.0".to_string(),
651                        end_address: "10.0.0.255".to_string(),
652                    })
653                    .build(),
654            ],
655        };
656
657        // WHEN
658        let actual = serde_json::to_string(&template).expect("serializing template");
659        let actual: Template = serde_json::from_str(&actual).expect("deserialize network template");
660
661        // THEN
662        let expected = r#"
663        {
664            "network":{
665                "object":{
666                    "rdapConformance":["cidr0","rdap_level_0"],
667                    "objectClassName":"ip network",
668                    "startAddress":"10.0.0.0",
669                    "endAddress":"10.0.0.255",
670                    "ipVersion":"v4",
671                    "cidr0_cidrs":[
672                        {"v4prefix":"10.0.0.0","length":24}
673                    ]
674                }
675            },
676            "ids":[
677                {"networkId":
678                    {"startAddress":"10.0.0.0","endAddress":"10.0.0.255"}
679                }
680            ]
681        }"#;
682        let expected: Template = serde_json::from_str(expected).expect("deserialize expected");
683        assert_eq!(actual, expected);
684    }
685
686    #[test]
687    fn test_template_network_with_cidr_text_deserialize() {
688        // GIVEN
689        let text = r#"
690        {
691            "network":{
692                "object":{
693                    "rdapConformance":["cidr0","rdap_level_0"],
694                    "objectClassName":"ip network",
695                    "startAddress":"10.0.0.0",
696                    "endAddress":"10.0.0.255",
697                    "ipVersion":"v4",
698                    "cidr0_cidrs":[{"v4prefix":"10.0.0.0","length":24}]
699                }
700            },
701            "ids":[
702                {"networkId":"10.0.0.0/24"}
703            ]
704        }"#;
705
706        // WHEN
707        let actual: Template = serde_json::from_str(text).expect("deserialize network template");
708
709        // THEN
710        let expected = Template::Network {
711            network: NetworkOrError::NetworkObject(Box::new(
712                Network::response_obj()
713                    .cidr("10.0.0.0/24")
714                    .build()
715                    .expect("cidr parsing"),
716            )),
717            ids: vec![
718                NetworkId::builder()
719                    .network_id(NetworkIdType::Cidr(
720                        "10.0.0.0/24".parse().expect("ipnet parsing"),
721                    ))
722                    .build(),
723            ],
724        };
725        assert_eq!(actual, expected);
726    }
727
728    #[test]
729    fn test_template_network_with_range_text_deserialize() {
730        // GIVEN
731        let text = r#"
732        {
733            "network":{
734                "object":{
735                    "rdapConformance":["cidr0","rdap_level_0"],
736                    "objectClassName":"ip network",
737                    "startAddress":"10.0.0.0",
738                    "endAddress":"10.0.0.255",
739                    "ipVersion":"v4",
740                    "cidr0_cidrs":[
741                        {"v4prefix":"10.0.0.0","length":24}
742                    ]
743                }
744            },
745            "ids":[
746                {"networkId":
747                    {"startAddress":"10.0.0.0","endAddress":"10.0.0.255"}
748                }
749            ]
750        }"#;
751
752        // WHEN
753        let actual: Template = serde_json::from_str(text).expect("deserialize network template");
754
755        // THEN
756        let expected = Template::Network {
757            network: NetworkOrError::NetworkObject(Box::new(
758                Network::response_obj()
759                    .cidr("10.0.0.0/24")
760                    .build()
761                    .expect("cidr parsing"),
762            )),
763            ids: vec![
764                NetworkId::builder()
765                    .network_id(NetworkIdType::Range {
766                        start_address: "10.0.0.0".to_string(),
767                        end_address: "10.0.0.255".to_string(),
768                    })
769                    .build(),
770            ],
771        };
772        assert_eq!(actual, expected);
773    }
774
775    #[test]
776    fn test_make_domain_with_domain_and_id() {
777        // GIVEN
778        let domain = Domain::builder()
779            .ldh_name("foo.example")
780            .link(
781                Link::builder()
782                    .rel("self")
783                    .href("http://reg.example/domain/foo.example")
784                    .value("http://reg.example/domain/foo.example")
785                    .build(),
786            )
787            .build();
788        let id = DomainId::builder().ldh_name("bar.example").build();
789
790        // WHEN
791        let actual = make_domain_from_template(&domain, id);
792
793        // THEN
794        assert_eq!(
795            actual.ldh_name.as_ref().expect("no ldhname on domain"),
796            "bar.example"
797        );
798        let self_link = actual.self_link().expect("self link messing");
799        assert_eq!(
800            self_link.href.as_ref().expect("link has no href"),
801            "http://reg.example/domain/bar.example"
802        );
803    }
804
805    #[test]
806    fn test_make_entity_with_entity_and_id() {
807        // GIVEN
808        let entity = Entity::builder()
809            .handle("foo")
810            .link(
811                Link::builder()
812                    .rel("self")
813                    .href("http://reg.example/entity/foo")
814                    .value("http://reg.example/entity/foo")
815                    .build(),
816            )
817            .build();
818        let id = EntityId::builder().handle("bar").build();
819
820        // WHEN
821        let actual = make_entity_from_template(&entity, id);
822
823        // THEN
824        assert_eq!(
825            actual
826                .object_common
827                .handle
828                .as_ref()
829                .expect("no handle on entity"),
830            &Stringish::from("bar")
831        );
832        let self_link = actual.self_link().expect("self link messing");
833        assert_eq!(
834            self_link.href.as_ref().expect("link has no href"),
835            "http://reg.example/entity/bar"
836        );
837    }
838
839    #[test]
840    fn test_make_ns_with_nameserver_and_id() {
841        // GIVEN
842        let nameserver = Nameserver::builder()
843            .ldh_name("ns.foo.example")
844            .link(
845                Link::builder()
846                    .rel("self")
847                    .href("http://reg.example/nameserver/ns.foo.example")
848                    .value("http://reg.example/nameserver/ns.foo.example")
849                    .build(),
850            )
851            .build()
852            .expect("creation of nameserver");
853        let id = NameserverId::builder().ldh_name("ns.bar.example").build();
854
855        // WHEN
856        let actual = make_nameserver_from_template(&nameserver, id);
857
858        // THEN
859        assert_eq!(
860            actual.ldh_name.as_ref().expect("no ldhname on nameserver"),
861            "ns.bar.example"
862        );
863        let self_link = actual.self_link().expect("self link messing");
864        assert_eq!(
865            self_link.href.as_ref().expect("link has no href"),
866            "http://reg.example/nameserver/ns.bar.example"
867        );
868    }
869
870    #[test]
871    fn test_make_autnum_with_autnum_and_id() {
872        // GIVEN
873        let autnum = Autnum::builder()
874            .autnum_range(700..710)
875            .link(
876                Link::builder()
877                    .rel("self")
878                    .href("http://reg.example/autnum/700")
879                    .value("http://reg.example/autnum/700")
880                    .build(),
881            )
882            .build();
883        let id = AutnumId::builder()
884            .start_autnum(900)
885            .end_autnum(999)
886            .build();
887
888        // WHEN
889        let actual = make_autnum_from_template(&autnum, id);
890
891        // THEN
892        assert_eq!(
893            *actual.start_autnum.as_ref().expect("no startnum on autnum"),
894            Numberish::<u32>::from(900)
895        );
896        assert_eq!(
897            *actual.end_autnum.as_ref().expect("no end on autnum"),
898            Numberish::<u32>::from(999)
899        );
900        let self_link = actual.self_link().expect("self link messing");
901        assert_eq!(
902            self_link.href.as_ref().expect("link has href"),
903            "http://reg.example/autnum/900"
904        );
905    }
906
907    #[test]
908    fn test_make_network_with_id_and_range() {
909        // GIVEN
910        let network = Network::builder()
911            .cidr("10.0.0.0/24")
912            .link(
913                Link::builder()
914                    .rel("self")
915                    .href("http://reg.example/ip/10.0.0.0/24")
916                    .value("http://reg.example/ip/10.0.0.0/24")
917                    .build(),
918            )
919            .build()
920            .expect("creating test network");
921        let id = NetworkId::builder()
922            .network_id(NetworkIdType::Range {
923                start_address: "11.0.0.0".to_string(),
924                end_address: "11.0.0.255".to_string(),
925            })
926            .build();
927
928        // WHEN
929        let actual = make_network_from_template(&network, id).expect("unable to make network");
930
931        // THEN
932        assert_eq!(
933            actual
934                .start_address
935                .as_ref()
936                .expect("no start address on network"),
937            "11.0.0.0"
938        );
939        assert_eq!(
940            actual
941                .end_address
942                .as_ref()
943                .expect("no end address on network"),
944            "11.0.0.255"
945        );
946        let cidr0 = actual.cidr0_cidrs.as_ref().expect("no cidr0");
947        let v4cidr = cidr0.first().expect("cidr0 is empty");
948        assert_eq!(v4cidr.prefix(), Some("11.0.0.0".to_string()));
949        assert_eq!(v4cidr.length(), Some(24));
950        let self_link = actual.self_link().expect("self link messing");
951        assert_eq!(
952            self_link.href.as_ref().expect("link has no href"),
953            "http://reg.example/ip/11.0.0.0/24"
954        );
955    }
956
957    #[test]
958    fn test_make_network_with_cidr_and_id() {
959        // GIVEN
960        let network = Network::builder()
961            .cidr("10.0.0.0/24")
962            .link(
963                Link::builder()
964                    .rel("self")
965                    .href("http://reg.example/ip/10.0.0.0/24")
966                    .value("http://reg.example/ip/10.0.0.0/24")
967                    .build(),
968            )
969            .build()
970            .expect("creating test network");
971        let id = NetworkId::builder()
972            .network_id(NetworkIdType::Cidr(IpNet::V4(
973                "11.0.0.0/24".parse().expect("bad cidr"),
974            )))
975            .build();
976
977        // WHEN
978        let actual = make_network_from_template(&network, id).expect("unable to make network");
979
980        // THEN
981        assert_eq!(
982            actual
983                .start_address
984                .as_ref()
985                .expect("no start address on network"),
986            "11.0.0.0"
987        );
988        assert_eq!(
989            actual
990                .end_address
991                .as_ref()
992                .expect("no end address on network"),
993            "11.0.0.255"
994        );
995        let cidr0 = actual.cidr0_cidrs.as_ref().expect("no cidr0");
996        let v4cidr = cidr0.first().expect("cidr0 is empty");
997        assert_eq!(v4cidr.prefix(), Some("11.0.0.0".to_string()));
998        assert_eq!(v4cidr.length(), Some(24));
999        let self_link = actual.self_link().expect("self link messing");
1000        assert_eq!(
1001            self_link.href.as_ref().expect("link has no href"),
1002            "http://reg.example/ip/11.0.0.0/24"
1003        );
1004    }
1005}