Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ rust-version = "1.80.0"

[dependencies]
serde = { version = "1.0.200", optional = true }
schemars = { version = "0.8.17", optional = true }
schemars = { version = "1.0.4", optional = true }

[dev-dependencies]
serde_json = "1.0.116"
criterion = {version = "0.5.1", features= ["html_reports"]}
does-it-json = "0.0.4"

[badges]
travis-ci = { repository = "achanda/ipnetwork" }
Expand Down
39 changes: 14 additions & 25 deletions src/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,20 @@ 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()
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("Ipv4Network")
}

fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"pattern": 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])$"#,
),
"x-rust-type": "ipnetwork::Ipv4Network"
})
}
}

Expand Down
60 changes: 25 additions & 35 deletions src/ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,41 +35,31 @@ 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()
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("Ipv6Network")
}

fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"pattern": 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])$"#,
),
"x-rust-type": "ipnetwork::Ipv6Network"
})
}
}

Expand Down
75 changes: 15 additions & 60 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,69 +59,24 @@ impl serde::Serialize for IpNetwork {

#[cfg(feature = "schemars")]
impl schemars::JsonSchema for IpNetwork {
fn schema_name() -> String {
"IpNetwork".to_string()
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("IpNetwork")
}

fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::SchemaObject {
metadata: Some(
schemars::schema::Metadata {
..Default::default()
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"oneOf": [
{
"title": "v4",
"allOf": [generator.subschema_for::<Ipv4Network>()]
},
{
"title": "v6",
"allOf": [generator.subschema_for::<Ipv6Network>()]
}
.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::<Ipv4Network>()]),
..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::<Ipv6Network>()]),
..Default::default()
}
.into(),
),
..Default::default()
}
.into(),
]),
..Default::default()
}
.into(),
),
extensions: [("x-rust-type".to_string(), "ipnetwork::IpNetwork".into())]
.iter()
.cloned()
.collect(),
..Default::default()
}
.into()
],
"x-rust-type": "ipnetwork::IpNetwork"
})
}
}

Expand Down
92 changes: 86 additions & 6 deletions tests/test_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,20 @@ mod tests {
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);
{
// Validate that we can generate a schema and it contains expected properties
let schema = schemars::schema_for!(MyStruct);
let schema_value = serde_json::to_value(&schema).unwrap();

// Verify the schema has the expected structure
assert!(schema_value.get("properties").is_some());
assert!(schema_value["properties"].get("ipnetwork").is_some());

// Verify our struct can be serialized to JSON that matches the schema expectations
let json_value = serde_json::to_value(&mystruct).unwrap();
assert!(json_value.get("ipnetwork").is_some());
// For single IpNetwork values, the JSON value should be a string
assert!(json_value["ipnetwork"].is_string());
}
}

Expand All @@ -50,8 +62,20 @@ mod tests {
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);
{
// Validate that we can generate a schema and it contains expected properties
let schema = schemars::schema_for!(MyStruct);
let schema_value = serde_json::to_value(&schema).unwrap();

// Verify the schema has the expected structure
assert!(schema_value.get("properties").is_some());
assert!(schema_value["properties"].get("ipnetwork").is_some());

// Verify our struct can be serialized to JSON that matches the schema expectations
let json_value = serde_json::to_value(&mystruct).unwrap();
assert!(json_value.get("ipnetwork").is_some());
// For single IpNetwork values, the JSON value should be a string
assert!(json_value["ipnetwork"].is_string());
}
}

Expand All @@ -78,8 +102,20 @@ mod tests {
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);
{
// Validate that we can generate a schema and it contains expected properties
let schema = schemars::schema_for!(MyStruct);
let schema_value = serde_json::to_value(&schema).unwrap();

// Verify the schema has the expected structure
assert!(schema_value.get("properties").is_some());
assert!(schema_value["properties"].get("ipnetwork").is_some());

// Verify our struct can be serialized to JSON that matches the schema expectations
let json_value = serde_json::to_value(&mystruct).unwrap();
assert!(json_value.get("ipnetwork").is_some());
// For Vec<IpNetwork>, the JSON value should be an array
assert!(json_value["ipnetwork"].is_array());
}
}

Expand All @@ -90,4 +126,48 @@ mod tests {
let size = network.size();
assert_eq!(size, u32::MAX);
}

#[test]
#[cfg(feature = "schemars")]
fn test_schema_generation() {
// Test that we can generate schemas for all network types
let ipv4_schema = schemars::schema_for!(Ipv4Network);
let ipv6_schema = schemars::schema_for!(Ipv6Network);
let ip_schema = schemars::schema_for!(IpNetwork);

// Convert to JSON to verify structure
let ipv4_json = serde_json::to_value(&ipv4_schema).unwrap();
let ipv6_json = serde_json::to_value(&ipv6_schema).unwrap();
let ip_json = serde_json::to_value(&ip_schema).unwrap();

// Verify IPv4 schema has string type and pattern
assert_eq!(ipv4_json["type"], "string");
assert!(ipv4_json.get("pattern").is_some());
assert_eq!(ipv4_json["x-rust-type"], "ipnetwork::Ipv4Network");

// Verify IPv6 schema has string type and pattern
assert_eq!(ipv6_json["type"], "string");
assert!(ipv6_json.get("pattern").is_some());
assert_eq!(ipv6_json["x-rust-type"], "ipnetwork::Ipv6Network");

// Verify IpNetwork schema has oneOf structure
assert!(ip_json.get("oneOf").is_some());
assert_eq!(ip_json["x-rust-type"], "ipnetwork::IpNetwork");

let one_of = ip_json["oneOf"].as_array().unwrap();
assert_eq!(one_of.len(), 2);
assert_eq!(one_of[0]["title"], "v4");
assert_eq!(one_of[1]["title"], "v6");

// Verify that the schemas follow the schemars 1.0 migration guide patterns
// The Schema should be a wrapper around serde_json::Value
assert!(ipv4_json.is_object());
assert!(ipv6_json.is_object());
assert!(ip_json.is_object());

// Print schemas for manual verification (useful for debugging)
println!("IPv4 Schema: {}", serde_json::to_string_pretty(&ipv4_json).unwrap());
println!("IPv6 Schema: {}", serde_json::to_string_pretty(&ipv6_json).unwrap());
println!("IpNetwork Schema: {}", serde_json::to_string_pretty(&ip_json).unwrap());
}
}
Loading