Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One-of required fields should generate Rust enums, not Option<T> #39

Open
alex-hunt-materialize opened this issue Apr 1, 2022 · 3 comments
Labels
bug Something isn't working help wanted Extra attention is needed

Comments

@alex-hunt-materialize
Copy link
Contributor

As only one value can be supplied at a time, it would be better to generate a Rust enum, so that the type system can enforce the one-of constraint.

Example:

              podSelector:
                description: Selects pods in the same namespace.
                oneOf:
                - required:
                  - matchExpressions
                - required:
                  - matchLabels
                properties:
                  matchExpressions:
                    items:
                      properties:
                        key:
                          type: string
                        operator:
                          enum:
                          - In
                          - NotIn
                          - Exists
                          - DoesNotExist
                          type: string
                        values:
                          items:
                            type: string
                          type: array
                      required:
                      - key
                      - operator
                      type: object
                    type: array
                  matchLabels:
                    type: object
                    x-kubernetes-preserve-unknown-fields: true
                type: object

This generates:

/// Selects pods in the same namespace.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ServerPodSelector {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub matchExpressions: Vec<ServerPodSelectorMatchExpressions>,
    pub matchLabels: Option<ServerPodSelectorMatchLabels>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ServerPodSelectorMatchExpressions {
    pub key: String,
    pub operator: String,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub values: Vec<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ServerPodSelectorMatchLabels {
}

Complete CRD https://gist.github.com/alex-hunt-materialize/2743b1e2e58a49c4df0a11ecb39f46ab

@clux clux added bug Something isn't working help wanted Extra attention is needed labels Apr 1, 2022
@clux
Copy link
Member

clux commented May 1, 2022

Had a bit of a look at this in #65. I'm expecting we will want to generate something like:

pub enum ServerPodSelector {
    MatchExpressions(Vec<ServerPodSelectorMatchExpressions>),
    MatchLabels(BTreeMap<String, serde_json::Value>), 
}

pub struct ServerPodSelectorMatchExpressions {
    pub key: String,
    pub operator: ServerPodSelectorMatchExpressionsOperator,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub values: Option<Vec<String>>,
}

pub enum ServerPodSelectorMatchExpressionsOperator {
    In,
    NotIn,
    Exists,
    DoesNotExists,
}

depending on choice in #31 for the preserve prop..
need to cross-reference with kube-derive to see what it is generating.

@clux
Copy link
Member

clux commented May 1, 2022

Ok, verified against kube-derive. The following example:

use kube::{CustomResource, CustomResourceExt};
use schemars::JsonSchema;
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};

#[derive(CustomResource, Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[kube(
    group = "clux.dev",
    version = "v1",
    kind = "Server",
    namespaced,
)]
#[serde(rename_all = "camelCase")]
pub struct ServerSpec {
    pod_selector: ServerPodSelector
}

#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub enum ServerPodSelector {
  MatchExpressions(Vec<ServerPodSelectorMatchExpressions>),
  MatchLabels(BTreeMap<String, String>),
}

#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ServerPodSelectorMatchExpressions {
  pub key: String,
  pub operator: ServerPodSelectorMatchExpressionsOperator,
  #[serde(default, skip_serializing_if = "Option::is_none")]
  pub values: Option<Vec<String>>,
}

#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
pub enum ServerPodSelectorMatchExpressionsOperator {
  In,
  NotIn,
  Exists,
  DoesNotExists,
}


fn main() {
    let crd = serde_yaml::to_string(&Server::crd()).unwrap();
    println!("CRD: \n{}", crd);
}

prints the crd with the following schema:

          description: "Auto-generated derived type for ServerSpec via `CustomResource`"
          properties:
            spec:
              properties:
                podSelector:
                  oneOf:
                    - required:
                        - matchExpressions
                    - required:
                        - matchLabels
                  properties:
                    matchExpressions:
                      items:
                        properties:
                          key:
                            type: string
                          operator:
                            enum:
                              - In
                              - NotIn
                              - Exists
                              - DoesNotExists
                            type: string
                          values:
                            items:
                              type: string
                            nullable: true
                            type: array
                        required:
                          - key
                          - operator
                        type: object
                      type: array
                    matchLabels:
                      additionalProperties: true
                      type: object
                  type: object
              required:
                - podSelector
              type: object
          required:
            - spec
          title: Server
          type: object

which matches almost perfectly.

@alex-hunt-materialize
Copy link
Contributor Author

That output looks fantastic!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants