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 ConsolePort = crate::models::ConsolePort;
pub type ConsoleServerPort = crate::models::ConsoleServerPort;
pub type Interface = crate::models::Interface;
pub type PowerPort = crate::models::PowerPort;
pub type PowerOutlet = crate::models::PowerOutlet;
pub type PowerFeed = crate::models::PowerFeed;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateDeviceRequest {
pub name: String,
pub device_type: i32,
pub role: i32,
pub site: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serial: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub asset_tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<i32>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateDeviceRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_type: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub site: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub serial: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub asset_tag: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchSiteFieldsRequest {
#[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 PatchDeviceFieldsRequest {
#[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>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub local_context_data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchInterfaceFieldsRequest {
#[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 Device = crate::models::DeviceWithConfigContext;
pub type ConnectedDevice = crate::models::Device;
pub type CableTerminationsApi = Resource<crate::models::CableTermination>;
pub type CablesApi = Resource<crate::models::Cable>;
pub type ConsolePortTemplatesApi = Resource<crate::models::ConsolePortTemplate>;
pub type ConsolePortsApi = Resource<crate::models::ConsolePort>;
pub type ConsoleServerPortTemplatesApi = Resource<crate::models::ConsoleServerPortTemplate>;
pub type ConsoleServerPortsApi = Resource<crate::models::ConsoleServerPort>;
pub type DeviceBayTemplatesApi = Resource<crate::models::DeviceBayTemplate>;
pub type DeviceBaysApi = Resource<crate::models::DeviceBay>;
pub type DeviceRolesApi = Resource<crate::models::DeviceRole>;
pub type DeviceTypesApi = Resource<crate::models::DeviceType>;
pub type DevicesApi = Resource<crate::models::DeviceWithConfigContext>;
pub type FrontPortTemplatesApi = Resource<crate::models::FrontPortTemplate>;
pub type FrontPortsApi = Resource<crate::models::FrontPort>;
pub type InterfaceTemplatesApi = Resource<crate::models::InterfaceTemplate>;
pub type InterfacesApi = Resource<crate::models::Interface>;
pub type InventoryItemRolesApi = Resource<crate::models::InventoryItemRole>;
pub type InventoryItemTemplatesApi = Resource<crate::models::InventoryItemTemplate>;
pub type InventoryItemsApi = Resource<crate::models::InventoryItem>;
pub type LocationsApi = Resource<crate::models::Location>;
pub type MacAddressesApi = Resource<crate::models::MacAddress>;
pub type ManufacturersApi = Resource<crate::models::Manufacturer>;
pub type ModuleBayTemplatesApi = Resource<crate::models::ModuleBayTemplate>;
pub type ModuleBaysApi = Resource<crate::models::ModuleBay>;
pub type ModuleTypeProfilesApi = Resource<crate::models::ModuleTypeProfile>;
pub type ModuleTypesApi = Resource<crate::models::ModuleType>;
pub type ModulesApi = Resource<crate::models::Module>;
pub type PlatformsApi = Resource<crate::models::Platform>;
pub type PowerFeedsApi = Resource<crate::models::PowerFeed>;
pub type PowerOutletTemplatesApi = Resource<crate::models::PowerOutletTemplate>;
pub type PowerOutletsApi = Resource<crate::models::PowerOutlet>;
pub type PowerPanelsApi = Resource<crate::models::PowerPanel>;
pub type PowerPortTemplatesApi = Resource<crate::models::PowerPortTemplate>;
pub type PowerPortsApi = Resource<crate::models::PowerPort>;
pub type RackReservationsApi = Resource<crate::models::RackReservation>;
pub type RackRolesApi = Resource<crate::models::RackRole>;
pub type RackTypesApi = Resource<crate::models::RackType>;
pub type RacksApi = Resource<crate::models::Rack>;
pub type RearPortTemplatesApi = Resource<crate::models::RearPortTemplate>;
pub type RearPortsApi = Resource<crate::models::RearPort>;
pub type RegionsApi = Resource<crate::models::Region>;
pub type SiteGroupsApi = Resource<crate::models::SiteGroup>;
pub type SitesApi = Resource<crate::models::Site>;
pub type VirtualChassisApi = Resource<crate::models::VirtualChassis>;
pub type VirtualDeviceContextsApi = Resource<crate::models::VirtualDeviceContext>;
#[derive(Clone)]
pub struct DcimApi {
client: Client,
}
impl DcimApi {
pub(crate) fn new(client: Client) -> Self {
Self { client }
}
pub async fn connected_device(
&self,
peer_device: &str,
peer_interface: &str,
) -> crate::error::Result<Vec<ConnectedDevice>> {
#[derive(Serialize)]
struct ConnectedDeviceQuery<'a> {
peer_device: &'a str,
peer_interface: &'a str,
}
let query = ConnectedDeviceQuery {
peer_device,
peer_interface,
};
self.client
.get_with_params("dcim/connected-device/", &query)
.await
}
pub fn cable_terminations(&self) -> CableTerminationsApi {
Resource::new(self.client.clone(), "dcim/cable-terminations/")
}
pub fn cables(&self) -> CablesApi {
Resource::new(self.client.clone(), "dcim/cables/")
}
pub fn console_port_templates(&self) -> ConsolePortTemplatesApi {
Resource::new(self.client.clone(), "dcim/console-port-templates/")
}
pub fn console_ports(&self) -> ConsolePortsApi {
Resource::new(self.client.clone(), "dcim/console-ports/")
}
pub fn console_server_port_templates(&self) -> ConsoleServerPortTemplatesApi {
Resource::new(self.client.clone(), "dcim/console-server-port-templates/")
}
pub fn console_server_ports(&self) -> ConsoleServerPortsApi {
Resource::new(self.client.clone(), "dcim/console-server-ports/")
}
pub fn device_bay_templates(&self) -> DeviceBayTemplatesApi {
Resource::new(self.client.clone(), "dcim/device-bay-templates/")
}
pub fn device_bays(&self) -> DeviceBaysApi {
Resource::new(self.client.clone(), "dcim/device-bays/")
}
pub fn device_roles(&self) -> DeviceRolesApi {
Resource::new(self.client.clone(), "dcim/device-roles/")
}
pub fn device_types(&self) -> DeviceTypesApi {
Resource::new(self.client.clone(), "dcim/device-types/")
}
pub fn devices(&self) -> DevicesApi {
Resource::new(self.client.clone(), "dcim/devices/")
}
pub fn front_port_templates(&self) -> FrontPortTemplatesApi {
Resource::new(self.client.clone(), "dcim/front-port-templates/")
}
pub fn front_ports(&self) -> FrontPortsApi {
Resource::new(self.client.clone(), "dcim/front-ports/")
}
pub fn interface_templates(&self) -> InterfaceTemplatesApi {
Resource::new(self.client.clone(), "dcim/interface-templates/")
}
pub fn interfaces(&self) -> InterfacesApi {
Resource::new(self.client.clone(), "dcim/interfaces/")
}
pub fn inventory_item_roles(&self) -> InventoryItemRolesApi {
Resource::new(self.client.clone(), "dcim/inventory-item-roles/")
}
pub fn inventory_item_templates(&self) -> InventoryItemTemplatesApi {
Resource::new(self.client.clone(), "dcim/inventory-item-templates/")
}
pub fn inventory_items(&self) -> InventoryItemsApi {
Resource::new(self.client.clone(), "dcim/inventory-items/")
}
pub fn locations(&self) -> LocationsApi {
Resource::new(self.client.clone(), "dcim/locations/")
}
pub fn mac_addresses(&self) -> MacAddressesApi {
Resource::new(self.client.clone(), "dcim/mac-addresses/")
}
pub fn manufacturers(&self) -> ManufacturersApi {
Resource::new(self.client.clone(), "dcim/manufacturers/")
}
pub fn module_bay_templates(&self) -> ModuleBayTemplatesApi {
Resource::new(self.client.clone(), "dcim/module-bay-templates/")
}
pub fn module_bays(&self) -> ModuleBaysApi {
Resource::new(self.client.clone(), "dcim/module-bays/")
}
pub fn module_type_profiles(&self) -> ModuleTypeProfilesApi {
Resource::new(self.client.clone(), "dcim/module-type-profiles/")
}
pub fn module_types(&self) -> ModuleTypesApi {
Resource::new(self.client.clone(), "dcim/module-types/")
}
pub fn modules(&self) -> ModulesApi {
Resource::new(self.client.clone(), "dcim/modules/")
}
pub fn platforms(&self) -> PlatformsApi {
Resource::new(self.client.clone(), "dcim/platforms/")
}
pub fn power_feeds(&self) -> PowerFeedsApi {
Resource::new(self.client.clone(), "dcim/power-feeds/")
}
pub fn power_outlet_templates(&self) -> PowerOutletTemplatesApi {
Resource::new(self.client.clone(), "dcim/power-outlet-templates/")
}
pub fn power_outlets(&self) -> PowerOutletsApi {
Resource::new(self.client.clone(), "dcim/power-outlets/")
}
pub fn power_panels(&self) -> PowerPanelsApi {
Resource::new(self.client.clone(), "dcim/power-panels/")
}
pub fn power_port_templates(&self) -> PowerPortTemplatesApi {
Resource::new(self.client.clone(), "dcim/power-port-templates/")
}
pub fn power_ports(&self) -> PowerPortsApi {
Resource::new(self.client.clone(), "dcim/power-ports/")
}
pub fn rack_reservations(&self) -> RackReservationsApi {
Resource::new(self.client.clone(), "dcim/rack-reservations/")
}
pub fn rack_roles(&self) -> RackRolesApi {
Resource::new(self.client.clone(), "dcim/rack-roles/")
}
pub fn rack_types(&self) -> RackTypesApi {
Resource::new(self.client.clone(), "dcim/rack-types/")
}
pub fn racks(&self) -> RacksApi {
Resource::new(self.client.clone(), "dcim/racks/")
}
pub fn rear_port_templates(&self) -> RearPortTemplatesApi {
Resource::new(self.client.clone(), "dcim/rear-port-templates/")
}
pub fn rear_ports(&self) -> RearPortsApi {
Resource::new(self.client.clone(), "dcim/rear-ports/")
}
pub fn regions(&self) -> RegionsApi {
Resource::new(self.client.clone(), "dcim/regions/")
}
pub fn site_groups(&self) -> SiteGroupsApi {
Resource::new(self.client.clone(), "dcim/site-groups/")
}
pub fn sites(&self) -> SitesApi {
Resource::new(self.client.clone(), "dcim/sites/")
}
pub fn virtual_chassis(&self) -> VirtualChassisApi {
Resource::new(self.client.clone(), "dcim/virtual-chassis/")
}
pub fn virtual_device_contexts(&self) -> VirtualDeviceContextsApi {
Resource::new(self.client.clone(), "dcim/virtual-device-contexts/")
}
pub async fn trace_interface(&self, id: u64) -> Result<Interface> {
self.client
.get(&format!("dcim/interfaces/{}/trace/", id))
.await
}
pub async fn trace_console_port(&self, id: u64) -> Result<ConsolePort> {
self.client
.get(&format!("dcim/console-ports/{}/trace/", id))
.await
}
pub async fn trace_console_server_port(&self, id: u64) -> Result<ConsoleServerPort> {
self.client
.get(&format!("dcim/console-server-ports/{}/trace/", id))
.await
}
pub async fn trace_power_port(&self, id: u64) -> Result<PowerPort> {
self.client
.get(&format!("dcim/power-ports/{}/trace/", id))
.await
}
pub async fn trace_power_outlet(&self, id: u64) -> Result<PowerOutlet> {
self.client
.get(&format!("dcim/power-outlets/{}/trace/", id))
.await
}
pub async fn trace_power_feed(&self, id: u64) -> Result<PowerFeed> {
self.client
.get(&format!("dcim/power-feeds/{}/trace/", id))
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ClientConfig;
use httpmock::prelude::*;
use serde::Serialize;
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 dcim_accessors_return_expected_paths() {
let api = DcimApi::new(test_client());
assert_path(api.cable_terminations(), "dcim/cable-terminations/");
assert_path(api.cables(), "dcim/cables/");
assert_path(api.console_port_templates(), "dcim/console-port-templates/");
assert_path(api.console_ports(), "dcim/console-ports/");
assert_path(
api.console_server_port_templates(),
"dcim/console-server-port-templates/",
);
assert_path(api.console_server_ports(), "dcim/console-server-ports/");
assert_path(api.device_bay_templates(), "dcim/device-bay-templates/");
assert_path(api.device_bays(), "dcim/device-bays/");
assert_path(api.device_roles(), "dcim/device-roles/");
assert_path(api.device_types(), "dcim/device-types/");
assert_path(api.devices(), "dcim/devices/");
assert_path(api.front_port_templates(), "dcim/front-port-templates/");
assert_path(api.front_ports(), "dcim/front-ports/");
assert_path(api.interface_templates(), "dcim/interface-templates/");
assert_path(api.interfaces(), "dcim/interfaces/");
assert_path(api.inventory_item_roles(), "dcim/inventory-item-roles/");
assert_path(
api.inventory_item_templates(),
"dcim/inventory-item-templates/",
);
assert_path(api.inventory_items(), "dcim/inventory-items/");
assert_path(api.locations(), "dcim/locations/");
assert_path(api.mac_addresses(), "dcim/mac-addresses/");
assert_path(api.manufacturers(), "dcim/manufacturers/");
assert_path(api.module_bay_templates(), "dcim/module-bay-templates/");
assert_path(api.module_bays(), "dcim/module-bays/");
assert_path(api.module_type_profiles(), "dcim/module-type-profiles/");
assert_path(api.module_types(), "dcim/module-types/");
assert_path(api.modules(), "dcim/modules/");
assert_path(api.platforms(), "dcim/platforms/");
assert_path(api.power_feeds(), "dcim/power-feeds/");
assert_path(api.power_outlet_templates(), "dcim/power-outlet-templates/");
assert_path(api.power_outlets(), "dcim/power-outlets/");
assert_path(api.power_panels(), "dcim/power-panels/");
assert_path(api.power_port_templates(), "dcim/power-port-templates/");
assert_path(api.power_ports(), "dcim/power-ports/");
assert_path(api.rack_reservations(), "dcim/rack-reservations/");
assert_path(api.rack_roles(), "dcim/rack-roles/");
assert_path(api.rack_types(), "dcim/rack-types/");
assert_path(api.racks(), "dcim/racks/");
assert_path(api.rear_port_templates(), "dcim/rear-port-templates/");
assert_path(api.rear_ports(), "dcim/rear-ports/");
assert_path(api.regions(), "dcim/regions/");
assert_path(api.site_groups(), "dcim/site-groups/");
assert_path(api.sites(), "dcim/sites/");
assert_path(api.virtual_chassis(), "dcim/virtual-chassis/");
assert_path(
api.virtual_device_contexts(),
"dcim/virtual-device-contexts/",
);
}
#[test]
fn serialize_device_requests() {
let create = CreateDeviceRequest {
name: "device1".to_string(),
device_type: 1,
role: 2,
site: 3,
status: Some("active".to_string()),
serial: None,
asset_tag: None,
tags: None,
};
let value = serde_json::to_value(&create).unwrap();
assert_eq!(value["name"], "device1");
assert_eq!(value["device_type"], 1);
assert_eq!(value["role"], 2);
assert_eq!(value["site"], 3);
assert_eq!(value["status"], "active");
let update = UpdateDeviceRequest {
name: None,
device_type: None,
role: None,
site: None,
status: Some("offline".to_string()),
serial: Some("SN1".to_string()),
asset_tag: None,
};
let value = serde_json::to_value(&update).unwrap();
assert_eq!(value["status"], "offline");
assert_eq!(value["serial"], "SN1");
assert!(value.get("name").is_none());
}
#[test]
fn serialize_projection_patch_requests() {
let mut fields = HashMap::new();
fields.insert("fabric".to_string(), json!("fra1"));
let tags = vec![crate::models::NestedTag::new(
"EVPN Fabric".to_string(),
"evpn-fabric".to_string(),
)];
let site = PatchSiteFieldsRequest {
custom_fields: Some(fields.clone()),
tags: Some(tags.clone()),
};
let value = serde_json::to_value(&site).unwrap();
assert_eq!(value["custom_fields"]["fabric"], "fra1");
assert_eq!(value["tags"][0]["slug"], "evpn-fabric");
let device = PatchDeviceFieldsRequest {
custom_fields: Some(fields),
tags: Some(tags),
local_context_data: Some(json!({"alembic": {"role_hint": "leaf"}})),
};
let value = serde_json::to_value(&device).unwrap();
assert_eq!(value["custom_fields"]["fabric"], "fra1");
assert_eq!(value["tags"][0]["name"], "EVPN Fabric");
assert_eq!(value["local_context_data"]["alembic"]["role_hint"], "leaf");
let interface = PatchInterfaceFieldsRequest {
custom_fields: None,
tags: None,
};
let value = serde_json::to_value(&interface).unwrap();
assert!(value.get("custom_fields").is_none());
assert!(value.get("tags").is_none());
}
#[test]
fn connected_device_query_serializes() {
#[derive(Serialize)]
struct Query<'a> {
peer_device: &'a str,
peer_interface: &'a str,
}
let query = Query {
peer_device: "leaf-01",
peer_interface: "Ethernet1",
};
let encoded = serde_urlencoded::to_string(&query).unwrap();
assert!(encoded.contains("peer_device=leaf-01"));
assert!(encoded.contains("peer_interface=Ethernet1"));
}
#[cfg_attr(miri, ignore)]
#[tokio::test]
async fn trace_endpoints_use_expected_paths() {
let server = MockServer::start();
let client = mock_client(&server);
let mock1 = server.mock(|when, then| {
when.method(GET).path("/api/dcim/interfaces/42/trace/");
then.status(200).json_body(json!({}));
});
let _ = client.dcim().trace_interface(42).await;
mock1.assert();
let mock2 = server.mock(|when, then| {
when.method(GET).path("/api/dcim/console-ports/5/trace/");
then.status(200).json_body(json!({}));
});
let _ = client.dcim().trace_console_port(5).await;
mock2.assert();
let mock3 = server.mock(|when, then| {
when.method(GET)
.path("/api/dcim/console-server-ports/3/trace/");
then.status(200).json_body(json!({}));
});
let _ = client.dcim().trace_console_server_port(3).await;
mock3.assert();
let mock4 = server.mock(|when, then| {
when.method(GET).path("/api/dcim/power-ports/7/trace/");
then.status(200).json_body(json!({}));
});
let _ = client.dcim().trace_power_port(7).await;
mock4.assert();
let mock5 = server.mock(|when, then| {
when.method(GET).path("/api/dcim/power-outlets/2/trace/");
then.status(200).json_body(json!({}));
});
let _ = client.dcim().trace_power_outlet(2).await;
mock5.assert();
let mock6 = server.mock(|when, then| {
when.method(GET).path("/api/dcim/power-feeds/9/trace/");
then.status(200).json_body(json!({}));
});
let _ = client.dcim().trace_power_feed(9).await;
mock6.assert();
}
}