use serde_json::{Map, Value};
use crate::k8s_wire_identity::K8sWireIdentity;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct K8sObjectRef {
pub kind: String,
pub name: String,
pub namespace: String,
}
impl K8sObjectRef {
pub fn new(
kind: impl Into<String>,
name: impl Into<String>,
namespace: impl Into<String>,
) -> Self {
Self {
kind: kind.into(),
name: name.into(),
namespace: namespace.into(),
}
}
pub fn as_json(&self) -> Value {
let mut m = Map::with_capacity(3);
m.insert("kind".to_string(), Value::String(self.kind.clone()));
m.insert("name".to_string(), Value::String(self.name.clone()));
m.insert(
"namespace".to_string(),
Value::String(self.namespace.clone()),
);
Value::Object(m)
}
}
impl K8sWireIdentity {
pub fn object_ref(self, name: impl Into<String>, namespace: impl Into<String>) -> K8sObjectRef {
K8sObjectRef::new(self.kind, name, namespace)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::flux_resource::FluxResource;
use crate::routing_edge_resource::RoutingEdgeResource;
#[test]
fn new_binds_slots_by_position() {
let r = K8sObjectRef::new("Widget", "my-widget", "my-ns");
assert_eq!(r.kind, "Widget");
assert_eq!(r.name, "my-widget");
assert_eq!(r.namespace, "my-ns");
}
#[test]
fn new_accepts_str_and_owned_string_at_every_slot() {
let a = K8sObjectRef::new("K", "n", "ns");
let b = K8sObjectRef::new(String::from("K"), String::from("n"), String::from("ns"));
assert_eq!(a, b);
}
#[test]
fn as_json_emits_exactly_three_slots_named_kind_name_namespace() {
let r = K8sObjectRef::new("Widget", "my-widget", "my-ns");
let v = r.as_json();
let obj = v.as_object().expect("as_json emits an Object");
assert_eq!(obj.len(), 3, "K8sObjectRef must emit exactly 3 slots");
assert_eq!(obj["kind"], "Widget");
assert_eq!(obj["name"], "my-widget");
assert_eq!(obj["namespace"], "my-ns");
assert!(
!obj.contains_key("apiVersion"),
"K8sObjectRef must not stamp an `apiVersion` slot — the K8s cross-\
reference form derives apiVersion from `kind` at the owning controller"
);
}
#[test]
fn as_json_is_a_pure_function_of_the_triple() {
let a = K8sObjectRef::new("K", "n", "ns");
let b = K8sObjectRef::new("K", "n", "ns");
assert_eq!(a.as_json(), b.as_json());
assert_eq!(a.as_json(), a.as_json());
}
#[test]
fn struct_equality_pins_the_triple_axis() {
let a = K8sObjectRef::new("K", "n", "ns");
let b = K8sObjectRef::new("K", "n", "ns");
let diff_kind = K8sObjectRef::new("K2", "n", "ns");
let diff_name = K8sObjectRef::new("K", "n2", "ns");
let diff_ns = K8sObjectRef::new("K", "n", "ns2");
assert_eq!(a, b);
assert_ne!(a, diff_kind);
assert_ne!(a, diff_name);
assert_ne!(a, diff_ns);
}
#[test]
fn wire_identity_object_ref_binds_kind_from_the_identity() {
let id = K8sWireIdentity::new("group.io/v1", "Widget");
let r = id.object_ref("my-widget", "my-ns");
assert_eq!(r.kind, "Widget");
assert_eq!(r.name, "my-widget");
assert_eq!(r.namespace, "my-ns");
}
#[test]
fn wire_identity_object_ref_drops_api_version() {
let id = K8sWireIdentity::new("group.io/v1", "Widget");
let r = id.object_ref("my-widget", "my-ns");
let obj = r.as_json();
let obj = obj.as_object().unwrap();
assert!(!obj.contains_key("apiVersion"));
assert_eq!(obj.len(), 3);
}
#[test]
fn flux_resource_composes_object_ref_through_wire_identity() {
for v in FluxResource::ALL {
let r = v.wire_identity().object_ref("some-name", "some-ns");
assert_eq!(r.kind, v.kind());
assert_eq!(r.name, "some-name");
assert_eq!(r.namespace, "some-ns");
}
}
#[test]
fn routing_edge_resource_composes_object_ref_through_wire_identity() {
for v in RoutingEdgeResource::ALL {
let r = v.wire_identity().object_ref("edge-name", "edge-ns");
assert_eq!(r.kind, v.kind());
}
}
#[test]
fn as_json_matches_pre_lift_flux_source_ref_hand_authored_shape() {
let hand_authored = serde_json::json!({
"kind": "GitRepository",
"name": "flux-system",
"namespace": "flux-system",
});
let composed = K8sObjectRef::new("GitRepository", "flux-system", "flux-system").as_json();
assert_eq!(composed, hand_authored);
}
#[test]
fn as_json_matches_pre_lift_helm_chart_ref_hand_authored_shape() {
let hand_authored = serde_json::json!({
"kind": FluxResource::OCIRepository.kind(),
"name": "ephemeral-demo",
"namespace": "demo-test",
});
let composed = FluxResource::OCIRepository
.wire_identity()
.object_ref("ephemeral-demo", "demo-test")
.as_json();
assert_eq!(composed, hand_authored);
}
#[test]
fn as_json_matches_pre_lift_helm_repository_source_ref_hand_authored_shape() {
let hand_authored = serde_json::json!({
"kind": "HelmRepository",
"name": "pleme-io",
"namespace": "flux-system",
});
let composed = K8sObjectRef::new("HelmRepository", "pleme-io", "flux-system").as_json();
assert_eq!(composed, hand_authored);
}
}