kube_derive/lib.rs
1//! A crate for kube's derive macros.
2#![recursion_limit = "1024"]
3extern crate proc_macro;
4#[macro_use] extern crate quote;
5
6mod cel_schema;
7mod custom_resource;
8mod resource;
9
10/// A custom derive for kubernetes custom resource definitions.
11///
12/// This will generate a **root object** containing your spec and metadata.
13/// This root object will implement the [`kube::Resource`] trait
14/// so it can be used with [`kube::Api`].
15///
16/// The generated type will also implement kube's [`kube::CustomResourceExt`] trait to generate the crd
17/// and generate [`kube::core::ApiResource`] information for use with the dynamic api.
18///
19/// # Example
20///
21/// ```rust
22/// use serde::{Serialize, Deserialize};
23/// use kube::core::{Resource, CustomResourceExt};
24/// use kube_derive::CustomResource;
25/// use schemars::JsonSchema;
26///
27/// #[derive(CustomResource, Clone, Debug, Deserialize, Serialize, JsonSchema)]
28/// #[kube(group = "clux.dev", version = "v1", kind = "Foo", namespaced)]
29/// struct FooSpec {
30/// info: String,
31/// }
32///
33/// println!("kind = {}", Foo::kind(&())); // impl kube::Resource
34/// let f = Foo::new("foo-1", FooSpec {
35/// info: "informative info".into(),
36/// });
37/// println!("foo: {:?}", f); // debug print on root type
38/// println!("crd: {}", serde_saphyr::to_string(&Foo::crd()).unwrap()); // crd yaml
39/// ```
40///
41/// This example generates a `struct Foo` containing metadata, the spec,
42/// and optionally status. The **root** struct `Foo` can be used with the [`kube`] crate
43/// as an `Api<Foo>` object (`FooSpec` can not be used with [`Api`][`kube::Api`]).
44///
45/// ```no_run
46/// # use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition;
47/// # use kube_derive::CustomResource;
48/// # use kube::{api::{Api, Patch, PatchParams}, Client, CustomResourceExt};
49/// # use serde::{Deserialize, Serialize};
50/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
51/// # #[derive(CustomResource, Clone, Debug, Deserialize, Serialize, schemars::JsonSchema)]
52/// # #[kube(group = "clux.dev", version = "v1", kind = "Foo", namespaced)]
53/// # struct FooSpec {}
54/// # let client: Client = todo!();
55/// let foos: Api<Foo> = Api::default_namespaced(client.clone());
56/// let crds: Api<CustomResourceDefinition> = Api::all(client.clone());
57/// crds.patch("foos.clux.dev", &PatchParams::apply("myapp"), &Patch::Apply(Foo::crd())).await;
58/// # Ok(())
59/// # }
60/// ```
61///
62/// This example posts the generated `::crd` to the `CustomResourceDefinition` API.
63/// After this has been accepted (few secs max), you can start using `foos` as a normal
64/// kube `Api` object. See the `crd_` prefixed [examples](https://github.com/kube-rs/kube/blob/main/examples/)
65/// for details on this.
66///
67/// # Required properties
68///
69/// ## `#[kube(group = "mygroup.tld")]`
70/// Your cr api group. The part before the slash in the top level `apiVersion` key.
71///
72/// ## `#[kube(version = "v1")]`
73/// Your cr api version. The part after the slash in the top level `apiVersion` key.
74///
75/// ## `#[kube(kind = "Kind")]`
76/// Name of your kind, and implied default for your generated root type.
77///
78/// # Optional `#[kube]` attributes
79///
80/// ## `#[kube(singular = "nonstandard-singular")]`
81/// To specify the singular name. Defaults to lowercased `.kind` value.
82///
83/// ## `#[kube(plural = "nonstandard-plural")]`
84/// To specify the plural name. Defaults to inferring from singular.
85///
86/// ## `#[kube(namespaced)]`
87/// To specify that this is a namespaced resource rather than cluster level.
88///
89/// ## `#[kube(root = "StructName")]`
90/// Customize the name of the generated root struct (defaults to `.kind` value).
91///
92/// ## `#[kube(crates(kube_core = "::kube::core"))]`
93/// Customize the crate name the generated code will reach into (defaults to `::kube::core`).
94/// Should be one of `kube::core`, `kube_client::core` or `kube_core`.
95///
96/// ## `#[kube(crates(k8s_openapi = "::k8s_openapi"))]`
97/// Customize the crate name the generated code will use for [`k8s_openapi`](https://docs.rs/k8s-openapi/) (defaults to `::k8s_openapi`).
98///
99/// ## `#[kube(crates(schemars = "::schemars"))]`
100/// Customize the crate name the generated code will use for [`schemars`](https://docs.rs/schemars/) (defaults to `::schemars`).
101///
102/// ## `#[kube(crates(serde = "::serde"))]`
103/// Customize the crate name the generated code will use for [`serde`](https://docs.rs/serde/) (defaults to `::serde`).
104///
105/// ## `#[kube(crates(serde_json = "::serde_json"))]`
106/// Customize the crate name the generated code will use for [`serde_json`](https://docs.rs/serde_json/) (defaults to `::serde_json`).
107///
108/// ## `#[kube(status = "StatusStructName")]`
109/// Adds a status struct to the top level generated type and enables the status
110/// subresource in your crd.
111///
112/// ## `#[kube(derive = "Trait")]`
113/// Adding `#[kube(derive = "PartialEq")]` is required if you want your generated
114/// top level type to be able to `#[derive(PartialEq)]`
115///
116/// ## `#[kube(attr = "attribute")]`
117/// Adding `#[kube(attr = "attribute")]` is required if you want your generated
118/// top level type to have the `#[attribute]` type level attribute added.
119/// The attributes will be added after the `#[derive(...)]` attribute of the root type.
120/// The added attributes can either attribute macros or derive macro helper attributes.
121///
122/// Does not allow to set the `derive`, `serde` or `schemars` derive helper attributes.
123/// To add `derive`s the correct way to set it is `#[kube(derive = "Trait")]`.
124/// Setting derive helper attributes for `serde` and `schemars` is not supported as
125/// it might yield unexpected behaviour when interacting with the other generated code.
126///
127/// ```ignore
128/// #[kube(
129/// attr="allow(deprecated)",
130/// attr="cfg_attr(docsrs,doc(cfg(feature = \"latest\")))",
131/// )]
132/// ```
133///
134/// ## `#[kube(schema = "mode")]`
135/// Defines whether the `JsonSchema` of the top level generated type should be used when generating a `CustomResourceDefinition`.
136///
137/// Legal values:
138/// - `"derived"`: A `JsonSchema` implementation is automatically derived
139/// - `"manual"`: `JsonSchema` is not derived, but used when creating the `CustomResourceDefinition` object
140/// - `"disabled"`: No `JsonSchema` is used
141///
142/// This can be used to provide a completely custom schema, or to interact with third-party custom resources
143/// where you are not responsible for installing the `CustomResourceDefinition`.
144///
145/// Defaults to `"derived"`.
146///
147/// NOTE: `CustomResourceDefinition`s require a schema. If `schema = "disabled"` then
148/// `Self::crd()` will not be installable into the cluster as-is.
149///
150/// ## `#[kube(scale(...))]`
151///
152/// Allow customizing the scale struct for the [scale subresource](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#subresources).
153/// It should be noted, that the status subresource must also be enabled to use the scale subresource. This is because
154/// the `statusReplicasPath` only accepts JSONPaths under `.status`.
155///
156/// ```ignore
157/// #[kube(scale(
158/// spec_replicas_path = ".spec.replicas",
159/// status_replica_path = ".status.replicas",
160/// label_selector_path = ".spec.labelSelector"
161/// ))]
162/// ```
163///
164/// The deprecated way of customizing the scale subresource using a raw JSON string is still
165/// support for backwards-compatibility.
166///
167/// ## `#[kube(printcolumn(...))]`
168///
169/// Allows adding a custom column to the [printcolumns](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#additional-printer-columns).
170///
171/// ```ignore
172/// #[kube(printcolumn(
173/// name = "CustomColumn",
174/// type_ = "integer",
175/// json_path = ".spec.someField",
176/// description = "a custom column", // optional
177/// format = "int32", // optional
178/// priority = "1", // optional
179/// ))]
180/// ```
181///
182/// The older method of supplying raw json is still supported:
183///
184/// ```ignore
185/// printcolumn = r#"{"name":"Spec", "type":"string", "description":"name of foo", "jsonPath":".spec.name"}"#,
186/// ```
187///
188/// ## `#[kube(shortname = "sn")]`
189/// Add a single shortname to the generated crd.
190///
191/// ## `#[kube(category = "apps")]`
192/// Add a single category to `crd.spec.names.categories`.
193///
194/// ## `#[kube(selectable = "fieldSelectorPath")]`
195/// Adds a Kubernetes >=1.30 `selectableFields` property ([KEP-4358](https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/4358-custom-resource-field-selectors/README.md)) to the schema.
196/// Unlocks `kubectl get kind --field-selector fieldSelectorPath`.
197///
198/// ## `#[kube(doc = "description")]`
199/// Sets the description of the schema in the generated CRD. If not specified
200/// `Auto-generated derived type for {customResourceName} via CustomResource` will be used instead.
201///
202/// ## `#[kube(annotation("ANNOTATION_KEY", "ANNOTATION_VALUE"))]`
203/// Add a single annotation to the generated CRD.
204///
205/// ## `#[kube(label("LABEL_KEY", "LABEL_VALUE"))]`
206/// Add a single label to the generated CRD.
207///
208/// ## `#[kube(storage = true)]`
209/// Sets the `storage` property to `true` or `false`.
210///
211/// ## `#[kube(served = true)]`
212/// Sets the `served` property to `true` or `false`.
213///
214/// ## `#[kube(deprecated [= "warning"])]`
215/// Sets the `deprecated` property to `true`.
216///
217/// ```ignore
218/// #[kube(deprecated)]
219/// ```
220///
221/// Additionally, you can provide a `deprecationWarning` using the following example.
222///
223/// ```ignore
224/// #[kube(deprecated = "Replaced by other CRD")]
225/// ```
226///
227/// ## `#[kube(validation = Rule::new("self == oldSelf").message("field is immutable"))]`
228/// Inject a top level CEL validation rule for the top level generated struct.
229/// This attribute is for resources deriving [`KubeSchema`] instead of [`schemars::JsonSchema`].
230///
231/// ## Example with all properties
232///
233/// ```rust
234/// use serde::{Serialize, Deserialize};
235/// use kube_derive::CustomResource;
236/// use schemars::JsonSchema;
237///
238/// #[derive(CustomResource, Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema)]
239/// #[kube(
240/// group = "clux.dev",
241/// version = "v1",
242/// kind = "Foo",
243/// root = "FooCrd",
244/// namespaced,
245/// doc = "Custom resource representing a Foo",
246/// status = "FooStatus",
247/// derive = "PartialEq",
248/// singular = "foot",
249/// plural = "feetz",
250/// shortname = "f",
251/// scale = r#"{"specReplicasPath":".spec.replicas", "statusReplicasPath":".status.replicas"}"#,
252/// printcolumn(
253/// name = "Spec",
254/// type_ = "string",
255/// description = "name of foo",
256/// json_path = ".spec.name"
257/// ),
258/// selectable = "spec.replicasCount"
259/// )]
260/// #[serde(rename_all = "camelCase")]
261/// struct FooSpec {
262/// #[schemars(length(min = 3))]
263/// data: String,
264/// replicas_count: i32
265/// }
266///
267/// #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema)]
268/// struct FooStatus {
269/// replicas: i32
270/// }
271/// ```
272///
273/// # Enums
274///
275/// Kubernetes requires that the generated [schema is "structural"](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema).
276/// This means that the structure of the schema must not depend on the particular values. For enums this imposes a few limitations:
277///
278/// - Only [externally tagged enums](https://serde.rs/enum-representations.html#externally-tagged) are supported
279/// - Unit variants may not be mixed with struct or tuple variants (`enum Foo { Bar, Baz {}, Qux() }` is invalid, for example)
280///
281/// If these restrictions are not followed then `YourCrd::crd()` may panic, or the Kubernetes API may reject the CRD definition.
282///
283/// # Generated code
284///
285/// The example above will **roughly** generate:
286/// ```compile_fail
287/// #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema)]
288/// #[serde(rename_all = "camelCase")]
289/// pub struct FooCrd {
290/// api_version: String,
291/// kind: String,
292/// metadata: ObjectMeta,
293/// spec: FooSpec,
294/// status: Option<FooStatus>,
295/// }
296/// impl kube::Resource for FooCrd { .. }
297///
298/// impl FooCrd {
299/// pub fn new(name: &str, spec: FooSpec) -> Self { .. }
300/// pub fn crd() -> CustomResourceDefinition { .. }
301/// }
302/// ```
303///
304/// # Customizing Schemas
305/// Should you need to customize the schemas, you can use:
306/// - [Serde/Schemars Attributes](https://graham.cool/schemars/examples/3-schemars_attrs/) (no need to duplicate serde renames)
307/// - [`#[schemars(schema_with = "func")]`](https://graham.cool/schemars/examples/7-custom_serialization/) (e.g. like in the [`crd_derive` example](https://github.com/kube-rs/kube/blob/main/examples/crd_derive.rs))
308/// - `impl JsonSchema` on a type / newtype around external type. See [#129](https://github.com/kube-rs/kube/issues/129#issuecomment-750852916)
309/// - [`#[garde(...)]` field attributes for client-side validation](https://github.com/jprochazk/garde) (see [`crd_api` example](https://github.com/kube-rs/kube/blob/main/examples/crd_api.rs))
310///
311/// You might need to override parts of the schemas (for fields in question) when you are:
312/// - **using complex enums**: enums do not currently generate [structural schemas](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema), so kubernetes won't support them by default
313/// - **customizing [merge-strategies](https://kubernetes.io/docs/reference/using-api/server-side-apply/#merge-strategy)** (e.g. like in the [`crd_derive_schema` example](https://github.com/kube-rs/kube/blob/main/examples/crd_derive_schema.rs))
314///
315/// See [kubernetes openapi validation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation) for the format of the OpenAPI v3 schemas.
316///
317/// If you have to override a lot, [you can opt-out of schema-generation entirely](#kubeschema--mode)
318///
319/// # Advanced Features
320///
321/// - **embedding k8s-openapi types** can be done by enabling the `schemars` feature of `k8s-openapi` from [`0.13.0`](https://github.com/Arnavion/k8s-openapi/blob/master/CHANGELOG.md#v0130-2021-08-09)
322/// - **adding validation** via [validator crate](https://github.com/Keats/validator) is supported from `schemars` >= [`0.8.5`](https://github.com/GREsau/schemars/blob/master/CHANGELOG.md#085---2021-09-20)
323/// - **generating rust code from schemas** can be done via [kopium](https://github.com/kube-rs/kopium) and is supported on stable crds (> 1.16 kubernetes)
324///
325/// ## Schema Validation
326/// There are two main ways of doing validation; **server-side** (embedding validation attributes into the schema for the apiserver to respect), and **client-side** (provides `validate()` methods in your code).
327///
328/// Client side validation of structs can be achieved by hooking up `#[garde]` attributes in your struct and is a replacement of the now unmaintained [`validator`](https://github.com/Keats/validator/issues/201) crate.
329/// Server-side validation require mutation of your generated schema, and can in the basic cases be achieved through the use of `schemars`'s [validation attributes](https://graham.cool/schemars/deriving/attributes/#supported-validator-attributes).
330/// For complete control, [parts of the schema can be overridden](https://github.com/kube-rs/kube/blob/e01187e13ba364ccecec452e023316a62fb13e04/examples/crd_derive.rs#L37-L38) to support more advanced [Kubernetes specific validation rules](https://kubernetes.io/blog/2022/09/23/crd-validation-rules-beta/).
331///
332/// When using `garde` directly, you must add it to your dependencies (with the `derive` feature).
333///
334/// ### CEL validation (client-side)
335/// CEL rules declared with `#[kube(validation = ...)]` / `#[x_kube(validation = ...)]` are normally
336/// enforced server-side by the apiserver. With `#[kube(cel)]` the derive also generates client-side
337/// methods so you can run the same rules locally without a cluster:
338/// - `Foo::validate_cel(&self) -> Result<(), ValidationErrors>` evaluates creation rules.
339/// - `Foo::validate_cel_update(&self, old: &Self) -> Result<(), ValidationErrors>` also evaluates
340/// transition rules (rules using `oldSelf`), comparing against `old`.
341///
342/// Likewise, `#[x_kube(cel)]` on any `KubeSchema` struct generates a static
343/// `T::validate_cel(value, old)` usable on a fragment in unit tests (see [`KubeSchema`]).
344/// Both require the downstream crate to enable the **`kube/cel`** feature, since the generated code
345/// references `kube::core::cel`. `#[kube(cel)]` requires a derived schema (it cannot be combined with
346/// `schema = "manual"`). See the [`crd_derive_cel`](https://github.com/kube-rs/kube/blob/main/examples/crd_derive_cel.rs) example.
347///
348/// ### Validation Caveats
349/// Make sure your validation rules are static and handled by `schemars`:
350/// - validations from `#[garde(custom(my_func))]` will not show up in the schema.
351/// - similarly; [nested / must_match / credit_card were unhandled by schemars at time of writing](https://github.com/GREsau/schemars/pull/78)
352/// - encoding validations specified through garde (i.e. #[garde(ascii)]), are currently not supported by schemars
353/// - to validate required attributes client-side, garde requires a custom validation function (`#[garde(custom(my_required_check))]`)
354/// - when using garde, fields that should not be validated need to be explicitly skipped through the `#[garde(skip)]` attr
355///
356/// For sanity, you should review the generated schema before sending it to kubernetes.
357///
358/// ## Versioning
359/// Note that any changes to your struct / validation rules / serialization attributes will require you to re-apply the
360/// generated schema to kubernetes, so that the apiserver can validate against the right version of your structs.
361///
362/// **Backwards compatibility** between schema versions is **recommended** unless you are in a controlled environment
363/// where you can migrate manually. I.e. if you add new properties behind options, and simply mark old fields as deprecated,
364/// then you can safely roll schema out changes **without bumping** the version.
365///
366/// If you need **multiple versions**, then you need:
367///
368/// - one **module** for **each version** of your types (e.g. `v1::MyCrd` and `v2::MyCrd`)
369/// - use the [`merge_crds`](https://docs.rs/kube/latest/kube/core/crd/fn.merge_crds.html) fn to combine crds
370/// - roll out new schemas utilizing conversion webhooks / manual conversions / or allow kubectl to do its best
371///
372/// See the [crd_derive_multi](https://github.com/kube-rs/kube/blob/main/examples/crd_derive_multi.rs) example to see
373/// how this upgrade flow works without special logic.
374///
375/// The **upgrade flow** with **breaking changes** involves:
376///
377/// 1. upgrade version marked as `storage` (from v1 to v2)
378/// 2. read instances from the older `Api<v1::MyCrd>`
379/// 3. perform conversion in memory and write them to the new `Api<v2::MyCrd>`.
380/// 4. remove support for old version
381///
382/// If you need to maintain support for the old version for some time, then you have to repeat or continuously
383/// run steps 2 and 3. I.e. you probably need a **conversion webhook**.
384///
385/// **NB**: kube does currently [not implement conversion webhooks yet](https://github.com/kube-rs/kube/issues/865).
386///
387/// ## Debugging
388/// Try `cargo-expand` to see your own macro expansion.
389///
390/// # Installation
391/// Enable the `derive` feature on the `kube` crate:
392///
393/// ```toml
394/// kube = { version = "...", features = ["derive"] }
395/// ```
396///
397/// ## Runtime dependencies
398/// Due to [rust-lang/rust#54363](https://github.com/rust-lang/rust/issues/54363), we cannot be resilient against crate renames within our generated code.
399/// It's therefore **required** that you have the following crates in scope, not renamed:
400///
401/// - `serde_json`
402/// - `k8s_openapi`
403/// - `schemars` (by default, unless `schema` feature disabled)
404///
405/// You are ultimately responsible for maintaining the versions and feature flags of these libraries.
406///
407/// [`kube`]: https://docs.rs/kube
408/// [`kube::Api`]: https://docs.rs/kube/*/kube/struct.Api.html
409/// [`kube::Resource`]: https://docs.rs/kube/*/kube/trait.Resource.html
410/// [`kube::core::ApiResource`]: https://docs.rs/kube/*/kube/core/struct.ApiResource.html
411/// [`kube::CustomResourceExt`]: https://docs.rs/kube/*/kube/trait.CustomResourceExt.html
412#[proc_macro_derive(CustomResource, attributes(kube))]
413pub fn derive_custom_resource(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
414 custom_resource::derive(proc_macro2::TokenStream::from(input)).into()
415}
416
417/// Generates a JsonSchema implementation a set of CEL validation rules applied on the CRD.
418///
419/// ```rust
420/// use kube::KubeSchema;
421/// use kube::CustomResource;
422/// use serde::Deserialize;
423/// use serde::Serialize;
424/// use kube::core::crd::CustomResourceExt;
425///
426/// #[derive(CustomResource, KubeSchema, Serialize, Deserialize, Clone, Debug)]
427/// #[kube(
428/// group = "kube.rs",
429/// version = "v1",
430/// kind = "Struct",
431/// validation = "self.metadata.name == 'singleton'",
432/// )]
433/// #[x_kube(validation = "self == oldSelf")]
434/// struct MyStruct {
435/// #[serde(default = "default")]
436/// #[x_kube(validation = Rule::new("self != ''").message("failure message"))]
437/// field: String,
438/// }
439///
440/// fn default() -> String {
441/// "value".into()
442/// }
443///
444/// assert!(serde_json::to_string(&Struct::crd()).unwrap().contains("x-kubernetes-validations"));
445/// assert!(serde_json::to_string(&Struct::crd()).unwrap().contains(r#""rule":"self == oldSelf""#));
446/// assert!(serde_json::to_string(&Struct::crd()).unwrap().contains(r#""rule":"self != ''""#));
447/// assert!(serde_json::to_string(&Struct::crd()).unwrap().contains(r#""message":"failure message""#));
448/// assert!(serde_json::to_string(&Struct::crd()).unwrap().contains(r#""default":"value""#));
449/// assert!(serde_json::to_string(&Struct::crd()).unwrap().contains(r#""rule":"self.metadata.name == 'singleton'""#));
450/// ```
451///
452/// ## Client-side CEL validation with `#[x_kube(cel)]`
453///
454/// Adding `cel` to `#[x_kube(...)]` generates a static `T::validate_cel(value, old)` method that
455/// evaluates this type's CEL rules locally, without an apiserver — handy in unit tests on a struct
456/// fragment. `value` is the serialized form (`serde_json::to_value(&instance)`); `old` supplies the
457/// previous state for transition rules (rules using `oldSelf`), or `None` on creation.
458///
459/// This requires the downstream crate to enable the **`kube/cel`** feature, since the generated code
460/// references `kube::core::cel`.
461///
462/// ```rust
463/// # use kube::KubeSchema;
464/// # use serde::{Deserialize, Serialize};
465/// #[derive(KubeSchema, Serialize, Deserialize, Clone, Debug)]
466/// #[x_kube(cel, validation = "self.max >= self.min")]
467/// struct Range {
468/// min: i32,
469/// max: i32,
470/// }
471///
472/// let ok = serde_json::json!({"min": 1, "max": 5});
473/// assert!(Range::validate_cel(&ok, None).is_ok());
474///
475/// let bad = serde_json::json!({"min": 5, "max": 1});
476/// assert!(Range::validate_cel(&bad, None).is_err());
477/// ```
478#[proc_macro_derive(KubeSchema, attributes(x_kube, schemars, validate))]
479pub fn derive_schema_validation(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
480 cel_schema::derive_validated_schema(input.into()).into()
481}
482
483/// A custom derive for inheriting Resource impl for the type.
484///
485/// This will generate a [`kube::Resource`] trait implementation,
486/// inheriting from a specified resource trait implementation.
487///
488/// This allows strict typing to some typical resources like `Secret` or `ConfigMap`,
489/// in cases when implementing CRD is not desirable or it does not fit the use-case.
490///
491/// Once derived, the type can be used with [`kube::Api`].
492///
493/// # Example
494///
495/// ```rust,no_run
496/// use kube::api::ObjectMeta;
497/// use k8s_openapi::api::core::v1::ConfigMap;
498/// use kube_derive::Resource;
499/// use kube::Client;
500/// use kube::Api;
501/// use serde::Deserialize;
502///
503/// #[derive(Resource, Clone, Debug, Deserialize)]
504/// #[resource(inherit = "ConfigMap")]
505/// struct FooMap {
506/// metadata: ObjectMeta,
507/// data: Option<FooMapSpec>,
508/// }
509///
510/// #[derive(Clone, Debug, Deserialize)]
511/// struct FooMapSpec {
512/// field: String,
513/// }
514///
515/// let client: Client = todo!();
516/// let api: Api<FooMap> = Api::default_namespaced(client);
517/// let config_map = api.get("with-field");
518/// ```
519///
520/// The example above will generate:
521/// ```
522/// // impl kube::Resource for FooMap { .. }
523/// ```
524/// [`kube`]: https://docs.rs/kube
525/// [`kube::Api`]: https://docs.rs/kube/*/kube/struct.Api.html
526/// [`kube::Resource`]: https://docs.rs/kube/*/kube/trait.Resource.html
527/// [`kube::core::ApiResource`]: https://docs.rs/kube/*/kube/core/struct.ApiResource.html
528/// [`kube::CustomResourceExt`]: https://docs.rs/kube/*/kube/trait.CustomResourceExt.html
529#[proc_macro_derive(Resource, attributes(resource))]
530pub fn derive_resource(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
531 resource::derive(proc_macro2::TokenStream::from(input)).into()
532}