Skip to content

Commit

Permalink
Switch from chrono to time.
Browse files Browse the repository at this point in the history
Ref: #109
  • Loading branch information
Arnavion committed Oct 11, 2021
1 parent 0e5de07 commit 244fb2b
Show file tree
Hide file tree
Showing 33 changed files with 578 additions and 110 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ links = "k8s-openapi-0.13.1"
[dependencies]
base64 = "0.13"
bytes = "1"
chrono = { version = "0.4.1", features = ["serde"] }
http = { version = "0.2", optional = true }
percent-encoding = { version = "2", optional = true }
schemars = { version = "0.8", optional = true }
serde = "1"
serde_json = "1"
serde-value = "0.7"
time = { version = "0.3", features = ["formatting", "macros", "parsing"] }
url = { version = "2", optional = true }

[features]
Expand Down
46 changes: 28 additions & 18 deletions k8s-openapi-codegen-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -966,8 +966,7 @@ pub fn run(
swagger20::SchemaKind::Ty(_) => {
let inner_type_name = get_rust_type(&definition.kind, map_namespace)?;

// Kubernetes requires MicroTime to be serialized with exactly six decimal digits, instead of the default serde serialization of `chrono::DateTime`
// that uses a variable number up to nine.
// Kubernetes requires MicroTime to be serialized with exactly six decimal digits.
//
// Furthermore, while Kubernetes does deserialize a Time from a string with one or more decimal digits,
// the format string it uses to *serialize* datetimes does not contain any decimal digits. So match that behavior just to be safe, and to have
Expand All @@ -977,28 +976,39 @@ pub fn run(
// - https://github.com/Arnavion/k8s-openapi/issues/63
// - https://github.com/deislabs/krustlet/issues/5
// - https://github.com/kubernetes/apimachinery/issues/88
let datetime_serialization_format = match (&**definition_path, &definition.kind) {
match (&**definition_path, &definition.kind) {
(
"io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime",
swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }),
) => templates::DateTimeSerializationFormat::SixDecimalDigits,
) => templates::newtype_time::generate(
&mut out,
vis,
type_name,
&inner_type_name,
templates::DateTimeSerializationFormat::SixDecimalDigits,
map_namespace,
)?,

(
"io.k8s.apimachinery.pkg.apis.meta.v1.Time",
swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }),
) => templates::DateTimeSerializationFormat::ZeroDecimalDigits,

_ => templates::DateTimeSerializationFormat::Default,
};
) => templates::newtype_time::generate(
&mut out,
vis,
type_name,
&inner_type_name,
templates::DateTimeSerializationFormat::ZeroDecimalDigits,
map_namespace,
)?,

templates::newtype::generate(
&mut out,
vis,
type_name,
&inner_type_name,
datetime_serialization_format,
map_namespace,
)?;
_ => templates::newtype::generate(
&mut out,
vis,
type_name,
&inner_type_name,
map_namespace,
)?,
}

run_result.num_generated_type_aliases += 1;
},
Expand Down Expand Up @@ -1124,7 +1134,7 @@ fn get_derives(
// Handled by evaluate_trait_bound
swagger20::SchemaKind::Ref(ref_path @ swagger20::RefPath { .. }) if !ref_path.references_scope(map_namespace) => unreachable!(),

// chrono::DateTime<chrono::Utc> is not Default
// time::OffsetDateTime is not Default
swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) => Ok(false),

// Enums without a default value
Expand Down Expand Up @@ -1537,7 +1547,7 @@ fn get_rust_type(
swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::Byte) }) =>
Ok(format!("{}ByteString", local).into()),
swagger20::SchemaKind::Ty(swagger20::Type::String { format: Some(swagger20::StringFormat::DateTime) }) =>
Ok(format!("{local}chrono::DateTime<{local}chrono::Utc>", local = local).into()),
Ok(format!("{local}time::OffsetDateTime", local = local).into()),
swagger20::SchemaKind::Ty(swagger20::Type::String { format: None }) => Ok("String".into()),

