diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e99e4..021a62e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Next +## [0.0.1-alpha.3] - 2026-09-17 + +* Added the type `Never` to represent a value that can never be constructed. + ## [0.0.1-alpha.2] - 2026-09-09 * Added the trait `OptionalNullable` to model absent, null, or a value diff --git a/Cargo.lock b/Cargo.lock index a3dbd95..f521083 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "json-serde" -version = "0.0.1-alpha.2" +version = "0.0.1-alpha.3" dependencies = [ "schemars 0.8.22", "schemars 1.2.2", diff --git a/Cargo.toml b/Cargo.toml index 8191a25..6fdf4de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "json-serde" -version = "0.0.1-alpha.2" +version = "0.0.1-alpha.3" edition = "2024" rust-version = "1.97" license = "Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 8ca7d30..95af841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -434,6 +434,80 @@ impl schemars1::JsonSchema for Absent { } } +/// Type for a value that cannot exist. +/// +/// [`Absent`] is the other type for an impossible value, and the two are not +/// interchangeable. `Absent` says a struct field must not appear on the wire, +/// which is a statement about serialization; the field still has to hold +/// something, so `Absent` can be created and implements `Default`. `Never` +/// says the type has no values at all. A struct with a `Never` field can +/// itself never be created! +/// +/// `Serialize` is vacuous: with no value to receive, its body is an empty +/// match. `Deserialize` always fails, which is the true answer, since no JSON +/// document deserializes into a type with no values. With the `schemars08` and +/// `schemars1` features, `JsonSchema` is the `false` schema, as it is for +/// `Absent`. +/// +/// There is deliberately no `Default`: a type with no values has nothing to +/// return. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Never {} + +impl serde_core::Serialize for Never { + fn serialize(&self, _serializer: S) -> Result + where + S: Serializer, + { + // No value of this type exists, so this is unreachable rather + // than an error path. + match *self {} + } +} + +impl<'de> Deserialize<'de> for Never { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use serde_core::de::Error; + // Chew up any input, so the error names the real problem rather + // than whatever the input happened to be. + let _ = serde_core::de::IgnoredAny::deserialize(deserializer)?; + Err(D::Error::custom("no value of this type can exist")) + } +} + +#[cfg(feature = "schemars08")] +impl schemars08::JsonSchema for Never { + fn schema_name() -> String { + "Never".to_string() + } + + fn json_schema(_: &mut schemars08::r#gen::SchemaGenerator) -> schemars08::schema::Schema { + schemars08::schema::Schema::Bool(false) + } + + fn is_referenceable() -> bool { + false + } +} + +#[cfg(feature = "schemars1")] +impl schemars1::JsonSchema for Never { + fn schema_name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Never") + } + + fn json_schema(_: &mut schemars1::SchemaGenerator) -> schemars1::Schema { + schemars1::Schema::from(false) + } + + fn inline_schema() -> bool { + true + } +} + /// Models a fields that may be absent, null, or a value. /// /// `Default` is required; it constructs the absent state. @@ -802,4 +876,42 @@ mod tests { assert_eq!(serde_json::to_value(&schema).unwrap(), expected); } + + #[test] + fn never_containers_hold_only_their_empty_document() { + use crate::Never; + + // The whole point: a container of Never still round-trips, because the + // empty array, the empty object, and null are the documents these + // positions accept. + let empty: Vec = serde_json::from_str("[]").unwrap(); + assert!(empty.is_empty()); + assert_eq!(serde_json::to_string(&empty).unwrap(), "[]"); + + let none: Option = serde_json::from_str("null").unwrap(); + assert!(none.is_none()); + assert_eq!(serde_json::to_string(&none).unwrap(), "null"); + + let map: std::collections::BTreeMap = serde_json::from_str("{}").unwrap(); + assert!(map.is_empty()); + assert_eq!(serde_json::to_string(&map).unwrap(), "{}"); + } + + #[test] + fn never_rejects_any_value() { + use crate::Never; + + // Anything that would need an actual value fails, and the message + // names the real problem rather than the input. + let err = serde_json::from_str::>("[1]").unwrap_err(); + assert!( + err.to_string().contains("no value of this type can exist"), + "unexpected error: {err}" + ); + let err = serde_json::from_str::>("1").unwrap_err(); + assert!( + err.to_string().contains("no value of this type can exist"), + "unexpected error: {err}" + ); + } }