schemars icon indicating copy to clipboard operation
schemars copied to clipboard

feat: use enum values as keys for map

Open MicaiahReid opened this issue 2 years ago • 1 comments

This PR fixes https://github.com/GREsau/okapi/issues/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 avatar Jul 08 '23 00:07 MicaiahReid

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

MicaiahReid avatar Jul 12 '23 14:07 MicaiahReid