swagger20::SchemaKind::Ty(swagger20::Type::CustomResourceSubresources(namespace)) => {
Expand Down
3 changes: 2 additions & 1 deletion k8s-openapi-codegen-common/src/templates/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub(crate) mod json_schema_props_or;

pub(crate) mod newtype;

pub(crate) mod newtype_time;

pub(crate) mod operation_response_common;

pub(crate) mod patch;
Expand Down Expand Up @@ -101,7 +103,6 @@ pub(crate) struct ResourceMetadata<'a> {

#[derive(Clone, Copy)]
pub(crate) enum DateTimeSerializationFormat {
Default,
SixDecimalDigits,
ZeroDecimalDigits,
}
8 changes: 0 additions & 8 deletions k8s-openapi-codegen-common/src/templates/newtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,17 @@ pub(crate) fn generate(
vis: &str,
type_name: &str,
inner_type_name: &str,
datetime_serialization_format: super::DateTimeSerializationFormat,
map_namespace: &impl crate::MapNamespace,
) -> Result<(), crate::Error> {
let local = crate::map_namespace_local_to_string(map_namespace)?;

let inner_value = match datetime_serialization_format {
super::DateTimeSerializationFormat::Default => "&self.0",
super::DateTimeSerializationFormat::SixDecimalDigits => "&self.0.to_rfc3339_opts(chrono::SecondsFormat::Micros, true)",
super::DateTimeSerializationFormat::ZeroDecimalDigits => "&self.0.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)",
};

writeln!(
writer,
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/newtype.rs")),
local = local,
type_name = type_name,
vis = vis,
inner_type_name = inner_type_name,
inner_value = inner_value,
)?;

Ok(())
Expand Down
27 changes: 27 additions & 0 deletions k8s-openapi-codegen-common/src/templates/newtype_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
pub(crate) fn generate(
mut writer: impl std::io::Write,
vis: &str,
type_name: &str,
inner_type_name: &str,
datetime_serialization_format: super::DateTimeSerializationFormat,
map_namespace: &impl crate::MapNamespace,
) -> Result<(), crate::Error> {
let local = crate::map_namespace_local_to_string(map_namespace)?;

let serialize_format = match datetime_serialization_format {
super::DateTimeSerializationFormat::SixDecimalDigits => "RFC3339_SUBSECONDS_SIX",
super::DateTimeSerializationFormat::ZeroDecimalDigits => "RFC3339_SUBSECONDS_ZERO",
};

writeln!(
writer,
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/newtype_time.rs")),
local = local,
type_name = type_name,
vis = vis,
inner_type_name = inner_type_name,
serialize_format = serialize_format,
)?;

Ok(())
}
2 changes: 1 addition & 1 deletion k8s-openapi-codegen-common/templates/newtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ impl<'de> {local}serde::Deserialize<'de> for {type_name} {{

impl {local}serde::Serialize for {type_name} {{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: {local}serde::Serializer {{
serializer.serialize_newtype_struct({type_name:?}, {inner_value})
serializer.serialize_newtype_struct({type_name:?}, &self.0)
}}
}}
43 changes: 43 additions & 0 deletions k8s-openapi-codegen-common/templates/newtype_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
struct {type_name}({vis}{inner_type_name});

impl<'de> {local}serde::Deserialize<'de> for {type_name} {{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: {local}serde::Deserializer<'de> {{
struct Visitor;

impl<'de> {local}serde::de::Visitor<'de> for Visitor {{
type Value = {type_name};

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
f.write_str({type_name:?})
}}

fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error> where D: {local}serde::Deserializer<'de> {{
struct Visitor;

impl<'de> {local}serde::de::Visitor<'de> for Visitor {{
type Value = {local}time::OffsetDateTime;

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
f.write_str("OffsetDateTime")
}}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: {local}serde::de::Error {{
{local}time::OffsetDateTime::parse(s, &{local}time::format_description::well_known::Rfc3339).map_err(serde::de::Error::custom)
}}
}}

Ok({type_name}(deserializer.deserialize_str(Visitor)?))
}}
}}

