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