Skip to content

Commit a7cbfbf

Browse files
thcedclaude
andcommitted
feat(schema): SchemaParser handles objects (with additionalProperties) and arrays
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6dd90d9 commit a7cbfbf

2 files changed

Lines changed: 91 additions & 2 deletions

File tree

src/main/java/com/retailsvc/http/spec/schema/SchemaParser.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.retailsvc.http.spec.schema;
22

33
import java.util.EnumSet;
4+
import java.util.LinkedHashMap;
45
import java.util.List;
56
import java.util.Map;
67
import java.util.Set;
@@ -26,8 +27,8 @@ public static Schema parse(Map<String, Object> raw) {
2627
case NUMBER -> parseNumber(raw, types);
2728
case BOOLEAN -> new BooleanSchema(types);
2829
case NULL -> new NullSchema();
29-
case OBJECT, ARRAY ->
30-
throw new UnsupportedOperationException("object/array parsing comes in C2");
30+
case OBJECT -> parseObject(raw, types);
31+
case ARRAY -> parseArray(raw, types);
3132
};
3233
}
3334

@@ -80,6 +81,47 @@ private static NumberSchema parseNumber(Map<String, Object> raw, Set<TypeName> t
8081
(String) raw.get("format"));
8182
}
8283

84+
@SuppressWarnings("unchecked")
85+
private static ObjectSchema parseObject(Map<String, Object> raw, Set<TypeName> types) {
86+
Map<String, Object> rawProps = (Map<String, Object>) raw.getOrDefault("properties", Map.of());
87+
Map<String, Schema> properties = new LinkedHashMap<>();
88+
for (var e : rawProps.entrySet()) {
89+
properties.put(e.getKey(), parse((Map<String, Object>) e.getValue()));
90+
}
91+
List<String> required = (List<String>) raw.getOrDefault("required", List.of());
92+
AdditionalProperties ap = parseAdditionalProperties(raw.get("additionalProperties"));
93+
return new ObjectSchema(
94+
types,
95+
Map.copyOf(properties),
96+
List.copyOf(required),
97+
ap,
98+
toIntOrNull(raw.get("minProperties")),
99+
toIntOrNull(raw.get("maxProperties")));
100+
}
101+
102+
@SuppressWarnings("unchecked")
103+
private static AdditionalProperties parseAdditionalProperties(Object value) {
104+
if (value == null || Boolean.TRUE.equals(value)) {
105+
return new AdditionalProperties.Allowed();
106+
}
107+
if (Boolean.FALSE.equals(value)) {
108+
return new AdditionalProperties.Forbidden();
109+
}
110+
return new AdditionalProperties.SchemaConstraint(parse((Map<String, Object>) value));
111+
}
112+
113+
@SuppressWarnings("unchecked")
114+
private static ArraySchema parseArray(Map<String, Object> raw, Set<TypeName> types) {
115+
Map<String, Object> items = (Map<String, Object>) raw.getOrDefault("items", Map.of());
116+
Schema itemSchema = items.isEmpty() ? new NullSchema() : parse(items);
117+
return new ArraySchema(
118+
types,
119+
itemSchema,
120+
toIntOrNull(raw.get("minItems")),
121+
toIntOrNull(raw.get("maxItems")),
122+
Boolean.TRUE.equals(raw.get("uniqueItems")));
123+
}
124+
83125
private static Integer toIntOrNull(Object v) {
84126
return v == null ? null : ((Number) v).intValue();
85127
}

src/test/java/com/retailsvc/http/spec/schema/SchemaParserTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,51 @@ void parsesLegacyNullableTrueAsTypeUnion() {
6060
Schema s = SchemaParser.parse(Map.of("type", "string", "nullable", true));
6161
assertThat(s.types()).containsExactlyInAnyOrder(TypeName.STRING, TypeName.NULL);
6262
}
63+
64+
@Test
65+
void parsesObjectWithRequiredAndProperties() {
66+
Map<String, Object> raw =
67+
Map.of(
68+
"type", "object",
69+
"required", List.of("name"),
70+
"properties", Map.of("name", Map.of("type", "string")));
71+
ObjectSchema o = (ObjectSchema) SchemaParser.parse(raw);
72+
assertThat(o.required()).containsExactly("name");
73+
assertThat(o.properties()).containsKey("name");
74+
assertThat(o.properties().get("name")).isInstanceOf(StringSchema.class);
75+
assertThat(o.additionalProperties()).isInstanceOf(AdditionalProperties.Allowed.class);
76+
}
77+
78+
@Test
79+
void parsesObjectWithAdditionalPropertiesFalse() {
80+
Map<String, Object> raw = Map.of("type", "object", "additionalProperties", false);
81+
ObjectSchema o = (ObjectSchema) SchemaParser.parse(raw);
82+
assertThat(o.additionalProperties()).isInstanceOf(AdditionalProperties.Forbidden.class);
83+
}
84+
85+
@Test
86+
void parsesObjectWithAdditionalPropertiesSchema() {
87+
Map<String, Object> raw =
88+
Map.of("type", "object", "additionalProperties", Map.of("type", "string"));
89+
ObjectSchema o = (ObjectSchema) SchemaParser.parse(raw);
90+
assertThat(o.additionalProperties()).isInstanceOf(AdditionalProperties.SchemaConstraint.class);
91+
}
92+
93+
@Test
94+
void parsesArrayWithItems() {
95+
Map<String, Object> raw =
96+
Map.of(
97+
"type",
98+
"array",
99+
"items",
100+
Map.of("type", "integer"),
101+
"minItems",
102+
1,
103+
"uniqueItems",
104+
true);
105+
ArraySchema a = (ArraySchema) SchemaParser.parse(raw);
106+
assertThat(a.items()).isInstanceOf(IntegerSchema.class);
107+
assertThat(a.minItems()).isEqualTo(1);
108+
assertThat(a.uniqueItems()).isTrue();
109+
}
63110
}

0 commit comments

Comments
 (0)