Skip to content

Commit 6dd90d9

Browse files
thcedclaude
andcommitted
feat(schema): SchemaParser handles primitives, refs, nullable forms
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 917594f commit 6dd90d9

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.retailsvc.http.spec.schema;
2+
3+
import java.util.EnumSet;
4+
import java.util.List;
5+
import java.util.Map;
6+
import java.util.Set;
7+
8+
public final class SchemaParser {
9+
private SchemaParser() {}
10+
11+
@SuppressWarnings("unchecked")
12+
public static Schema parse(Map<String, Object> raw) {
13+
if (raw.containsKey("$ref")) {
14+
return new RefSchema((String) raw.get("$ref"));
15+
}
16+
17+
Set<TypeName> types = parseTypes(raw);
18+
19+
// Pick primary (non-null) type for record dispatch.
20+
TypeName primary =
21+
types.stream().filter(t -> t != TypeName.NULL).findFirst().orElse(TypeName.NULL);
22+
23+
return switch (primary) {
24+
case STRING -> parseString(raw, types);
25+
case INTEGER -> parseInteger(raw, types);
26+
case NUMBER -> parseNumber(raw, types);
27+
case BOOLEAN -> new BooleanSchema(types);
28+
case NULL -> new NullSchema();
29+
case OBJECT, ARRAY ->
30+
throw new UnsupportedOperationException("object/array parsing comes in C2");
31+
};
32+
}
33+
34+
private static Set<TypeName> parseTypes(Map<String, Object> raw) {
35+
Object t = raw.get("type");
36+
EnumSet<TypeName> out = EnumSet.noneOf(TypeName.class);
37+
if (t instanceof String s) {
38+
out.add(TypeName.fromJsonSchema(s));
39+
} else if (t instanceof List<?> list) {
40+
for (Object name : list) {
41+
out.add(TypeName.fromJsonSchema((String) name));
42+
}
43+
}
44+
if (Boolean.TRUE.equals(raw.get("nullable"))) {
45+
out.add(TypeName.NULL);
46+
}
47+
return out;
48+
}
49+
50+
@SuppressWarnings("unchecked")
51+
private static StringSchema parseString(Map<String, Object> raw, Set<TypeName> types) {
52+
return new StringSchema(
53+
types,
54+
(String) raw.get("pattern"),
55+
toIntOrNull(raw.get("minLength")),
56+
toIntOrNull(raw.get("maxLength")),
57+
(String) raw.get("format"),
58+
(List<String>) raw.get("enum"));
59+
}
60+
61+
private static IntegerSchema parseInteger(Map<String, Object> raw, Set<TypeName> types) {
62+
return new IntegerSchema(
63+
types,
64+
toLongOrNull(raw.get("minimum")),
65+
toLongOrNull(raw.get("maximum")),
66+
toLongOrNull(raw.get("exclusiveMinimum")),
67+
toLongOrNull(raw.get("exclusiveMaximum")),
68+
toLongOrNull(raw.get("multipleOf")),
69+
(String) raw.get("format"));
70+
}
71+
72+
private static NumberSchema parseNumber(Map<String, Object> raw, Set<TypeName> types) {
73+
return new NumberSchema(
74+
types,
75+
(Number) raw.get("minimum"),
76+
(Number) raw.get("maximum"),
77+
(Number) raw.get("exclusiveMinimum"),
78+
(Number) raw.get("exclusiveMaximum"),
79+
(Number) raw.get("multipleOf"),
80+
(String) raw.get("format"));
81+
}
82+
83+
private static Integer toIntOrNull(Object v) {
84+
return v == null ? null : ((Number) v).intValue();
85+
}
86+
87+
private static Long toLongOrNull(Object v) {
88+
return v == null ? null : ((Number) v).longValue();
89+
}
90+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.retailsvc.http.spec.schema;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
import org.junit.jupiter.api.Test;
8+
9+
class SchemaParserTest {
10+
@Test
11+
void parsesString() {
12+
Schema s = SchemaParser.parse(Map.of("type", "string", "minLength", 1, "maxLength", 64));
13+
assertThat(s).isInstanceOf(StringSchema.class);
14+
StringSchema str = (StringSchema) s;
15+
assertThat(str.minLength()).isEqualTo(1);
16+
assertThat(str.maxLength()).isEqualTo(64);
17+
}
18+
19+
@Test
20+
void parsesIntegerWithFormat() {
21+
Schema s = SchemaParser.parse(Map.of("type", "integer", "format", "int64", "minimum", 0));
22+
assertThat(s).isInstanceOf(IntegerSchema.class);
23+
assertThat(((IntegerSchema) s).format()).isEqualTo("int64");
24+
assertThat(((IntegerSchema) s).minimum()).isEqualTo(0L);
25+
}
26+
27+
@Test
28+
void parsesNumber() {
29+
Schema s = SchemaParser.parse(Map.of("type", "number", "multipleOf", 0.5));
30+
assertThat(s).isInstanceOf(NumberSchema.class);
31+
assertThat(((NumberSchema) s).multipleOf()).isEqualTo(0.5);
32+
}
33+
34+
@Test
35+
void parsesBoolean() {
36+
assertThat(SchemaParser.parse(Map.of("type", "boolean"))).isInstanceOf(BooleanSchema.class);
37+
}
38+
39+
@Test
40+
void parsesNull() {
41+
assertThat(SchemaParser.parse(Map.of("type", "null"))).isInstanceOf(NullSchema.class);
42+
}
43+
44+
@Test
45+
void parsesRef() {
46+
Schema s = SchemaParser.parse(Map.of("$ref", "#/components/schemas/User"));
47+
assertThat(s).isInstanceOf(RefSchema.class);
48+
assertThat(((RefSchema) s).pointer()).isEqualTo("#/components/schemas/User");
49+
}
50+
51+
@Test
52+
void parsesTypeArrayWithNullForNullable() {
53+
Schema s = SchemaParser.parse(Map.of("type", List.of("string", "null")));
54+
assertThat(s).isInstanceOf(StringSchema.class);
55+
assertThat(s.types()).containsExactlyInAnyOrder(TypeName.STRING, TypeName.NULL);
56+
}
57+
58+
@Test
59+
void parsesLegacyNullableTrueAsTypeUnion() {
60+
Schema s = SchemaParser.parse(Map.of("type", "string", "nullable", true));
61+
assertThat(s.types()).containsExactlyInAnyOrder(TypeName.STRING, TypeName.NULL);
62+
}
63+
}

0 commit comments

Comments
 (0)