deserializer.deserialize_newtype_struct({type_name:?}, Visitor)
}}
}}

impl {local}serde::Serialize for {type_name} {{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: {local}serde::Serializer {{
// TODO: This could be `&self.0.format({local}time2::{serialize_format})`,
// but can't because of https://github.com/time-rs/time/issues/359
serializer.serialize_newtype_struct({type_name:?}, &self.0.format(&{local}time2::{serialize_format}).map_err({local}serde::ser::Error::custom)?)
}}
}}
16 changes: 8 additions & 8 deletions k8s-openapi-tests/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ fn time() {
for &(input, expected_time, expected_micro_time) in &[
(
r#""2020-03-05T12:34:56.789012345Z""#,
r#""2020-03-05T12:34:56Z""#,
r#""2020-03-05T12:34:56.789012Z""#,
r#""2020-03-05T12:34:56+00:00""#,
r#""2020-03-05T12:34:56.789012+00:00""#,
),
(
r#""2020-03-05T12:34:56.789012Z""#,
r#""2020-03-05T12:34:56Z""#,
r#""2020-03-05T12:34:56.789012Z""#,
r#""2020-03-05T12:34:56+00:00""#,
r#""2020-03-05T12:34:56.789012+00:00""#,
),
(
r#""2020-03-05T12:34:56.789000Z""#,
r#""2020-03-05T12:34:56Z""#,
r#""2020-03-05T12:34:56.789000Z""#,
r#""2020-03-05T12:34:56+00:00""#,
r#""2020-03-05T12:34:56.789000+00:00""#,
),
(
r#""2020-03-05T12:34:56.789Z""#,
r#""2020-03-05T12:34:56Z""#,
r#""2020-03-05T12:34:56.789000Z""#,
r#""2020-03-05T12:34:56+00:00""#,
r#""2020-03-05T12:34:56.789000+00:00""#,
),
] {
let time: meta::Time = serde_json::from_str(input).unwrap();
Expand Down
13 changes: 12 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,6 @@
//! The [`k8s-openapi-derive` crate](https://crates.io/crates/k8s-openapi-derive) provides a custom derive for generating clientsets
//! for custom resources. See that crate's docs for more information.

pub use chrono;
#[cfg(feature = "api")]
pub use http;
#[cfg(feature = "api")]
Expand All @@ -470,6 +469,7 @@ pub use schemars;
pub use serde;
pub use serde_json;
pub use serde_value;
pub use time;
#[cfg(feature = "api")]
pub use url;

Expand Down Expand Up @@ -772,6 +772,17 @@ pub mod percent_encoding2 {
.add(b'#').add(b'?').add(b'{').add(b'}'); // path percent-encode set
}

/// Extensions to the time crate
pub mod time2 {
/// RFC3339 format with no subsecond digits.
pub const RFC3339_SUBSECONDS_ZERO: &[time::format_description::FormatItem<'_>] =
time::macros::format_description!("[year padding:zero repr:full base:calendar sign:automatic]-[month padding:zero repr:numerical]-[day padding:zero]T[hour padding:zero repr:24]:[minute padding:zero]:[second padding:zero][offset_hour padding:zero sign:mandatory]:[offset_minute padding:zero]");

/// RFC3339 format with six subsecond digits.
pub const RFC3339_SUBSECONDS_SIX: &[time::format_description::FormatItem<'_>] =
time::macros::format_description!("[year padding:zero repr:full base:calendar sign:automatic]-[month padding:zero repr:numerical]-[day padding:zero]T[hour padding:zero repr:24]:[minute padding:zero]:[second padding:zero].[subsecond digits:6][offset_hour padding:zero sign:mandatory]:[offset_minute padding:zero]");
}

#[cfg(feature = "v1_11")] mod v1_11;
#[cfg(feature = "v1_11")] pub use self::v1_11::*;

Expand Down
22 changes: 19 additions & 3 deletions src/v1_11/apimachinery/pkg/apis/meta/v1/micro_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/// MicroTime is version of Time with microsecond level precision.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct MicroTime(pub crate::chrono::DateTime<crate::chrono::Utc>);
pub struct MicroTime(pub crate::time::OffsetDateTime);

impl<'de> crate::serde::Deserialize<'de> for MicroTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: crate::serde::Deserializer<'de> {
Expand All @@ -16,7 +16,21 @@ impl<'de> crate::serde::Deserialize<'de> for MicroTime {
}

fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error> where D: crate::serde::Deserializer<'de> {
Ok(MicroTime(crate::serde::Deserialize::deserialize(deserializer)?))
struct Visitor;

impl<'de> crate::serde::de::Visitor<'de> for Visitor {
type Value = crate::time::OffsetDateTime;

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("OffsetDateTime")
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: crate::serde::de::Error {
crate::time::OffsetDateTime::parse(s, &crate::time::format_description::well_known::Rfc3339).map_err(serde::de::Error::custom)
}
}

Ok(MicroTime(deserializer.deserialize_str(Visitor)?))
}
}

Expand All @@ -26,7 +40,9 @@ impl<'de> crate::serde::Deserialize<'de> for MicroTime {

impl crate::serde::Serialize for MicroTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: crate::serde::Serializer {
serializer.serialize_newtype_struct("MicroTime", &self.0.to_rfc3339_opts(chrono::SecondsFormat::Micros, true))
// TODO: This could be `&self.0.format(crate::time2::RFC3339_SUBSECONDS_SIX)`,
// but can't because of https://github.com/time-rs/time/issues/359
serializer.serialize_newtype_struct("MicroTime", &self.0.format(&crate::time2::RFC3339_SUBSECONDS_SIX).map_err(crate::serde::ser::Error::custom)?)
}
}

Expand Down
22 changes: 19 additions & 3 deletions src/v1_11/apimachinery/pkg/apis/meta/v1/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/// Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Time(pub crate::chrono::DateTime<crate::chrono::Utc>);
pub struct Time(pub crate::time::OffsetDateTime);

impl<'de> crate::serde::Deserialize<'de> for Time {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: crate::serde::Deserializer<'de> {
Expand All @@ -16,7 +16,21 @@ impl<'de> crate::serde::Deserialize<'de> for Time {
}

fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error> where D: crate::serde::Deserializer<'de> {
Ok(Time(crate::serde::Deserialize::deserialize(deserializer)?))
struct Visitor;

impl<'de> crate::serde::de::Visitor<'de> for Visitor {
type Value = crate::time::OffsetDateTime;

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("OffsetDateTime")
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: crate::serde::de::Error {
crate::time::OffsetDateTime::parse(s, &crate::time::format_description::well_known::Rfc3339).map_err(serde::de::Error::custom)
}
}

Ok(Time(deserializer.deserialize_str(Visitor)?))
}
}

Expand All @@ -26,7 +40,9 @@ impl<'de> crate::serde::Deserialize<'de> for Time {

impl crate::serde::Serialize for Time {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: crate::serde::Serializer {
serializer.serialize_newtype_struct("Time", &self.0.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
// TODO: This could be `&self.0.format(crate::time2::RFC3339_SUBSECONDS_ZERO)`,
// but can't because of https://github.com/time-rs/time/issues/359
serializer.serialize_newtype_struct("Time", &self.0.format(&crate::time2::RFC3339_SUBSECONDS_ZERO).map_err(crate::serde::ser::Error::custom)?)
}
}

Expand Down
Loading

0 comments on commit 244fb2b

Please sign in to comment.