1use crate::{gvk::GroupVersionKind, resource::Resource};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
10pub struct ApiResource {
11 pub group: String,
13 pub version: String,
15 pub api_version: String,
18 pub kind: String,
20 pub plural: String,
22}
23
24impl ApiResource {
25 pub fn erase<K: Resource>(dt: &K::DynamicType) -> Self {
27 ApiResource {
28 group: K::group(dt).to_string(),
29 version: K::version(dt).to_string(),
30 api_version: K::api_version(dt).to_string(),
31 kind: K::kind(dt).to_string(),
32 plural: K::plural(dt).to_string(),
33 }
34 }
35
36 pub fn from_gvk_with_plural(gvk: &GroupVersionKind, plural: &str) -> Self {
38 ApiResource {
39 api_version: gvk.api_version(),
40 group: gvk.group.clone(),
41 version: gvk.version.clone(),
42 kind: gvk.kind.clone(),
43 plural: plural.to_string(),
44 }
45 }
46
47 pub fn from_gvk(gvk: &GroupVersionKind) -> Self {
56 ApiResource::from_gvk_with_plural(gvk, &to_plural(&gvk.kind.to_ascii_lowercase()))
57 }
58}
59
60#[derive(Debug, Clone, Hash, Eq, PartialEq)]
62pub enum Scope {
63 Cluster,
65 Namespaced,
67}
68
69pub mod verbs {
71 pub const CREATE: &str = "create";
73 pub const GET: &str = "get";
75 pub const LIST: &str = "list";
77 pub const WATCH: &str = "watch";
79 pub const DELETE: &str = "delete";
81 pub const DELETE_COLLECTION: &str = "deletecollection";
83 pub const UPDATE: &str = "update";
85 pub const PATCH: &str = "patch";
87}
88
89#[derive(Debug, Clone)]
91pub struct ApiCapabilities {
92 pub scope: Scope,
94 pub subresources: Vec<(ApiResource, ApiCapabilities)>,
100 pub operations: Vec<String>,
102}
103
104impl ApiCapabilities {
105 pub fn supports_operation(&self, operation: &str) -> bool {
107 self.operations.iter().any(|op| op == operation)
108 }
109}
110
111fn to_plural(word: &str) -> String {
113 if word == "endpoints" || word == "endpointslices" {
114 return word.to_owned();
115 } else if word == "nodemetrics" {
116 return "nodes".to_owned();
117 } else if word == "podmetrics" {
118 return "pods".to_owned();
119 }
120
121 if word.ends_with('s')
123 || word.ends_with('x')
124 || word.ends_with('z')
125 || word.ends_with("ch")
126 || word.ends_with("sh")
127 {
128 return format!("{word}es");
129 }
130
131 if word.ends_with('y') {
134 if let Some(c) = word.chars().nth(word.len() - 2) {
135 if !matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') {
136 let mut chars = word.chars();
138 chars.next_back();
139 return format!("{}ies", chars.as_str());
140 }
141 }
142 }
143
144 format!("{word}s")
146}
147
148#[test]
149fn test_to_plural_native() {
150 #[rustfmt::skip]
152 let native_kinds = vec![
153 ("APIService", "apiservices"),
154 ("Binding", "bindings"),
155 ("CertificateSigningRequest", "certificatesigningrequests"),
156 ("ClusterRole", "clusterroles"), ("ClusterRoleBinding", "clusterrolebindings"),
157 ("ComponentStatus", "componentstatuses"),
158 ("ConfigMap", "configmaps"),
159 ("ControllerRevision", "controllerrevisions"),
160 ("CronJob", "cronjobs"),
161 ("CSIDriver", "csidrivers"), ("CSINode", "csinodes"), ("CSIStorageCapacity", "csistoragecapacities"),
162 ("CustomResourceDefinition", "customresourcedefinitions"),
163 ("DaemonSet", "daemonsets"),
164 ("Deployment", "deployments"),
165 ("Endpoints", "endpoints"), ("EndpointSlice", "endpointslices"),
166 ("Event", "events"),
167 ("FlowSchema", "flowschemas"),
168 ("HorizontalPodAutoscaler", "horizontalpodautoscalers"),
169 ("Ingress", "ingresses"), ("IngressClass", "ingressclasses"),
170 ("Job", "jobs"),
171 ("Lease", "leases"),
172 ("LimitRange", "limitranges"),
173 ("LocalSubjectAccessReview", "localsubjectaccessreviews"),
174 ("MutatingWebhookConfiguration", "mutatingwebhookconfigurations"),
175 ("Namespace", "namespaces"),
176 ("NetworkPolicy", "networkpolicies"),
177 ("Node", "nodes"),
178 ("PersistentVolumeClaim", "persistentvolumeclaims"),
179 ("PersistentVolume", "persistentvolumes"),
180 ("PodDisruptionBudget", "poddisruptionbudgets"),
181 ("Pod", "pods"),
182 ("PodSecurityPolicy", "podsecuritypolicies"),
183 ("PodTemplate", "podtemplates"),
184 ("PriorityClass", "priorityclasses"),
185 ("PriorityLevelConfiguration", "prioritylevelconfigurations"),
186 ("ReplicaSet", "replicasets"),
187 ("ReplicationController", "replicationcontrollers"),
188 ("ResourceQuota", "resourcequotas"),
189 ("Role", "roles"), ("RoleBinding", "rolebindings"),
190 ("RuntimeClass", "runtimeclasses"),
191 ("Secret", "secrets"),
192 ("SelfSubjectAccessReview", "selfsubjectaccessreviews"),
193 ("SelfSubjectRulesReview", "selfsubjectrulesreviews"),
194 ("ServiceAccount", "serviceaccounts"),
195 ("Service", "services"),
196 ("StatefulSet", "statefulsets"),
197 ("StorageClass", "storageclasses"), ("StorageVersion", "storageversions"),
198 ("SubjectAccessReview", "subjectaccessreviews"),
199 ("TokenReview", "tokenreviews"),
200 ("ValidatingWebhookConfiguration", "validatingwebhookconfigurations"),
201 ("VolumeAttachment", "volumeattachments"),
202 ];
203 for (kind, plural) in native_kinds {
204 assert_eq!(to_plural(&kind.to_ascii_lowercase()), plural);
205 }
206}