Skip to content

Commit 36bf874

Browse files
committed
Add enum support for TaskMessage and enhance schema handling for enums
1 parent 29fffe1 commit 36bf874

3 files changed

Lines changed: 250 additions & 3 deletions

File tree

src/iop/cls/IOP/Message.cls

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,11 +418,21 @@ ClassMethod GetContentArray(
418418
ClassMethod SchemaToContents(
419419
schema As %DynamicObject,
420420
Output pContents) As %Status
421+
{
422+
// Call internal method with root schema tracking
423+
Return ##class(IOP.Message).SchemaToContentsInternal(schema, schema, .pContents)
424+
}
425+
426+
/// Internal method - same as SchemaToContents but tracks root schema for $defs resolution
427+
ClassMethod SchemaToContentsInternal(
428+
schema As %DynamicObject,
429+
rootSchema As %DynamicObject,
430+
Output pContents) As %Status
421431
{
422432
Set tSC = $$$OK
423433
Try {
424434
Set idx = 0
425-
Do ..ProcessProperties(schema.properties, .idx, .pContents, schema)
435+
Do ..ProcessPropertiesInternal(schema.properties, .idx, .pContents, rootSchema)
426436
}
427437
Catch ex {
428438
Set tSC = ex.AsStatus()
@@ -444,6 +454,63 @@ ClassMethod ProcessProperties(
444454
Return $$$OK
445455
}
446456

457+
ClassMethod ProcessPropertiesInternal(
458+
properties As %DynamicObject,
459+
ByRef idx As %Integer,
460+
Output pContents,
461+
rootSchema As %DynamicObject) As %Status
462+
{
463+
Set iterator = properties.%GetIterator()
464+
While iterator.%GetNext(.key, .value) {
465+
Set idx = idx + 1
466+
Do ..HandlePropertyInternal(value, .key, idx, .pContents, rootSchema)
467+
}
468+
Return $$$OK
469+
}
470+
471+
ClassMethod HandlePropertyInternal(
472+
value As %DynamicObject,
473+
ByRef key As %String,
474+
idx As %Integer,
475+
Output pContents,
476+
rootSchema As %DynamicObject)
477+
{
478+
Set type = value.type
479+
480+
If (type = "string") || (type = "number") || (type = "boolean") {
481+
Do ..HandlePrimitiveType(type, idx, .pContents)
482+
}
483+
ElseIf type = "array" {
484+
Do ..HandleArrayTypeInternal(value, .key, idx, .pContents, rootSchema)
485+
}
486+
ElseIf type = "object" {
487+
Do ..HandleObjectType(value, idx, .pContents)
488+
}
489+
ElseIf $IsObject(value.allOf) {
490+
Do ..HandleAllOfTypeInternal(value, key, idx, .pContents, rootSchema)
491+
}
492+
ElseIf value.%Get("$ref")'="" {
493+
Set tDef = rootSchema."$defs".%Get($Piece(value."$ref", "/", *))
494+
// Check if it's an enum or an object
495+
If $IsObject(tDef.enum) {
496+
// Handle enum - treat as string with enum values
497+
Do ..HandleEnumType(tDef, idx, .pContents)
498+
}
499+
Else {
500+
// Handle object type
501+
Do ..HandleObjectTypeInternal(tDef, idx, .pContents, rootSchema)
502+
}
503+
}
504+
Else {
505+
Set pContents(idx,"type") = type
506+
}
507+
508+
If type = "array" Set key = key_"()"
509+
Set pContents = idx
510+
Set pContents(idx,"name") = key
511+
Set pContents(idx,"alias") = key
512+
}
513+
447514
ClassMethod HandleProperty(
448515
value As %DynamicObject,
449516
ByRef key As %String,
@@ -467,7 +534,15 @@ ClassMethod HandleProperty(
467534
}
468535
ElseIf value.%Get("$ref")'="" {
469536
Set tDef = schema."$defs".%Get($Piece(value."$ref", "/", *))
470-
Do ..HandleObjectType(tDef, idx, .pContents)
537+
// Check if it's an enum or an object
538+
If $IsObject(tDef.enum) {
539+
// Handle enum - treat as string with enum values
540+
Do ..HandleEnumType(tDef, idx, .pContents)
541+
}
542+
Else {
543+
// Handle object type
544+
Do ..HandleObjectType(tDef, idx, .pContents)
545+
}
471546
}
472547
Else {
473548
Set pContents(idx,"type") = type
@@ -502,6 +577,18 @@ ClassMethod HandleArrayType(
502577
Do ..HandleProperty(value.items, key, idx, .pContents, schema)
503578
}
504579

580+
ClassMethod HandleArrayTypeInternal(
581+
value As %DynamicObject,
582+
ByRef key As %String,
583+
idx As %Integer,
584+
Output pContents,
585+
rootSchema As %DynamicObject)
586+
{
587+
Set pContents(idx,"type") = "()"
588+
// Handle array as a Handle Property
589+
Do ..HandlePropertyInternal(value.items, key, idx, .pContents, rootSchema)
590+
}
591+
505592
ClassMethod HandleObjectType(
506593
value As %DynamicObject,
507594
idx As %Integer,
@@ -514,6 +601,37 @@ ClassMethod HandleObjectType(
514601
}
515602
}
516603

604+
ClassMethod HandleObjectTypeInternal(
605+
value As %DynamicObject,
606+
idx As %Integer,
607+
Output pContents,
608+
rootSchema As %DynamicObject)
609+
{
610+
Set pContents(idx,"type") = "object"
611+
If $IsObject(value.properties) {
612+
Do ..SchemaToContentsInternal(value, rootSchema, .subContents)
613+
Merge pContents(idx) = subContents
614+
}
615+
}
616+
617+
ClassMethod HandleEnumType(
618+
value As %DynamicObject,
619+
idx As %Integer,
620+
Output pContents)
621+
{
622+
Set pContents(idx,"type") = "%String"
623+
// Store enum values for reference
624+
If $IsObject(value.enum) {
625+
Set enumString = ""
626+
Set enumIterator = value.enum.%GetIterator()
627+
While enumIterator.%GetNext(.enumIdx, .enumValue) {
628+
If enumString '= "" Set enumString = enumString_","
629+
Set enumString = enumString_enumValue
630+
}
631+
Set pContents(idx,"enum") = enumString
632+
}
633+
}
634+
517635
ClassMethod HandleAllOfType(
518636
value As %DynamicObject,
519637
key As %String,
@@ -539,6 +657,31 @@ ClassMethod HandleAllOfType(
539657
}
540658
}
541659

660+
ClassMethod HandleAllOfTypeInternal(
661+
value As %DynamicObject,
662+
key As %String,
663+
idx As %Integer,
664+
Output pContents,
665+
rootSchema As %DynamicObject)
666+
{
667+
Set pContents(idx) = 1 //TODO size of subContents
668+
Set pContents(idx,"type") = "object"
669+
Set pContents(idx,"name") = key
670+
Set pContents(idx,"alias") = key
671+
672+
Set allOfIterator = value.allOf.%GetIterator()
673+
While allOfIterator.%GetNext(.allOfKey, .allOfValue) {
674+
If $IsObject(allOfValue."$ref") {
675+
Do ..SchemaToContentsInternal(allOfValue."$ref", rootSchema, .subContents)
676+
}
677+
Else {
678+
Set tDef = rootSchema."$defs".%Get($Piece(allOfValue."$ref","/",*))
679+
Do ..SchemaToContentsInternal(tDef, rootSchema, .subContents)
680+
}
681+
Merge pContents(idx) = subContents
682+
}
683+
}
684+
542685
Method bufferGet()
543686
{
544687
Quit ..#BUFFER

src/tests/e2e/local/test_dtl.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import iris
44
import json
55
from iop._utils import _Utils
6-
from fixtures.message import SimpleMessage, ComplexMessage
6+
from fixtures.message import SimpleMessage, ComplexMessage, TaskMessage
77

88
# Constants
99
TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'cls')
@@ -102,3 +102,84 @@ def test_set_transform_vdoc(self, load_cls_files, iop_message):
102102

103103
assert json.loads(result.json) == json.loads('{"string":"Foo", "integer":42}')
104104
assert result.classname == 'fixtures.message.SimpleMessage'
105+
106+
107+
class TestEnumSchemaHandling:
108+
"""Test that enums are properly handled in JSON schema to contents conversion."""
109+
110+
def test_enum_schema_to_contents(self):
111+
"""Test that enum types are properly converted to contents format."""
112+
# Register TaskMessage schema
113+
_Utils.register_message_schema(TaskMessage)
114+
115+
# Get the schema from storage
116+
schema_name = f"{TaskMessage.__module__}.{TaskMessage.__name__}"
117+
iop_schema = iris.cls('IOP.Message.JSONSchema')._OpenId(schema_name)
118+
assert iop_schema is not None
119+
120+
# Parse the schema
121+
schema = json.loads(iop_schema.JSONSchema)
122+
123+
# Verify schema has definitions for enums
124+
assert '$defs' in schema
125+
assert 'Status' in schema['$defs']
126+
assert 'Priority' in schema['$defs']
127+
128+
# Verify enum definition structure
129+
status_enum = schema['$defs']['Status']
130+
assert status_enum['type'] == 'string'
131+
assert 'enum' in status_enum
132+
assert set(status_enum['enum']) == {'ACTIVE', 'INACTIVE', 'PENDING'}
133+
134+
priority_enum = schema['$defs']['Priority']
135+
assert priority_enum['type'] == 'string'
136+
assert 'enum' in priority_enum
137+
assert set(priority_enum['enum']) == {'HIGH', 'MEDIUM', 'LOW'}
138+
139+
def test_schema_to_contents_with_enum(self):
140+
"""Test SchemaToContents correctly processes enum definitions."""
141+
# Register TaskMessage schema
142+
_Utils.register_message_schema(TaskMessage)
143+
144+
# Get schema
145+
schema_name = f"{TaskMessage.__module__}.{TaskMessage.__name__}"
146+
iop_schema = iris.cls('IOP.Message.JSONSchema')._OpenId(schema_name)
147+
schema_json = iop_schema.JSONSchema
148+
schema = iris.cls('%DynamicObject')._FromJSON(schema_json)
149+
150+
# Convert schema to contents
151+
contents = iris.ref(None)
152+
_Utils.raise_on_error(iris.cls('IOP.Message').SchemaToContents(schema, contents))
153+
154+
# Verify contents were generated
155+
result = contents.value
156+
assert result is not None
157+
158+
def test_enum_ref_resolution(self):
159+
"""Test that enum references in schema are properly resolved."""
160+
# Create schema with enum references
161+
schema_json = """{
162+
"type": "object",
163+
"properties": {
164+
"name": {"type": "string"},
165+
"status": {"$ref": "#/$defs/Status"}
166+
},
167+
"$defs": {
168+
"Status": {
169+
"type": "string",
170+
"enum": ["ACTIVE", "INACTIVE", "PENDING"],
171+
"title": "Status"
172+
}
173+
}
174+
}"""
175+
176+
schema = iris.cls('%DynamicObject')._FromJSON(schema_json)
177+
178+
# Convert to contents
179+
contents = iris.ref(None)
180+
_Utils.raise_on_error(iris.cls('IOP.Message').SchemaToContents(schema, contents))
181+
182+
# Verify that contents generated successfully
183+
result = contents.value
184+
assert result is not None
185+

src/tests/fixtures/message.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,35 @@
11
from typing import List, Dict
22
from iop import Message, PickleMessage, PydanticMessage
3+
from enum import Enum
4+
from pydantic import BaseModel
35

46
from dataclasses import dataclass
57

68
from obj import PostClass
79

810
from datetime import datetime, date, time
911

12+
# Enum types for testing
13+
class Status(str, Enum):
14+
ACTIVE = "ACTIVE"
15+
INACTIVE = "INACTIVE"
16+
PENDING = "PENDING"
17+
18+
class Priority(str, Enum):
19+
HIGH = "HIGH"
20+
MEDIUM = "MEDIUM"
21+
LOW = "LOW"
22+
23+
# Pydantic model with enums
24+
class TaskModel(BaseModel):
25+
name: str
26+
status: Status
27+
priority: Priority
28+
29+
# Pydantic message with enums
30+
class TaskMessage(PydanticMessage):
31+
task: TaskModel
32+
1033
class PydanticPostClass(PydanticMessage):
1134
Title: str
1235
Selftext : str

0 commit comments

Comments
 (0)