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

test out enum variants in jsonschemas #398

Closed
wants to merge 1 commit into from
Closed

test out enum variants in jsonschemas #398

wants to merge 1 commit into from

Conversation

clux
Copy link
Member

@clux clux commented Feb 7, 2021

Indeed correct what is said in #129 (comment)
By default, enum variants is not well-received by kubernetes api, as they are not structural:

Error: ApiError: CustomResourceDefinition.apiextensions.k8s.io "foos.clux.dev" is invalid: [spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.anyOf[0].properties[VaraintA].type: Forbidden: must be empty to be structural, spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.anyOf[0].type: Forbidden: must be empty to be structural, spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.anyOf[1].properties[VariantB].type: Forbidden: must be empty to be structural, spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.anyOf[1].type: Forbidden: must be empty to be structural, spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.type: Required value: must not be empty for specified array items]: Invalid (ErrorResponse { status: "Failure", message: "CustomResourceDefinition.apiextensions.k8s.io \"foos.clux.dev\" is invalid: [spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.anyOf[0].properties[VaraintA].type: Forbidden: must be empty to be structural, spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.anyOf[0].type: Forbidden: must be empty to be structural, spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.anyOf[1].properties[VariantB].type: Forbidden: must be empty to be structural, spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.anyOf[1].type: Forbidden: must be empty to be structural, spec.validation.openAPIV3Schema.properties[spec].properties[variants].items.type: Required value: must not be empty for specified array items]", reason: "Invalid", code: 422 })

@clux
Copy link
Member Author

clux commented Feb 7, 2021

Tried a few variants on turning VariantType into a tagged enum:

#[serde(tag = "type", content = "value")]
enum VariantType {
    VaraintA(u32),
    VariantB(String),
}

but same error :/

@thedodd
Copy link

thedodd commented Feb 8, 2021

I'll have to pull this branch and start digging into the individual error statements. Overall, I was originally thinking that going down the path of using OpenAPI Discriminators would be the best option, alas k8s does not yet support that approach.

As such, it would seem that we will need to stick with generating the full schema inline. This mostly seems to be an issue with how we are generating the oneOf statements. If we can pin those down and ensure that they are structural, then we should be gold.

Hopefully some of that info is useful.

@kazk
Copy link
Member

kazk commented Feb 12, 2021

We don't support generating structural schema yet. Go frameworks doesn't either as far as I know.

A workaround is to use #[schemars(schema_with = "func")] to use a function that returns structural schema for that type or use a newtype that implements JsonSchema that's structural.

We should first find some examples of non-structural to structural transformations and see if it's something we can support, or at least document how to do it. I wish Kubernetes had documentation about this transformation.

@12101111
Copy link

12101111 commented Jun 6, 2021

I write a workaround for this:

use schemars::{
    schema::{Schema, SchemaObject},
    visit::{visit_schema_object, Visitor},
    JsonSchema,
};

#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[serde(tag = "runtime")]
pub enum Runtime {
    Deno(DenoManifest),
    Python(PythonManifest),
}

fn to_k8s(gen: &mut schemars::gen::SchemaGenerator) -> Schema {

    const TAG: &str = "runtime"; // keep sync with #[serde(tag = "")]

    fn as_ref<'a>(s: &'a Schema) -> &'a SchemaObject {
        match s {
            Schema::Object(o) => o,
            Schema::Bool(_) => unreachable!(),
        }
    }

    fn as_mut<'a>(s: &'a mut Schema) -> &'a mut SchemaObject {
        match s {
            Schema::Object(o) => o,
            Schema::Bool(_) => unreachable!(),
        }
    }

    pub struct K8sStructualPurge;

    impl Visitor for K8sStructualPurge {
        fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
            let meta = schema.metadata();
            meta.description = None;
            meta.default = None;
            schema.instance_type = None;
            schema.object().additional_properties = None;
            if schema.extensions.contains_key("nullable") {
                schema.extensions.remove("nullable");
            }
            visit_schema_object(self, schema);
        }
    }

    let mut runtime = gen.subschema_for::<Runtime>().into_object();
    let sub = runtime.subschemas();
    let subschemas = &mut sub.any_of.as_mut().unwrap();
    let mut root = subschemas[0].clone();
    let mut tags = Vec::new();
    for s in subschemas.iter_mut() {
        // FIXME: This is unpublic api
        root = root.flatten(s.clone());
        {
            let props = &mut as_mut(s).object().properties;
            let enums = &as_ref(&props[TAG]).enum_values.as_ref().unwrap();
            assert_eq!(enums.len(), 1);
            tags.push(enums[0].clone());
        }
        let mut visitor = K8sStructualPurge;
        visitor.visit_schema_object(as_mut(s));
    }
    assert!(runtime.object.is_none());
    let mut object_validation = root.into_object().object.unwrap();
    let tag = object_validation.properties.get_mut(TAG).unwrap();
    let enums = as_mut(tag).enum_values.as_mut().unwrap();
    *enums = tags;
    object_validation.as_mut().required = schemars::Set::new();
    runtime.object = Some(object_validation);
    runtime.instance_type = Some(schemars::schema::SingleOrVec::Single(Box::new(
        schemars::schema::InstanceType::Object,
    )));
    runtime.into()
}

@kazk
Copy link
Member

kazk commented Jul 20, 2022

Fixed by #779

@kazk kazk closed this Jul 20, 2022
@clux clux deleted the enum-schema branch October 2, 2024 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants