diff --git a/jsf/parser.py b/jsf/parser.py index 3174429..92f5e90 100644 --- a/jsf/parser.py +++ b/jsf/parser.py @@ -264,8 +264,8 @@ def __parse_definition( enum_list = schema["enum"] assert len(enum_list) > 0, "Enum List is Empty" assert all( - isinstance(item, (int, float, str, dict, type(None))) for item in enum_list - ), "Enum Type is not null, int, float, string or dict" + isinstance(item, (bool, int, float, str, dict, type(None))) for item in enum_list + ), "Enum Type is not null, bool, int, float, string or dict" return JSFEnum.from_dict( { "name": name, diff --git a/jsf/schema_types/base.py b/jsf/schema_types/base.py index 3edd443..1dea205 100644 --- a/jsf/schema_types/base.py +++ b/jsf/schema_types/base.py @@ -1,6 +1,7 @@ import logging import random import uuid +from collections import ChainMap from typing import Any, Dict, List, Optional, Tuple, Type, Union from pydantic import BaseModel, Field @@ -28,6 +29,10 @@ class BaseSchema(BaseModel): # The $comment keyword is strictly intended for adding comments to the JSON schema source. Its value must always be a string. Unlike the annotations title, description and examples, JSON schema implementations aren't allowed to attach any meaning or behavior to it whatsoever, and may even strip them at any time. Therefore, they are useful for leaving notes to future editors of a JSON schema, (which is quite likely your future self), but should not be used to communicate to users of the schema. comments: Optional[str] = Field(None, alias="$comments") + oneOf: Optional[List[Dict[str, Any]]] = None + anyOf: Optional[List[Dict[str, Any]]] = None + allOf: Optional[List[Dict[str, Any]]] = None + # JSF Custom fields path: Optional[str] = None name: Optional[str] = None @@ -49,6 +54,20 @@ def generate(self, context: Dict[str, Any]) -> Any: if self.set_state is not None: context["state"][self.path] = {k: eval(v, context)() for k, v in self.set_state.items()} + if self.oneOf is not None and self.__class__.__name__ != "OneOf": + additional_validations = random.choice(self.oneOf) + for k, v in additional_validations.items(): + setattr(self, k, v) + + if self.allOf is not None and self.__class__.__name__ != "AllOf": + additional_validations = dict(ChainMap(*self.allOf)) + for k, v in additional_validations.items(): + setattr(self, k, v) + if self.anyOf is not None and self.__class__.__name__ != "AnyOf": + additional_validations = dict(ChainMap(*random.choices(self.anyOf, k=len(self.anyOf)))) + for k, v in additional_validations.items(): + setattr(self, k, v) + if self.is_nullable and ( random.uniform(0, 1) < self.allow_none_optionals or context["state"]["__depth__"] > self.max_recursive_depth diff --git a/jsf/schema_types/enum.py b/jsf/schema_types/enum.py index f93b4c3..95d1660 100644 --- a/jsf/schema_types/enum.py +++ b/jsf/schema_types/enum.py @@ -12,7 +12,7 @@ class JSFEnum(BaseSchema): - enum: Optional[List[Union[str, int, float, dict, None]]] = [] + enum: Optional[List[Union[bool, str, int, float, dict, None]]] = [] model_config = ConfigDict() def generate(self, context: Dict[str, Any]) -> Optional[Union[str, int, float]]: diff --git a/jsf/tests/data/bool-enum.json b/jsf/tests/data/bool-enum.json new file mode 100644 index 0000000..f33930e --- /dev/null +++ b/jsf/tests/data/bool-enum.json @@ -0,0 +1,3 @@ +{ + "enum": [true] +} diff --git a/jsf/tests/data/min-required-props-oneof.json b/jsf/tests/data/min-required-props-oneof.json new file mode 100644 index 0000000..5b4b4f4 --- /dev/null +++ b/jsf/tests/data/min-required-props-oneof.json @@ -0,0 +1,15 @@ +{ + "type": "object", + "friendly_name": "Corporate Structure Changes", + "description": "Parameters for corporate structure changes", + "properties": { + "new_parent": { "const": true }, + "new_subsidiary": { "const": true }, + "new_sibling": { "const": true } + }, + "oneOf": [ + { "required": ["new_parent"] }, + { "required": ["new_subsidiary"] }, + { "required": ["new_sibling"] } + ] +} diff --git a/jsf/tests/test_default_fake.py b/jsf/tests/test_default_fake.py index 56b42e6..2a341f5 100644 --- a/jsf/tests/test_default_fake.py +++ b/jsf/tests/test_default_fake.py @@ -544,3 +544,27 @@ def test_use_defaults_and_examples(TestData): assert d["name"] in ["Chop", "Luna", "Thanos"] breed = d.get("breed") assert breed is None or breed == "Mixed Breed" + + +def test_min_required_props_oneof(TestData): + with open(TestData / "min-required-props-oneof.json") as file: + schema = json.load(file) + p = JSF(schema) + fake_data = [p.generate(use_examples=True) for _ in range(100)] + + for d in fake_data: + assert isinstance(d, dict) + assert len(d.keys()) >= 1 + assert all(isinstance(v, bool) for v in d.values()) + assert all(d.values()) + + +def test_bool_enum(TestData): + with open(TestData / "bool-enum.json") as file: + schema = json.load(file) + p = JSF(schema) + fake_data = [p.generate() for _ in range(100)] + + for d in fake_data: + assert isinstance(d, bool) + assert d