1
2
3
4
5
6 package dev.metaschema.schemagen.json.impl;
7
8 import com.fasterxml.jackson.databind.node.ArrayNode;
9 import com.fasterxml.jackson.databind.node.ObjectNode;
10
11 import java.util.List;
12 import java.util.Set;
13 import java.util.stream.Collectors;
14 import java.util.stream.Stream;
15
16 import dev.metaschema.core.model.IModule;
17 import dev.metaschema.core.util.CollectionUtil;
18 import dev.metaschema.core.util.ObjectUtils;
19 import dev.metaschema.schemagen.SchemaGenerationException;
20 import edu.umd.cs.findbugs.annotations.NonNull;
21
22
23
24
25 public class JsonSchemaModule
26 implements IJsonSchema {
27 private final IModule module;
28 private final List<IJsonSchemaDefinitionAssembly> roots;
29
30
31
32
33
34
35
36
37
38 public JsonSchemaModule(
39 @NonNull IModule module,
40 @NonNull IJsonGenerationState state) {
41 this.module = module;
42 this.roots = module.getExportedRootAssemblyDefinitions().stream()
43 .map(root -> state.getAssemblyDefinition(ObjectUtils.notNull(root), null))
44 .collect(Collectors.toUnmodifiableList());
45 }
46
47 @Override
48 public boolean isInline(IJsonGenerationState state) {
49
50 return true;
51 }
52
53
54
55
56
57
58
59
60
61 @NonNull
62 public Stream<IJsonSchemaDefinable> collectDefinitions(@NonNull IJsonGenerationState state) {
63 return ObjectUtils.notNull(roots.stream()
64 .flatMap(root -> root.collectDefinitions(CollectionUtil.emptySet(), state)));
65 }
66
67 @Override
68 public void generateInlineJsonSchema(ObjectNode node, IJsonGenerationState state) {
69 node.put("$schema", "http://json-schema.org/draft-07/schema#");
70 node.put("$id",
71 String.format("%s/%s-%s-schema.json",
72 module.getXmlNamespace(),
73 module.getShortName(),
74 module.getVersion()));
75 node.put("$comment", module.getName().toMarkdown());
76 node.put("type", "object");
77
78 if (roots.isEmpty()) {
79 throw new SchemaGenerationException("No root definitions found");
80 }
81
82 node.set("definitions", generateDefinitions(state));
83
84 if (roots.size() == 1) {
85 generateRoot(node, ObjectUtils.notNull(roots.iterator().next()), state);
86 } else {
87 ArrayNode oneOfNode = node.putArray("oneOf");
88 roots.forEach(root -> {
89 assert root != null;
90 ObjectNode rootNode = ObjectUtils.notNull(oneOfNode.addObject());
91 assert rootNode != null;
92
93 generateRoot(rootNode, root, state);
94 });
95 }
96 }
97
98
99
100
101
102
103
104
105 private ObjectNode generateDefinitions(@NonNull IJsonGenerationState state) {
106
107
108 Set<IJsonSchemaDefinable> usedDefinitions = ObjectUtils.notNull(collectDefinitions(state)
109 .collect(Collectors.toUnmodifiableSet()));
110
111 ObjectNode definitionsNode = ObjectUtils.notNull(state.getJsonNodeFactory().objectNode());
112
113 usedDefinitions.stream()
114 .filter(definition -> !definition.isInline(state))
115 .distinct()
116 .sorted(JsonSchemaHelper.DEFINABLE_NAME_COMPARATOR)
117 .forEach(definition -> {
118 ObjectNode definitionNode = definitionsNode.putObject(definition.getDefinitionName());
119 assert definitionNode != null;
120 definition.generateDefinitionJsonSchema(definitionNode, state);
121 });
122
123 state.generateDataTypeDefinitions(definitionsNode);
124
125 return definitionsNode;
126 }
127
128 private static void generateRoot(
129 @NonNull ObjectNode node,
130 @NonNull IJsonSchemaDefinitionAssembly schema,
131 @NonNull IJsonGenerationState state) {
132 ObjectNode propertiesObj = node.putObject("properties");
133
134 propertiesObj.putObject("$schema")
135 .put("type", "string")
136 .put("format", "uri-reference");
137
138 String name = schema.getDefinition().getRootJsonName();
139
140 schema.generateJsonSchemaOrDefinitionRef(ObjectUtils.notNull(propertiesObj.putObject(name)), state);
141
142 node.putArray("required")
143 .add(name);
144 node.put("additionalProperties", false);
145 }
146 }