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

feat: use enum values as keys for map #231

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

MicaiahReid
Copy link

@MicaiahReid MicaiahReid commented Jul 8, 2023

This PR fixes GREsau/okapi#128.

When generating a schema for a Map with a key that is an enum, the generated schema only allows keys based off of the enum options. For example,

use schemars::{schema_for, JsonSchema};
use serde::Serialize;
use std::collections::BTreeMap;

#[derive(serde::Serialize, JsonSchema, Debug)]
struct EnumKeyStruct {
    key: BTreeMap<Thing, SomeOtherStruct>,
}

#[derive(serde::Serialize, JsonSchema, Debug)]
enum Thing {
    Option1,
    Option2,
}

#[derive(serde::Serialize, JsonSchema, Debug)]
struct SomeOtherStruct {
    key1: String,
    key2: u64,
    key3: bool,
}

fn main() {
    let schema = schema_for!(EnumKeyStruct);
    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}

yields:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "EnumKeyStruct",
  "type": "object",
  "required": [
    "key"
  ],
  "properties": {
    "key": {
      "oneOf": [
        {
          "type": "object",
          "required": [
            "\"Option1\""
          ],
          "properties": {
            "\"Option1\"": {
              "$ref": "#/definitions/SomeOtherStruct"
            }
          }
        },
        {
          "type": "object",
          "required": [
            "\"Option2\""
          ],
          "properties": {
            "\"Option2\"": {
              "$ref": "#/definitions/SomeOtherStruct"
            }
          }
        }
      ]
    }
  },
  "definitions": {
    "SomeOtherStruct": {
      "type": "object",
      "required": [
        "key1",
        "key2",
        "key3"
      ],
      "properties": {
        "key1": {
          "type": "string"
        },
        "key2": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0.0
        },
        "key3": {
          "type": "boolean"
        }
      }
    },
    "Thing": {
      "type": "string",
      "enum": [
        "Option1",
        "Option2"
      ]
    }
  }
}

This also maintains the original behavior for maps with non-enum keys:

BTreeMap<String, String>

Code

use schemars::{schema_for, JsonSchema};
use serde::Serialize;
use std::collections::BTreeMap;

#[derive(Serialize, JsonSchema, Debug)]
struct PlainMap {
    key: BTreeMap<String, String>,
}

fn main() {
    let schema = schema_for!(PlainMap);
    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}

Output

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "PlainMap",
  "type": "object",
  "required": [
    "key"
  ],
  "properties": {
    "key": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    }
  }
}
BTreeMap<SomeStruct, String>

Code

use schemars::{schema_for, JsonSchema};
use serde::Serialize;
use std::collections::BTreeMap;

#[derive(serde::Serialize, JsonSchema, Debug)]
struct SomeStruct {
    key: BTreeMap<SomeOtherStruct, String>,
}

#[derive(serde::Serialize, JsonSchema, Debug)]
struct SomeOtherStruct {
    key1: String,
    key2: u64,
    key3: bool,
}
fn main() {
    let schema = schema_for!(SomeOtherStruct);
    println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}

Output

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "SomeOtherStruct",
  "type": "object",
  "required": [
    "key1",
    "key2",
    "key3"
  ],
  "properties": {
    "key1": {
      "type": "string"
    },
    "key2": {
      "type": "integer",
      "format": "uint64",
      "minimum": 0.0
    },
    "key3": {
      "type": "boolean"
    }
  }
}

@MicaiahReid MicaiahReid marked this pull request as ready for review July 12, 2023 14:43
@MicaiahReid
Copy link
Author

@GREsau I think I've got this working, I'd appreciate a review!

for value in values {
// enum values all have quotes around them, so remove them
let str_value = &value.to_string();
let value = format!("{}", &str_value[1..str_value.len()-1]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definitely feels a bit hacky, I'm open to suggestions.

Copy link
Owner

@GREsau GREsau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, but unfortunately I don't think the schemas created by this change are correct.

In your example:

  • according to the schema, key must be either an object with an Option1 property, or an object with an Option2 property (but not both). This means the schema would reject empty maps, or a map with both Option1 and Option2.
  • it probably shouldn't add Thing to definitions since it's not actually referenced anywhere in the schema.
  • according to the schema, the property names include double quotes (e.g. "Option1" instead of Option1), which is not correct

I think the correct schema would be something like:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "EnumKeyStruct",
  "type": "object",
  "required": [
    "key"
  ],
  "properties": {
    "key": {
      "type": "object",
      "properties": {
        "Option1": {
          "$ref": "#/definitions/SomeOtherStruct"
        },
        "Option2": {
          "$ref": "#/definitions/SomeOtherStruct"
        }
      }
    }
  },
  "definitions": {
    "SomeOtherStruct": {      
      /* ... */
    }
  }
}

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.

Enforce strict key values for BTreeMap with enum key
2 participants