use crate::Client;
use crate::error::Result;
use crate::resource::Resource;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
pub type AvailableIp = crate::models::AvailableIp;
pub type AvailablePrefix = crate::models::AvailablePrefix;
pub type AvailableAsn = crate::models::AvailableAsn;
pub type AvailableVlan = crate::models::AvailableVlan;
pub type Asn = crate::models::Asn;
pub type Vlan = crate::models::Vlan;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateIpAddressRequest {
pub address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub vrf: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigned_object_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assigned_object_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<i32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateIpAddressRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchIpAddressFieldsRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_fields: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<crate::models::NestedTag>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreatePrefixRequest {
pub prefix: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub site: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vrf: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tenant: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vlan: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_pool: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<i32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdatePrefixRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub site: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scope_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchPrefixFieldsRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_fields: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<crate::models::NestedTag>>,
}
pub type IpAddress = crate::models::IpAddress;
pub type Prefix = crate::models::Prefix;
pub type AggregatesApi = Resource<crate::models::Aggregate>;
pub type AsnRangesApi = Resource<crate::models::AsnRange>;
pub type AsnsApi = Resource<crate::models::Asn>;
pub type FhrpGroupAssignmentsApi = Resource<crate::models::FhrpGroupAssignment>;
pub type FhrpGroupsApi = Resource<crate::models::FhrpGroup>;
pub type IpAddressesApi = Resource<crate::models::IpAddress>;
pub type IpRangesApi = Resource<crate::models::IpRange>;
pub type PrefixesApi = Resource<crate::models::Prefix>;
pub type RirsApi = Resource<crate::models::Rir>;
pub type RolesApi = Resource<crate::models::Role>;
pub type RouteTargetsApi = Resource<crate::models::RouteTarget>;
pub type ServiceTemplatesApi = Resource<crate::models::ServiceTemplate>;
pub type ServicesApi = Resource<crate::models::Service>;
pub type VlanGroupsApi = Resource<crate::models::VlanGroup>;
pub type VlanTranslationPoliciesApi = Resource<crate::models::VlanTranslationPolicy>;
pub type VlanTranslationRulesApi = Resource<crate::models::VlanTranslationRule>;
pub type VlansApi = Resource<crate::models::Vlan>;
pub type VrfsApi = Resource<crate::models::Vrf>;
#[derive(Clone)]
pub struct IpamApi {
client: Client,
}
impl IpamApi {
pub(crate) fn new(client: Client) -> Self {
Self { client }
}
pub fn aggregates(&self) -> AggregatesApi {
Resource::new(self.client.clone(), "ipam/aggregates/")
}
pub fn asn_ranges(&self) -> AsnRangesApi {
Resource::new(self.client.clone(), "ipam/asn-ranges/")
}
pub fn asns(&self) -> AsnsApi {
Resource::new(self.client.clone(), "ipam/asns/")
}
pub fn fhrp_group_assignments(&self) -> FhrpGroupAssignmentsApi {
Resource::new(self.client.clone(), "ipam/fhrp-group-assignments/")
}
pub fn fhrp_groups(&self) -> FhrpGroupsApi {
Resource::new(self.client.clone(), "ipam/fhrp-groups/")
}
pub fn ip_addresses(&self) -> IpAddressesApi {
Resource::new(self.client.clone(), "ipam/ip-addresses/")
}
pub fn ip_ranges(&self) -> IpRangesApi {
Resource::new(self.client.clone(), "ipam/ip-ranges/")
}
pub fn prefixes(&self) -> PrefixesApi {
Resource::new(self.client.clone(), "ipam/prefixes/")
}
pub fn rirs(&self) -> RirsApi {
Resource::new(self.client.clone(), "ipam/rirs/")
}
pub fn roles(&self) -> RolesApi {
Resource::new(self.client.clone(), "ipam/roles/")
}
pub fn route_targets(&self) -> RouteTargetsApi {
Resource::new(self.client.clone(), "ipam/route-targets/")
}
pub fn service_templates(&self) -> ServiceTemplatesApi {
Resource::new(self.client.clone(), "ipam/service-templates/")
}
pub fn services(&self) -> ServicesApi {
Resource::new(self.client.clone(), "ipam/services/")
}
pub fn vlan_groups(&self) -> VlanGroupsApi {
Resource::new(self.client.clone(), "ipam/vlan-groups/")
}
pub fn vlan_translation_policies(&self) -> VlanTranslationPoliciesApi {
Resource::new(self.client.clone(), "ipam/vlan-translation-policies/")
}
pub fn vlan_translation_rules(&self) -> VlanTranslationRulesApi {
Resource::new(self.client.clone(), "ipam/vlan-translation-rules/")
}
pub fn vlans(&self) -> VlansApi {
Resource::new(self.client.clone(), "ipam/vlans/")
}
pub fn vrfs(&self) -> VrfsApi {
Resource::new(self.client.clone(), "ipam/vrfs/")
}
pub async fn available_ips_in_prefix(&self, id: u64) -> Result<Vec<AvailableIp>> {
self.client
.get(&format!("ipam/prefixes/{}/available-ips/", id))
.await
}
pub async fn create_available_ips_in_prefix<B: Serialize>(
&self,
id: u64,
body: &[B],
) -> Result<Vec<IpAddress>> {
self.client
.post(&format!("ipam/prefixes/{}/available-ips/", id), body)
.await
}
pub async fn available_prefixes_in_prefix(&self, id: u64) -> Result<Vec<AvailablePrefix>> {
self.client
.get(&format!("ipam/prefixes/{}/available-prefixes/", id))
.await
}
pub async fn create_available_prefixes_in_prefix<B: Serialize>(
&self,
id: u64,
body: &[B],
) -> Result<Vec<Prefix>> {
self.client
.post(&format!("ipam/prefixes/{}/available-prefixes/", id), body)
.await
}
pub async fn available_ips_in_range(&self, id: u64) -> Result<Vec<AvailableIp>> {
self.client
.get(&format!("ipam/ip-ranges/{}/available-ips/", id))
.await
}
pub async fn create_available_ips_in_range<B: Serialize>(
&self,
id: u64,
body: &[B],
) -> Result<Vec<IpAddress>> {
self.client
.post(&format!("ipam/ip-ranges/{}/available-ips/", id), body)
.await
}
pub async fn available_vlans_in_group(&self, id: u64) -> Result<Vec<AvailableVlan>> {
self.client
.get(&format!("ipam/vlan-groups/{}/available-vlans/", id))
.await
}
pub async fn create_available_vlans_in_group<B: Serialize>(
&self,
id: u64,
body: &[B],
) -> Result<Vec<Vlan>> {
self.client
.post(&format!("ipam/vlan-groups/{}/available-vlans/", id), body)
.await
}
pub async fn available_asns_in_range(&self, id: u64) -> Result<Vec<AvailableAsn>> {
self.client
.get(&format!("ipam/asn-ranges/{}/available-asns/", id))
.await
}
pub async fn create_available_asns_in_range<B: Serialize>(
&self,
id: u64,
body: &[B],
) -> Result<Vec<Asn>> {
self.client
.post(&format!("ipam/asn-ranges/{}/available-asns/", id), body)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ClientConfig;
use httpmock::prelude::*;
use serde_json::json;
use std::collections::HashMap;
fn test_client() -> Client {
let config = ClientConfig::new("https://netbox.example.com", "token");
Client::new(config).unwrap()
}
fn mock_client(server: &MockServer) -> Client {
let config = ClientConfig::new(server.base_url(), "test-token");
Client::new(config).unwrap()
}
fn assert_path<T>(resource: Resource<T>, expected: &str)
where
T: serde::de::DeserializeOwned,
{
let paginator = resource.paginate(None).unwrap();
assert_eq!(paginator.next_url(), Some(expected));
}
#[test]
fn ipam_accessors_return_expected_paths() {
let api = IpamApi::new(test_client());
assert_path(api.aggregates(), "ipam/aggregates/");
assert_path(api.asn_ranges(), "ipam/asn-ranges/");
assert_path(api.asns(), "ipam/asns/");
assert_path(api.fhrp_group_assignments(), "ipam/fhrp-group-assignments/");
assert_path(api.fhrp_groups(), "ipam/fhrp-groups/");
assert_path(api.ip_addresses(), "ipam/ip-addresses/");
assert_path(api.ip_ranges(), "ipam/ip-ranges/");
assert_path(api.prefixes(), "ipam/prefixes/");
assert_path(api.rirs(), "ipam/rirs/");
assert_path(api.roles(), "ipam/roles/");
assert_path(api.route_targets(), "ipam/route-targets/");
assert_path(api.service_templates(), "ipam/service-templates/");
assert_path(api.services(), "ipam/services/");
assert_path(api.vlan_groups(), "ipam/vlan-groups/");
assert_path(
api.vlan_translation_policies(),
"ipam/vlan-translation-policies/",
);
assert_path(api.vlan_translation_rules(), "ipam/vlan-translation-rules/");
assert_path(api.vlans(), "ipam/vlans/");
assert_path(api.vrfs(), "ipam/vrfs/");
}
#[test]
fn serialize_ipam_requests() {
let ip = CreateIpAddressRequest {
address: "10.0.0.1/24".to_string(),
vrf: None,
tenant: None,
status: Some("active".to_string()),
role: None,
assigned_object_type: None,
assigned_object_id: None,
dns_name: None,
description: None,
tags: None,
};
let value = serde_json::to_value(&ip).unwrap();
assert_eq!(value["address"], "10.0.0.1/24");
assert_eq!(value["status"], "active");
let prefix = CreatePrefixRequest {
prefix: "192.168.0.0/24".to_string(),
site: None,
vrf: None,
tenant: None,
vlan: None,
scope_type: None,
scope_id: None,
status: None,
role: None,
is_pool: Some(true),
description: None,
tags: None,
};
let value = serde_json::to_value(&prefix).unwrap();
assert_eq!(value["prefix"], "192.168.0.0/24");
assert_eq!(value["is_pool"], true);
}
#[test]
fn serialize_projection_patch_requests() {
let mut fields = HashMap::new();
fields.insert("owner".to_string(), json!("netops"));
let tags = vec![crate::models::NestedTag::new(
"Core".to_string(),
"core".to_string(),
)];
let prefix = PatchPrefixFieldsRequest {
custom_fields: Some(fields.clone()),
tags: Some(tags.clone()),
};
let value = serde_json::to_value(&prefix).unwrap();
assert_eq!(value["custom_fields"]["owner"], "netops");
assert_eq!(value["tags"][0]["slug"], "core");
let ip = PatchIpAddressFieldsRequest {
custom_fields: Some(fields),
tags: Some(tags),
};
let value = serde_json::to_value(&ip).unwrap();
assert_eq!(value["custom_fields"]["owner"], "netops");
assert_eq!(value["tags"][0]["name"], "Core");
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn availability_endpoints_use_expected_paths() {
let server = MockServer::start();
let client = mock_client(&server);
let mock1 = server.mock(|when, then| {
when.method(GET)
.path("/api/ipam/prefixes/42/available-ips/");
then.status(200).json_body(json!([]));
});
let _ = client.ipam().available_ips_in_prefix(42).await;
mock1.assert();
let mock2 = server.mock(|when, then| {
when.method(POST)
.path("/api/ipam/prefixes/42/available-ips/");
then.status(201).json_body(json!([]));
});
let body = vec![json!({"description": "test"})];
let _ = client
.ipam()
.create_available_ips_in_prefix(42, &body)
.await;
mock2.assert();
let mock3 = server.mock(|when, then| {
when.method(GET)
.path("/api/ipam/prefixes/42/available-prefixes/");
then.status(200).json_body(json!([]));
});
let _ = client.ipam().available_prefixes_in_prefix(42).await;
mock3.assert();
let mock4 = server.mock(|when, then| {
when.method(POST)
.path("/api/ipam/prefixes/42/available-prefixes/");
then.status(201).json_body(json!([]));
});
let body = vec![json!({"prefix_length": 26})];
let _ = client
.ipam()
.create_available_prefixes_in_prefix(42, &body)
.await;
mock4.assert();
let mock5 = server.mock(|when, then| {
when.method(GET)
.path("/api/ipam/ip-ranges/10/available-ips/");
then.status(200).json_body(json!([]));
});
let _ = client.ipam().available_ips_in_range(10).await;
mock5.assert();
let mock6 = server.mock(|when, then| {
when.method(POST)
.path("/api/ipam/ip-ranges/10/available-ips/");
then.status(201).json_body(json!([]));
});
let body = vec![json!({})];
let _ = client.ipam().create_available_ips_in_range(10, &body).await;
mock6.assert();
let mock7 = server.mock(|when, then| {
when.method(GET)
.path("/api/ipam/vlan-groups/5/available-vlans/");
then.status(200).json_body(json!([]));
});
let _ = client.ipam().available_vlans_in_group(5).await;
mock7.assert();
let mock8 = server.mock(|when, then| {
when.method(POST)
.path("/api/ipam/vlan-groups/5/available-vlans/");
then.status(201).json_body(json!([]));
});
let body = vec![json!({"name": "test-vlan"})];
let _ = client
.ipam()
.create_available_vlans_in_group(5, &body)
.await;
mock8.assert();
let mock9 = server.mock(|when, then| {
when.method(GET)
.path("/api/ipam/asn-ranges/3/available-asns/");
then.status(200).json_body(json!([]));
});
let _ = client.ipam().available_asns_in_range(3).await;
mock9.assert();
let mock10 = server.mock(|when, then| {
when.method(POST)
.path("/api/ipam/asn-ranges/3/available-asns/");
then.status(201).json_body(json!([]));
});
let body = vec![json!({})];
let _ = client.ipam().create_available_asns_in_range(3, &body).await;
mock10.assert();
}
}