#![allow(non_snake_case)]
#![allow(missing_docs)]
use std::marker::PhantomData;
use std::net;
use std::ops::Not;
use chrono::{DateTime, FixedOffset};
use osauth::common::empty_as_default;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use super::super::common::{NetworkRef, SecurityGroupRef};
use super::super::Result;
use crate::session::Session;
protocol_enum! {
#[doc = "IP protocol version."]
enum IpVersion: u8 {
V4 = 4,
V6 = 6
}
}
protocol_enum! {
#[doc = "Network IP protocol."]
enum NetworkProtocol {
TCP = "tcp",
UDP = "udp"
}
}
protocol_enum! {
#[doc = "Possible network statuses."]
enum NetworkStatus {
Active = "ACTIVE",
Down = "DOWN",
Building = "BUILD",
Error = "ERROR"
}
}
protocol_enum! {
#[doc = "Available sort keys."]
enum NetworkSortKey {
CreatedAt = "created_at",
Id = "id",
Name = "name",
UpdatedAt = "updated_at"
}
}
protocol_enum! {
#[doc = "Possible floating IP statuses."]
enum FloatingIpStatus {
Active = "ACTIVE",
Down = "DOWN",
Error = "ERROR"
}
}
protocol_enum! {
#[doc = "Available sort keys."]
enum FloatingIpSortKey {
FixedIpAddress = "fixed_ip_address",
FloatingIpAddress = "floating_ip_address",
FloatingNetworkId = "floating_network_id",
Id = "id",
RouterId = "router_id",
Status = "status"
}
}
impl Default for NetworkSortKey {
fn default() -> NetworkSortKey {
NetworkSortKey::CreatedAt
}
}
protocol_enum! {
#[doc = "Available sort keys."]
enum PortSortKey {
AdminStateUp = "admin_state_up",
DeviceId = "device_id",
DeviceOwner = "device_owner",
Id = "id",
MacAddress = "mac_address",
Name = "name",
NetworkId = "network_id",
Status = "status"
}
}
protocol_enum! {
#[doc = "Available sort keys."]
enum RouterSortKey {
AdminStateUp = "admin_state_up",
FlavorId = "flavor_id",
Id = "id",
Name = "name",
ProjectId = "project_id",
Status = "status"
}
}
protocol_enum! {
#[doc = "Possible router statuses."]
enum RouterStatus {
Active = "ACTIVE",
Allocating = "ALLOCATING",
Error = "ERROR"
}
}
protocol_enum! {
#[doc = "Available sort keys."]
enum SubnetSortKey {
Cidr = "cidr",
DhcpEnabled = "enable_dhcp",
GatewayIp = "gateway_ip",
Id = "id",
IpVersion = "ip_version",
Ipv6AddressMode = "ipv6_address_mode",
Ipv6RouterAdvertisementMode = "ipv6_ra_mode",
Name = "name",
NetworkId = "network_id"
}
}
protocol_enum! {
#[doc = "IPv6 modes for assigning IP addresses."]
enum Ipv6Mode {
DhcpStateful = "dhcpv6-stateful",
DhcpStateless = "dhcpv6-stateless",
Slaac = "slaac"
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Network {
pub admin_state_up: bool,
#[serde(default, skip_serializing)]
pub availability_zones: Vec<String>,
#[serde(default, skip_serializing)]
pub created_at: Option<DateTime<FixedOffset>>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub description: Option<String>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub dns_domain: Option<String>,
#[serde(rename = "router:external", skip_serializing_if = "Option::is_none")]
pub external: Option<bool>,
#[serde(skip_serializing)]
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub is_default: Option<bool>,
#[serde(default, skip_serializing)]
pub l2_adjacency: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtu: Option<u32>,
#[serde(
deserialize_with = "empty_as_default",
skip_serializing_if = "Option::is_none"
)]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub port_security_enabled: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
#[serde(default, skip_serializing_if = "Not::not")]
pub shared: bool,
#[serde(skip_serializing)]
pub status: NetworkStatus,
#[serde(default, skip_serializing)]
pub updated_at: Option<DateTime<FixedOffset>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vlan_transparent: Option<bool>,
}
impl Default for Network {
fn default() -> Network {
Network {
admin_state_up: true,
availability_zones: Vec::new(),
created_at: None,
description: None,
dns_domain: None,
external: None,
id: String::new(),
is_default: None,
l2_adjacency: None,
mtu: None,
name: None,
port_security_enabled: None,
project_id: None,
shared: false,
status: NetworkStatus::Active,
updated_at: None,
vlan_transparent: None,
}
}
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct NetworkUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub admin_state_up: Option<bool>,
#[serde(rename = "router:external", skip_serializing_if = "Option::is_none")]
pub external: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns_domain: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_default: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mtu: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port_security_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shared: Option<bool>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NetworkRoot {
pub network: Network,
}
#[derive(Debug, Clone, Serialize)]
pub struct NetworkUpdateRoot {
pub network: NetworkUpdate,
}
#[derive(Debug, Clone, Deserialize)]
pub struct NetworksRoot {
pub networks: Vec<Network>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PortExtraDhcpOption {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ip_version: Option<IpVersion>,
#[serde(rename = "opt_name")]
pub name: String,
#[serde(rename = "opt_value")]
pub value: String,
#[doc(hidden)]
#[serde(skip)]
pub __nonexhaustive: PhantomData<()>,
}
impl PortExtraDhcpOption {
pub fn new<S1, S2>(name: S1, value: S2) -> PortExtraDhcpOption
where
S1: Into<String>,
S2: Into<String>,
{
PortExtraDhcpOption {
ip_version: None,
name: name.into(),
value: value.into(),
__nonexhaustive: PhantomData,
}
}
pub fn new_with_ip_version<S1, S2>(
name: S1,
value: S2,
ip_version: IpVersion,
) -> PortExtraDhcpOption
where
S1: Into<String>,
S2: Into<String>,
{
PortExtraDhcpOption {
ip_version: Some(ip_version),
name: name.into(),
value: value.into(),
__nonexhaustive: PhantomData,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FixedIp {
#[serde(skip_serializing_if = "::std::net::IpAddr::is_unspecified")]
pub ip_address: net::IpAddr,
#[serde(skip_serializing_if = "String::is_empty")]
pub subnet_id: String,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Ord, PartialOrd, Hash)]
pub struct MacAddress(macaddr::MacAddr6);
impl MacAddress {
pub fn is_nil(&self) -> bool {
self.0.is_nil()
}
}
impl std::fmt::Display for MacAddress {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::ops::Deref for MacAddress {
type Target = macaddr::MacAddr6;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::str::FromStr for MacAddress {
type Err = macaddr::ParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(Self(s.parse::<macaddr::MacAddr6>()?))
}
}
impl Serialize for MacAddress {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for MacAddress {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Copy)]
pub struct AllowedAddressPair {
pub ip_address: net::IpAddr,
#[serde(skip_serializing_if = "Option::is_none")]
pub mac_address: Option<MacAddress>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Port {
pub admin_state_up: bool,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub allowed_address_pairs: Vec<AllowedAddressPair>,
#[serde(default, skip_serializing)]
pub created_at: Option<DateTime<FixedOffset>>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub description: Option<String>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub device_id: Option<String>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub device_owner: Option<String>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub dns_domain: Option<String>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub dns_name: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub extra_dhcp_opts: Vec<PortExtraDhcpOption>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fixed_ips: Vec<FixedIp>,
#[serde(skip_serializing)]
pub id: String,
#[serde(skip_serializing_if = "MacAddress::is_nil")]
pub mac_address: MacAddress,
#[serde(
deserialize_with = "empty_as_default",
skip_serializing_if = "Option::is_none"
)]
pub name: Option<String>,
pub network_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub security_groups: Vec<SecurityGroupRef>,
#[serde(skip_serializing)]
pub status: NetworkStatus,
#[serde(default, skip_serializing)]
pub updated_at: Option<DateTime<FixedOffset>>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct PortUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub admin_state_up: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_owner: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns_domain: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra_dhcp_opts: Option<Vec<PortExtraDhcpOption>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fixed_ips: Option<Vec<FixedIp>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mac_address: Option<MacAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub security_groups: Option<Vec<SecurityGroupRef>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PortRoot {
pub port: Port,
}
#[derive(Debug, Clone, Serialize)]
pub struct PortUpdateRoot {
pub port: PortUpdate,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PortsRoot {
pub ports: Vec<Port>,
}
protocol_enum! {
#[doc = "Allowed conntrack helpers as defined [here](https://opendev.org/openstack/neutron/src/branch/master/neutron/conf/extensions/conntrack_helper.py)"]
enum Helper {
Amanda = "amanda",
FTP = "ftp",
H323 = "h323",
IRC = "irc",
NetbiosNS = "netbios-ns",
PPTP = "pptp",
SANE = "sane",
SIP = "sip",
SNMP = "snmp",
TFTP = "tftp"
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq)]
pub struct ConntrackHelper {
pub helper: Helper,
pub protocol: NetworkProtocol,
pub port: u16,
}
#[non_exhaustive]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ExternalGateway {
pub network_id: NetworkRef,
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_snat: Option<bool>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub external_fixed_ips: Vec<FixedIp>,
}
impl ExternalGateway {
pub fn new<N: Into<NetworkRef>>(external_network: N) -> ExternalGateway {
ExternalGateway {
network_id: external_network.into(),
enable_snat: None,
external_fixed_ips: Vec::new(),
}
}
pub(crate) async fn into_verified(self, session: &Session) -> Result<Self> {
Ok(ExternalGateway {
network_id: self.network_id.into_verified(session).await?,
..self
})
}
}
#[derive(Debug, Serialize)]
pub struct Routes {
pub routes: Vec<HostRoute>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Router {
pub admin_state_up: bool,
#[serde(default, skip_serializing)]
pub availability_zone_hints: Vec<String>,
#[serde(default, skip_serializing)]
pub availability_zones: Vec<String>,
#[serde(default, skip_serializing)]
pub conntrack_helpers: Vec<ConntrackHelper>,
#[serde(default, skip_serializing)]
pub created_at: Option<DateTime<FixedOffset>>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub distributed: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "external_gateway_info"
)]
pub external_gateway: Option<ExternalGateway>,
#[serde(skip_serializing_if = "Option::is_none")]
pub flavor_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ha: Option<bool>,
#[serde(skip_serializing)]
pub id: String,
#[serde(
deserialize_with = "empty_as_default",
skip_serializing_if = "Option::is_none"
)]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
#[serde(default, skip_serializing)]
pub revision_number: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub routes: Option<Vec<HostRoute>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_type_id: Option<String>,
#[serde(skip_serializing)]
pub status: RouterStatus,
#[serde(skip_serializing)]
pub tags: Option<Vec<String>>,
#[serde(default, skip_serializing)]
pub updated_at: Option<DateTime<FixedOffset>>,
}
impl Default for Router {
fn default() -> Router {
Router {
admin_state_up: true,
availability_zones: vec![],
availability_zone_hints: vec![],
created_at: None,
conntrack_helpers: vec![],
description: None,
distributed: None,
external_gateway: None,
flavor_id: None,
ha: None,
id: String::new(),
name: None,
project_id: None,
revision_number: None,
routes: None,
service_type_id: None,
status: RouterStatus::Active,
tags: None,
updated_at: None,
}
}
}
impl Router {
pub(crate) async fn into_verified(self, session: &Session) -> Result<Self> {
Ok(Router {
external_gateway: match self.external_gateway {
Some(gw) => Some(gw.into_verified(session).await?),
None => None,
},
..self
})
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RouterRoot {
pub router: Router,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct RouterUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub admin_state_up: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub distributed: Option<bool>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "external_gateway_info"
)]
pub external_gateway: Option<ExternalGateway>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ha: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub routes: Option<Vec<HostRoute>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct RouterUpdateRoot {
pub router: RouterUpdate,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RoutersRoot {
pub routers: Vec<Router>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AllocationPool {
pub start: net::IpAddr,
pub end: net::IpAddr,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct HostRoute {
pub destination: ipnet::IpNet,
#[serde(rename = "nexthop")]
pub next_hop: net::IpAddr,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Subnet {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allocation_pools: Vec<AllocationPool>,
pub cidr: ipnet::IpNet,
#[serde(default, skip_serializing)]
pub created_at: Option<DateTime<FixedOffset>>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub description: Option<String>,
#[serde(rename = "enable_dhcp")]
pub dhcp_enabled: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dns_nameservers: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gateway_ip: Option<net::IpAddr>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub host_routes: Vec<HostRoute>,
#[serde(skip_serializing)]
pub id: String,
pub ip_version: IpVersion,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ipv6_address_mode: Option<Ipv6Mode>,
#[serde(
default,
rename = "ipv6_ra_mode",
skip_serializing_if = "Option::is_none"
)]
pub ipv6_router_advertisement_mode: Option<Ipv6Mode>,
#[serde(
deserialize_with = "empty_as_default",
skip_serializing_if = "Option::is_none"
)]
pub name: Option<String>,
pub network_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
#[serde(default, skip_serializing)]
pub updated_at: Option<DateTime<FixedOffset>>,
}
impl Subnet {
pub(crate) fn empty(cidr: ipnet::IpNet) -> Subnet {
Subnet {
allocation_pools: Vec::new(),
cidr,
created_at: None,
description: None,
dhcp_enabled: true,
dns_nameservers: Vec::new(),
gateway_ip: None,
host_routes: Vec::new(),
id: String::new(),
ip_version: match cidr {
ipnet::IpNet::V4(..) => IpVersion::V4,
ipnet::IpNet::V6(..) => IpVersion::V6,
},
ipv6_address_mode: None,
ipv6_router_advertisement_mode: None,
name: None,
network_id: String::new(),
project_id: None,
updated_at: None,
}
}
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct SubnetUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub allocation_pools: Option<Vec<AllocationPool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "enable_dhcp", skip_serializing_if = "Option::is_none")]
pub dhcp_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dns_nameservers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway_ip: Option<net::IpAddr>,
#[serde(skip_serializing_if = "Option::is_none")]
pub host_routes: Option<Vec<HostRoute>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SubnetRoot {
pub subnet: Subnet,
}
#[derive(Debug, Clone, Serialize)]
pub struct SubnetUpdateRoot {
pub subnet: SubnetUpdate,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SubnetsRoot {
pub subnets: Vec<Subnet>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct PortForwarding {
pub external_port: u16,
pub internal_ip_address: net::IpAddr,
pub internal_port: u16,
pub protocol: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FloatingIp {
#[serde(default, skip_serializing)]
pub created_at: Option<DateTime<FixedOffset>>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub description: Option<String>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub dns_domain: Option<String>,
#[serde(
deserialize_with = "empty_as_default",
default,
skip_serializing_if = "Option::is_none"
)]
pub dns_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fixed_ip_address: Option<net::IpAddr>,
#[serde(skip_serializing_if = "::std::net::IpAddr::is_unspecified")]
pub floating_ip_address: net::IpAddr,
pub floating_network_id: String,
#[serde(skip_serializing)]
pub id: String,
#[serde(default)]
pub port_id: Option<String>,
#[serde(default, skip_serializing)]
pub port_forwardings: Vec<PortForwarding>,
#[serde(default, skip_serializing)]
pub router_id: Option<String>,
#[serde(skip_serializing)]
pub status: FloatingIpStatus,
#[serde(skip_deserializing, skip_serializing_if = "Option::is_none")]
pub subnet_id: Option<String>,
#[serde(default, skip_serializing)]
pub updated_at: Option<DateTime<FixedOffset>>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct FloatingIpUpdate {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fixed_ip_address: Option<net::IpAddr>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port_id: Option<Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FloatingIpRoot {
pub floatingip: FloatingIp,
}
#[derive(Debug, Clone, Serialize)]
pub struct FloatingIpUpdateRoot {
pub floatingip: FloatingIpUpdate,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FloatingIpsRoot {
pub floatingips: Vec<FloatingIp>,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_macaddr() {
let a: AllowedAddressPair = serde_json::from_value(
serde_json::json!({"ip_address":"0.0.0.0", "mac_address":"ab:aa:aa:aa:aa:aa"}),
)
.expect("Could not parse this JSON");
assert_eq!(
a.mac_address.expect("MAC address is missing").to_string(),
"AB:AA:AA:AA:AA:AA"
);
assert_eq!(
serde_json::to_value(&a)
.expect("Could not serialize")
.get("mac_address")
.expect("No mac_address")
.as_str()
.expect("No string found"),
"AB:AA:AA:AA:AA:AA"
);
let a: AllowedAddressPair =
serde_json::from_value(serde_json::json!({"ip_address":"0.0.0.0"}))
.expect("Cannot parse this JSON");
assert_eq!(a.mac_address, None);
}
}