diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0c35f5a..adbde45 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -23,6 +23,6 @@ jobs: - name: Build run: cargo build --verbose - name: Run tests - run: cargo test --verbose + run: cargo test --verbose --all-features - name: Build docs run: cargo doc --verbose diff --git a/Cargo.toml b/Cargo.toml index 5f7e589..fe702f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ schemars = { version = "0.8.10", optional = true } [dev-dependencies] serde_json = "1.0" -serde_derive = "1" criterion = {version = "0.3.4", features= ["html_reports"]} +does-it-json = "0.0.3" [badges] travis-ci = { repository = "achanda/ipnetwork" } diff --git a/src/ipv4.rs b/src/ipv4.rs index c681b2c..d162fb6 100644 --- a/src/ipv4.rs +++ b/src/ipv4.rs @@ -1,11 +1,10 @@ use crate::common::{cidr_parts, parse_prefix, IpNetworkError}; -use std::{fmt, net::Ipv4Addr, str::FromStr, convert::TryFrom}; +use std::{convert::TryFrom, fmt, net::Ipv4Addr, str::FromStr}; const IPV4_BITS: u8 = 32; /// Represents a network range where the IP addresses are of v4 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Ipv4Network { addr: Ipv4Addr, prefix: u8, @@ -32,6 +31,36 @@ impl serde::Serialize for Ipv4Network { } } +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for Ipv4Network { + fn schema_name() -> String { + "Ipv4Network".to_string() + } + + fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::String.into()), + string: Some(Box::new(schemars::schema::StringValidation { + pattern: Some( + concat!( + r#"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}"#, + r#"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"#, + r#"\/(3[0-2]|[0-2]?[0-9])$"#, + ) + .to_string(), + ), + ..Default::default() + })), + extensions: [("x-rust-type".to_string(), "ipnetwork::Ipv4Network".into())] + .iter() + .cloned() + .collect(), + ..Default::default() + } + .into() + } +} + impl Ipv4Network { /// Constructs a new `Ipv4Network` from any `Ipv4Addr` and a prefix denoting the network size. /// diff --git a/src/ipv6.rs b/src/ipv6.rs index dfce6c4..b2299df 100644 --- a/src/ipv6.rs +++ b/src/ipv6.rs @@ -1,12 +1,11 @@ use crate::common::{cidr_parts, parse_prefix, IpNetworkError}; -use std::{cmp, fmt, net::Ipv6Addr, str::FromStr, convert::TryFrom}; +use std::{cmp, convert::TryFrom, fmt, net::Ipv6Addr, str::FromStr}; const IPV6_BITS: u8 = 128; const IPV6_SEGMENT_BITS: u8 = 16; /// Represents a network range where the IP addresses are of v6 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Ipv6Network { addr: Ipv6Addr, prefix: u8, @@ -33,6 +32,46 @@ impl serde::Serialize for Ipv6Network { } } +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for Ipv6Network { + fn schema_name() -> String { + "Ipv6Network".to_string() + } + + fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::String.into()), + string: Some(Box::new(schemars::schema::StringValidation { + pattern: Some( + concat!( + r#"^("#, + r#"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}"#, + r#"|([0-9a-fA-F]{1,4}:){1,7}:"#, + r#"|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}"#, + r#"|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}"#, + r#"|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}"#, + r#"|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}"#, + r#"|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}"#, + r#"|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})"#, + r#"|:((:[0-9a-fA-F]{1,4}){1,7}|:)"#, + r#"|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}"#, + r#"|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"#, + r#"|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"#, + r#"")[/](12[0-8]|1[0-1][0-9]|[0-9]?[0-9])$"#, + ).to_string(), + ), + ..Default::default() + })), + extensions: [("x-rust-type".to_string(), "ipnetwork::Ipv6Network".into())] + .iter() + .cloned() + .collect(), + ..Default::default() + } + .into() + } +} + impl Ipv6Network { /// Constructs a new `Ipv6Network` from any `Ipv6Addr` and a prefix denoting the network size. /// diff --git a/src/lib.rs b/src/lib.rs index 546e902..8d3e353 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,14 @@ //! The `ipnetwork` crate provides a set of APIs to work with IP CIDRs in //! Rust. #![crate_type = "lib"] - #![deny( missing_debug_implementations, unsafe_code, unused_extern_crates, - unused_import_braces)] + unused_import_braces +)] -use std::{fmt, net::IpAddr, str::FromStr, convert::TryFrom}; +use std::{convert::TryFrom, fmt, net::IpAddr, str::FromStr}; mod common; mod ipv4; @@ -23,7 +23,6 @@ pub use crate::ipv6::{ipv6_mask_to_prefix, Ipv6Network}; /// Represents a generic network range. This type can have two variants: /// the v4 and the v6 case. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum IpNetwork { V4(Ipv4Network), V6(Ipv6Network), @@ -50,6 +49,74 @@ impl serde::Serialize for IpNetwork { } } +#[cfg(feature = "schemars")] +impl schemars::JsonSchema for IpNetwork { + fn schema_name() -> String { + "IpNetwork".to_string() + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + metadata: Some( + schemars::schema::Metadata { + ..Default::default() + } + .into(), + ), + subschemas: Some( + schemars::schema::SubschemaValidation { + one_of: Some(vec![ + schemars::schema::SchemaObject { + metadata: Some( + schemars::schema::Metadata { + title: Some("v4".to_string()), + ..Default::default() + } + .into(), + ), + subschemas: Some( + schemars::schema::SubschemaValidation { + all_of: Some(vec![gen.subschema_for::()]), + ..Default::default() + } + .into(), + ), + ..Default::default() + } + .into(), + schemars::schema::SchemaObject { + metadata: Some( + schemars::schema::Metadata { + title: Some("v6".to_string()), + ..Default::default() + } + .into(), + ), + subschemas: Some( + schemars::schema::SubschemaValidation { + all_of: Some(vec![gen.subschema_for::()]), + ..Default::default() + } + .into(), + ), + ..Default::default() + } + .into(), + ]), + ..Default::default() + } + .into(), + ), + extensions: [("x-rust-type".to_string(), "ipnetwork::IpNetwork".into())] + .iter() + .cloned() + .collect(), + ..Default::default() + } + .into() + } +} + /// Represents a generic network size. For IPv4, the max size is a u32 and for IPv6, it is a u128 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum NetworkSize { diff --git a/tests/test_json.rs b/tests/test_json.rs index 2ad039f..77f8709 100644 --- a/tests/test_json.rs +++ b/tests/test_json.rs @@ -3,7 +3,7 @@ #[cfg(test)] mod tests { use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; - use serde_derive::{Deserialize, Serialize}; + use serde::{Deserialize, Serialize}; use std::net::{Ipv4Addr, Ipv6Addr}; #[test] @@ -11,6 +11,7 @@ mod tests { let json_string = r#"{"ipnetwork":"127.1.0.0/24"}"#; #[derive(Serialize, Deserialize)] + #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] struct MyStruct { ipnetwork: Ipv4Network, } @@ -21,6 +22,11 @@ mod tests { assert_eq!(mystruct.ipnetwork.prefix(), 24); assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string); + + #[cfg(feature = "schemars")] + if let Err(s) = does_it_json::validate_with_output(&mystruct) { + panic!("{}", s); + } } #[test] @@ -28,6 +34,7 @@ mod tests { let json_string = r#"{"ipnetwork":"::1/0"}"#; #[derive(Serialize, Deserialize)] + #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] struct MyStruct { ipnetwork: Ipv6Network, } @@ -41,6 +48,11 @@ mod tests { assert_eq!(mystruct.ipnetwork.prefix(), 0); assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string); + + #[cfg(feature = "schemars")] + if let Err(s) = does_it_json::validate_with_output(&mystruct) { + panic!("{}", s); + } } #[test] @@ -48,6 +60,7 @@ mod tests { let json_string = r#"{"ipnetwork":["127.1.0.0/24","::1/0"]}"#; #[derive(Serialize, Deserialize)] + #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] struct MyStruct { ipnetwork: Vec, } @@ -63,5 +76,10 @@ mod tests { assert_eq!(mystruct.ipnetwork[1].prefix(), 0); assert_eq!(::serde_json::to_string(&mystruct).unwrap(), json_string); + + #[cfg(feature = "schemars")] + if let Err(s) = does_it_json::validate_with_output(&mystruct) { + panic!("{}", s); + } } }