Repository: linkml/linkml

Issue:
JSON Schema generation fails when slot uses `any_of` to combine multiple value conditions
**Describe the bug**
The `gen-json-schema` command fails when the input schema contains a slot with an `any_of` (or likely any other boolean operator) to combine multiple value conditions. The error is:

```
AttributeError: 'AnonymousSlotExpression' object has no attribute 'multivalued'
```

**To reproduce**
With the following schema:
```yaml
# test.yaml
id: https://example.com/test/
name: test
imports:
  - linkml:types
prefixes:
  linkml: https://w3id.org/linkml/

slots:
  s:
    range: string
    any_of:
      - equals_string: s1
      - equals_string: s2
      - equals_string: s3

classes:
  Test:
    slots:
      - s
```

Run:

```shell
gen-json-schema test.yaml
```



Discussion:

Patch:
diff --git a/linkml/generators/jsonschemagen.py b/linkml/generators/jsonschemagen.py
index fe3c7ff5..788219b1 100644
--- a/linkml/generators/jsonschemagen.py
+++ b/linkml/generators/jsonschemagen.py
@@ -378,17 +378,17 @@ class JsonSchemaGenerator(Generator):
 
         if slot.any_of is not None and len(slot.any_of) > 0:
             if not slot_has_range_union:
-                prop['anyOf'] = [self.get_subschema_for_slot(s, omit_type) for s in slot.any_of]
+                prop['anyOf'] = [self.get_subschema_for_slot(s, omit_type=True) for s in slot.any_of]
 
         if slot.all_of is not None and len(slot.all_of) > 0:
-            prop['allOf'] = [self.get_subschema_for_slot(s, omit_type) for s in slot.all_of]
+            prop['allOf'] = [self.get_subschema_for_slot(s, omit_type=True) for s in slot.all_of]
 
         if slot.exactly_one_of is not None and len(slot.exactly_one_of) > 0:
-            prop['oneOf'] = [self.get_subschema_for_slot(s, omit_type) for s in slot.exactly_one_of]
+            prop['oneOf'] = [self.get_subschema_for_slot(s, omit_type=True) for s in slot.exactly_one_of]
 
         if slot.none_of is not None and len(slot.none_of) > 0:
             prop['not'] = {
-                'anyOf': [self.get_subschema_for_slot(s, omit_type) for s in slot.none_of]
+                'anyOf': [self.get_subschema_for_slot(s, omit_type=True) for s in slot.none_of]
             }
 
         return prop


Test patch:
diff --git a/tests/test_generators/input/jsonschema_value_constraints.yaml b/tests/test_generators/input/jsonschema_value_constraints.yaml
new file mode 100644
index 00000000..50806f45
--- /dev/null
+++ b/tests/test_generators/input/jsonschema_value_constraints.yaml
@@ -0,0 +1,132 @@
+schema:
+  id: http://example.org/test_value_constraints
+  name: test_value_constraints
+
+  imports:
+    - https://w3id.org/linkml/types
+
+  slots:
+    string_constant:
+      range: string
+      equals_string: constant
+    string_pattern:
+      range: string
+      pattern: "pat{2,}ern[!]?"
+    integer_constant:
+      range: integer
+      equals_number: 10
+    integer_under:
+      range: integer
+      maximum_value: 10
+    integer_over:
+      range: integer
+      minimum_value: 10
+    string_any_constant:
+      range: string
+      any_of:
+        - equals_string: this
+        - equals_string: that
+    integer_big_or_small:
+      range: integer
+      any_of:
+        - maximum_value: 10
+        - minimum_value: 100
+    string_all_patterns:
+      range: string
+      all_of:
+        - pattern: Michael \S+
+        - pattern: \S+ Jordan
+    string_exactly_one_pattern:
+      range: string
+      exactly_one_of:
+        - pattern: Michael \S+
+        - pattern: \S+ Jordan
+    string_none_of:
+      range: string
+      none_of:
+        - equals_string: purple
+        - equals_string: green
+
+  classes:
+    Test:
+      tree_root: true
+      slots:
+        - string_constant
+        - string_pattern
+        - integer_constant
+        - integer_under
+        - integer_over
+        - string_any_constant
+        - integer_big_or_small
+        - string_all_patterns
+        - string_exactly_one_pattern
+        - string_none_of
+
+data_cases:
+  - data:
+      string_constant: constant
+  - data:
+      string_constant: wrong
+    error_message: "'constant' was expected"
+  - data:
+      string_pattern: pattttttern
+  - data:
+      string_pattern: pattern!
+  - data:
+      string_pattern: patern
+    error_message: does not match
+  - data:
+      integer_constant: 10
+  - data:
+      integer_constant: 9
+    error_message: 10 was expected
+  - data:
+      integer_under: 10
+  - data:
+      integer_under: 11
+    error_message: 11 is greater than the maximum of 10
+  - data:
+      integer_over: 10
+  - data:
+      integer_over: 9
+    error_message: 9 is less than the minimum of 10
+  - data:
+      string_any_constant: this
+  - data:
+      string_any_constant: that
+  - data:
+      string_any_constant: another
+    error_message: Failed validating 'anyOf'
+  - data:
+      integer_big_or_small: 9
+  - data:
+      integer_big_or_small: 101
+  - data:
+      integer_big_or_small: 50
+    error_message: Failed validating 'anyOf'
+  - data:
+      string_all_patterns: Michael Jeffrey Jordan
+  - data:
+      string_all_patterns: Air Jordan
+    error_message: does not match 'Michael \S+'
+  - data:
+      string_all_patterns: Michael J. Fox
+    error_message: does not match '\S+ Jordan'
+  - data:
+      string_exactly_one_pattern: Air Jordan
+  - data:
+      string_exactly_one_pattern: Michael J. Fox
+  - data:
+      string_exactly_one_pattern: Michael Jordan
+    error_message: is valid under each of
+  - data:
+      string_exactly_one_pattern: Scottie Pippen
+    error_message: is not valid under any of
+  - data:
+      string_none_of: orange
+  - data:
+      string_none_of: purple
+    error_message: should not be valid under
+  - data: 
+      string_none_of: green
+    error_message: should not be valid under
diff --git a/tests/test_generators/test_jsonschemagen.py b/tests/test_generators/test_jsonschemagen.py
index 85c2a081..afab82e3 100644
--- a/tests/test_generators/test_jsonschemagen.py
+++ b/tests/test_generators/test_jsonschemagen.py
@@ -173,6 +173,26 @@ classes:
         self.assertIn("id", json_schema["required"])
 
 
+    def test_value_constraints(self):
+        with open(env.input_path("jsonschema_value_constraints.yaml")) as f:
+            test_def = yaml.safe_load(f)
+        
+        generator = JsonSchemaGenerator(yaml.dump(test_def["schema"]), stacktrace=True, not_closed=False)
+        json_schema = json.loads(generator.serialize())
+
+        for data_case in test_def.get('data_cases', []):
+            data = data_case['data']
+            with self.subTest(data=data):
+                if 'error_message' in data_case:
+                    self.assertRaisesRegex(
+                        jsonschema.ValidationError, 
+                        data_case['error_message'],
+                        lambda: jsonschema.validate(data, json_schema),
+                    )
+                else:
+                    jsonschema.validate(data, json_schema)
+
+
     def test_rules(self):
         with open(RULES_CASES) as cases_file:
             cases = yaml.safe_load(cases_file)