k8s_openapi_derive/lib.rs
1#![recursion_limit = "1024"]
2#![warn(rust_2018_idioms)]
3#![deny(clippy::all, clippy::pedantic)]
4#![allow(
5 clippy::too_many_lines,
6)]
7
8//! This crate contains custom derives related to the [`k8s-openapi`](https://crates.io/crates/k8s-openapi) crate.
9
10mod custom_resource_definition;
11
12trait CustomDerive: Sized {
13 fn parse(input: syn::DeriveInput, tokens: proc_macro2::TokenStream) -> Result<Self, syn::Error>;
14 fn emit(self) -> Result<proc_macro2::TokenStream, syn::Error>;
15}
16
17fn run_custom_derive<T>(input: proc_macro::TokenStream) -> proc_macro::TokenStream where T: CustomDerive {
18 let input: proc_macro2::TokenStream = input.into();
19 let tokens = input.clone();
20 let token_stream = match syn::parse2(input).and_then(|input| <T as CustomDerive>::parse(input, tokens)).and_then(<T as CustomDerive>::emit) {
21 Ok(token_stream) => token_stream,
22 Err(err) => err.to_compile_error(),
23 };
24 token_stream.into()
25}
26
27trait ResultExt<T> {
28 fn spanning(self, spanned: impl quote::ToTokens) -> Result<T, syn::Error>;
29}
30
31impl<T, E> ResultExt<T> for Result<T, E> where E: std::fmt::Display {
32 fn spanning(self, spanned: impl quote::ToTokens) -> Result<T, syn::Error> {
33 self.map_err(|err| syn::Error::new_spanned(spanned, err))
34 }
35}
36
37/// This custom derive can be used on a Kubernetes custom resource spec type to generate a custom resource definition object type
38/// and related trait impls.
39///
40/// # Example
41///
42/// ```rust,ignore
43/// #[derive(
44/// Clone, Debug, PartialEq,
45/// k8s_openapi_derive::CustomResourceDefinition,
46/// serde::Deserialize, serde::Serialize,
47/// )]
48/// #[custom_resource_definition(
49/// group = "k8s-openapi-tests-custom-resource-definition.com",
50/// version = "v1",
51/// plural = "foobars",
52/// generate_schema,
53/// generate_schema08,
54/// namespaced,
55/// has_subresources = "v1",
56/// impl_deep_merge,
57/// )]
58/// struct FooBarSpec {
59/// prop1: String,
60/// prop2: Vec<bool>,
61/// #[serde(skip_serializing_if = "Option::is_none")]
62/// prop3: Option<i32>,
63/// }
64/// ```
65///
66/// Note:
67///
68/// - The spec type must impl the following traits (either manually or via `#[derive]`): `Clone`, `Debug`, `PartialEq`,
69/// `serde::Deserialize` and `serde::Serialize`
70///
71/// - The name of the spec type must end with `Spec`. This suffix is trimmed to generate the name of the object type.
72///
73/// - The `k8s_openapi` crate must have been added as a dependency, since the macro expansion refers to types from it.
74///
75/// The custom derive then generates a `FooBar` type that represents a custom resource corresponding to this definition:
76///
77/// ```rust,ignore
78/// /// Custom resource for FooBarSpec
79/// #[derive(Clone, Debug, Default, PartialEq)]
80/// struct FooBar {
81/// metadata: k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta,
82/// spec: Option<FooBarSpec>,
83/// subresources: k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceSubresources,
84/// }
85///
86/// impl k8s_openapi::Resource for FooBar { ... }
87///
88/// impl k8s_openapi::ListableResource for FooBar { ... }
89///
90/// impl k8s_openapi::Metadata for FooBar {
91/// type Ty = k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
92///
93/// ...
94/// }
95///
96/// impl<'de> k8s_openapi::serde::Deserialize<'de> for FooBar { ... }
97///
98/// impl k8s_openapi::serde::Serialize for FooBar { ... }
99///
100/// impl k8s_openapi::schemars::JsonSchema for FooBar { ... }
101/// ```
102///
103/// The name of this type is automatically derived from the name of the spec type by truncating the `Spec` suffix.
104///
105/// The `group` and `version` meta items of the `#[custom_resource_definition]` attribute of the macro are used to set
106/// the "group" and "API version" in the `k8s_openapi::Resource` impl respectively. The "kind" is automatically set to be the same as the resource type name,
107/// ie `"FooBar"` in this example. The `plural` meta item is used to construct the URLs of API operations for this custom resource.
108///
109/// The `generate_schema` meta item is optional. If set, the generated custom resource type will have an impl of `schemars::JsonSchema` from the `schemars` crate.
110/// The `schemars` feature of the `k8s-openapi` crate must be enabled so that the types in that crate also have their `schemars::JsonSchema` impls enabled.
111/// You will also need to impl `schemars::JsonSchema` on the `Spec` type itself, either manually or via `#[derive(schemars::JsonSchema)]`.
112///
113/// The `generate_schema08` meta item is optional. If set, the generated custom resource type will have an impl of `schemars08::JsonSchema` from the `schemars` v0.8 crate.
114/// The `schemars08` feature of the `k8s-openapi` crate must be enabled so that the types in that crate also have their `schemars08::JsonSchema` impls enabled.
115/// You will also need to have a dependency on schemars v0.8 named `schemars08` (via `schemars08 = { package = "schemars", version = "0.8" }` in `Cargo.toml`),
116/// and impl `schemars08::JsonSchema` on the `Spec` type itself, either manually or via `#[derive(schemars08::JsonSchema)] #[schemars(crate = "schemars08")]`.
117///
118/// The `has_subresources` meta item is optional. If set, the generated custom resource type will have a `subresources` field. The value of the meta item
119/// specifies which namespace the type will be used from. For example, setting `has_subresources = "v1"` causes the field to be of the
120/// `k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceSubresources` type.
121///
122/// The `impl_deep_merge` meta item is optional. If set, the generated custom resource type will impl the `k8s_openapi::DeepMerge` trait. This impl will require
123/// you to impl `k8s_openapi::DeepMerge` on the spec type yourself.
124///
125/// You would then register this custom resource definition with Kubernetes, with code like this:
126///
127/// ```rust,ignore
128/// use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1 as apiextensions;
129/// use k8s_openapi::apimachinery::pkg::apis::meta::v1 as meta;
130///
131/// // Same as the `plurals` meta item in the `#[custom_resource_definition]` attribute
132/// let plural = "foobars";
133///
134/// let custom_resource_definition_spec = apiextensions::CustomResourceDefinitionSpec {
135/// group: <FooBar as k8s_openapi::Resource>::GROUP.to_owned(),
136/// names: apiextensions::CustomResourceDefinitionNames {
137/// kind: <FooBar as k8s_openapi::Resource>::KIND.to_owned(),
138/// plural: plural.to_owned(),
139/// short_names: Some(vec!["fb".to_owned()]),
140/// singular: Some("foobar".to_owned()),
141/// ..Default::default()
142/// },
143/// scope: "Namespaced".to_owned(),
144/// version: <FooBar as k8s_openapi::Resource>::VERSION.to_owned().into(),
145/// ..Default::default()
146/// };
147///
148/// let custom_resource_definition = apiextensions::CustomResourceDefinition {
149/// metadata: meta::ObjectMeta {
150/// name: Some(format!("{plural}.{}", <FooBar as k8s_openapi::Resource>::GROUP)),
151/// ..Default::default()
152/// },
153/// spec: custom_resource_definition_spec.into(),
154/// ..Default::default()
155/// };
156///
157/// client.create(custom_resource_definition);
158/// ```
159///
160/// (You may wish to generate run your crate through `cargo-expand` to see the macro expansion.)
161///
162/// See the [`custom_resource_definition` test in the repository](https://github.com/Arnavion/k8s-openapi/blob/master/k8s-openapi-tests/src/custom_resource_definition.rs)
163/// for a full example of using this custom derive.
164#[proc_macro_derive(CustomResourceDefinition, attributes(custom_resource_definition))]
165pub fn derive_custom_resource_definition(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
166 run_custom_derive::<custom_resource_definition::CustomResourceDefinition>(input)
167}