engenho-types 0.1.2

Typed Kubernetes resource catalog for engenho. Generated from upstream OpenAPI v3 via forge-gen (Pillar 12 — generation over composition). One #[derive(KubeResource, TataraDomain)] per kind; no hand-authored types per the engenho prime directive.
Documentation
//! Object / List / Type metadata — the upstream `meta/v1` types.
//!
//! These are the only structurally-shared types across every Kubernetes
//! resource. They land in the crate root rather than a generated
//! `meta_v1/` module because every generated kind module depends on
//! `ObjectMeta` — circular module deps are avoided by hoisting.
//!
//! Per the engenho prime directive (theory/ENGENHO.md §IV — generation
//! over composition), even these "shared root types" will eventually be
//! generated by `forge-gen --backend kube-resource` once the generator
//! supports the `metaV1` schema as a special-cased dependency. For M0.0
//! they are hand-authored as the smallest possible target shape; for
//! M0.0.3 the generator emits them and this file is deleted.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

/// `meta/v1.ObjectMeta` — the metadata every namespaced/cluster-scoped
/// resource carries.
///
/// Fields are ordered to match upstream OpenAPI's required-then-optional
/// shape. `BTreeMap` is intentional (not `HashMap`) for byte-reproducible
/// serialization (theory/ENGENHO.md §VI.4 — determinism in SSA merge).
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ObjectMeta {
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub name: String,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub namespace: Option<String>,

    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub resource_version: String,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uid: Option<String>,

    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub labels: BTreeMap<String, String>,

    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub annotations: BTreeMap<String, String>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub creation_timestamp: Option<chrono::DateTime<chrono::Utc>>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deletion_timestamp: Option<chrono::DateTime<chrono::Utc>>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deletion_grace_period_seconds: Option<i64>,

    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub finalizers: Vec<String>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub generation: Option<i64>,
}

/// `meta/v1.TypeMeta` — apiVersion + kind, the discriminator on the wire.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TypeMeta {
    #[serde(rename = "apiVersion", default, skip_serializing_if = "String::is_empty")]
    pub api_version: String,

    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub kind: String,
}

/// `meta/v1.ListMeta` — pagination + resourceVersion for List responses.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ListMeta {
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub resource_version: String,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub continue_token: Option<String>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub remaining_item_count: Option<i64>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn object_meta_serializes_minimally() {
        let m = ObjectMeta { name: "foo".into(), ..Default::default() };
        let j = serde_json::to_string(&m).unwrap();
        assert_eq!(j, r#"{"name":"foo"}"#);
    }

    #[test]
    fn type_meta_uses_api_version() {
        let m = TypeMeta { api_version: "v1".into(), kind: "Pod".into() };
        let j = serde_json::to_string(&m).unwrap();
        assert!(j.contains("\"apiVersion\":\"v1\""));
    }

    #[test]
    fn object_meta_btreemap_labels_are_byte_deterministic() {
        // Determinism contract per theory/ENGENHO.md §VI.4 — label ordering
        // must be byte-stable across runs. BTreeMap (not HashMap) ensures this.
        let mut m = ObjectMeta::default();
        m.labels.insert("zzz".into(), "z".into());
        m.labels.insert("aaa".into(), "a".into());
        let j = serde_json::to_string(&m).unwrap();
        let idx_aaa = j.find("\"aaa\"").unwrap();
        let idx_zzz = j.find("\"zzz\"").unwrap();
        assert!(idx_aaa < idx_zzz, "BTreeMap must serialize keys in ascending order");
    }
}