Skip to content

Commit 3cc212b

Browse files
committed
extract/improve overlay code (#120)
1 parent 399c113 commit 3cc212b

File tree

13 files changed

+452
-124
lines changed

13 files changed

+452
-124
lines changed

json-schema-validator/src/main/java/io/openapiprocessor/jsonschema/support/Types.java

+5
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public static boolean isString (@Nullable Object o) {
9797
return o instanceof String;
9898
}
9999

100+
@EnsuresNonNullIf(expression = "#1", result = true)
101+
public static boolean isNumber (@Nullable Object o) {
102+
return o instanceof Number;
103+
}
104+
100105
@EnsuresNonNullIf(expression = "#1", result = true)
101106
public static boolean isMap (@Nullable Object o) {
102107
return o instanceof Map;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright 2025 https://github.com/openapi-processor/openapi-parser
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiparser;
7+
8+
public class NotImplementedException extends RuntimeException {
9+
}

openapi-parser/src/main/java/io/openapiparser/OpenApiResult30.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ public void write(Writer writer) throws IOException {
6363
}
6464

6565
@Override
66-
public void apply(OverlayResult overlayResult) {
67-
// todo
66+
public Map<String, Object> apply(OverlayResult overlayResult) {
67+
OverlayApplier overlay = new OverlayApplier(root.getRawValues());
68+
return overlay.apply(overlayResult);
6869
}
6970

7071
@Override

openapi-parser/src/main/java/io/openapiparser/OpenApiResult31.java

+3-15
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
package io.openapiparser;
77

8-
import com.jayway.jsonpath.JsonPath;
9-
import io.openapiparser.model.ov10.Overlay;
108
import io.openapiparser.model.v31.OpenApi;
119
import io.openapiprocessor.interfaces.Writer;
1210
import io.openapiprocessor.jsonschema.ouput.OutputConverter;
@@ -69,19 +67,9 @@ public void write(Writer writer) throws IOException {
6967
}
7068

7169
@Override
72-
public void apply(OverlayResult overlayResult) {
73-
Overlay overlay = overlayResult.getModel(Overlay.class);
74-
75-
overlay.getActions().forEach(action -> {
76-
String target = action.getTarget();
77-
78-
if (Boolean.TRUE.equals(action.getRemove())) {
79-
JsonPath.parse(root.getRawValues()).delete(target);
80-
81-
} else {
82-
83-
}
84-
});
70+
public Map<String, Object> apply(OverlayResult overlayResult) {
71+
OverlayApplier overlay = new OverlayApplier(root.getRawValues());
72+
return overlay.apply(overlayResult);
8573
}
8674

8775
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* Copyright 2025 https://github.com/openapi-processor/openapi-parser
3+
* PDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.openapiparser;
7+
8+
import com.jayway.jsonpath.*;
9+
import com.jayway.jsonpath.spi.json.AbstractJsonProvider;
10+
import com.jayway.jsonpath.spi.mapper.MappingProvider;
11+
import io.openapiparser.model.ov10.Action;
12+
import io.openapiparser.model.ov10.Overlay;
13+
import io.openapiparser.support.Experimental;
14+
import io.openapiprocessor.jsonschema.schema.NotImplementedException;
15+
import io.openapiprocessor.jsonschema.support.Types;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
19+
import java.io.InputStream;
20+
import java.util.*;
21+
22+
@Experimental
23+
public class OverlayApplier {
24+
private static final Logger log = LoggerFactory.getLogger (OverlayApplier.class);
25+
26+
private final Map<String, Object> document;
27+
28+
public OverlayApplier(Map<String, Object> document) {
29+
this.document = deepCopy(document);
30+
}
31+
32+
public Map<String, Object> apply(OverlayResult overlayResult) {
33+
OverlayResult.Version version = overlayResult.getVersion();
34+
35+
if (version == OverlayResult.Version.V10) {
36+
return apply10(overlayResult);
37+
38+
} else {
39+
throw new UnknownVersionException(version.toString());
40+
}
41+
}
42+
43+
private Map<String, Object> apply10(OverlayResult overlayResult) {
44+
Overlay overlay = overlayResult.getModel(Overlay.class);
45+
DocumentContext context = createContext();
46+
47+
overlay.getActions().forEach(action -> {
48+
String location = action.getTarget();
49+
50+
if (Boolean.TRUE.equals(action.getRemove())) {
51+
context.delete(location);
52+
53+
} else {
54+
Collection<Object> targets = context.read(location);
55+
if (targets == null || targets.isEmpty()) {
56+
log.warn("target json path {} result is empty!", location);
57+
return;
58+
}
59+
60+
targets.forEach(target -> {
61+
if(Types.isObject(target) && Types.isObject(action.getUpdate())) {
62+
Map<String, Object> targetObject = Types.asObject(target);
63+
mergeObject(targetObject, action);
64+
65+
} else if (Types.isArray(target)) {
66+
Collection<Object> targetArray = Types.asArray(target);
67+
targetArray.add(action.getUpdate());
68+
69+
} else {
70+
log.warn("target json path {} is not an object or array!", location);
71+
}
72+
});
73+
}
74+
});
75+
76+
return document;
77+
}
78+
79+
private DocumentContext createContext() {
80+
Configuration conf = Configuration.builder()
81+
.options(Option.ALWAYS_RETURN_LIST)
82+
.jsonProvider(new OverlayJsonProvider())
83+
.mappingProvider(new OverlayMappingProvider())
84+
.build();
85+
86+
return JsonPath.using(conf).parse(document);
87+
}
88+
89+
private void mergeObject(Map<String, Object> targetRoot, Action action) {
90+
Map<String, Object> actionRoot = Types.asObject(action.getUpdate());
91+
if (actionRoot == null) {
92+
log.warn("target json path {} update is empty!", action.getTarget());
93+
return;
94+
}
95+
96+
actionRoot.forEach((key, value) -> {
97+
Object target = targetRoot.get(key);
98+
99+
if (Types.isObject(target) && Types.isObject(value)) {
100+
mergeObject(Types.asObject(target), Types.asObject(value));
101+
102+
} else if (Types.isArray(target)) {
103+
Collection<Object> targetArray = Types.asArray(value);
104+
targetArray.add(deepCopy(value));
105+
} else {
106+
Object oldValue = targetRoot.get(key);
107+
if (oldValue != null && oldValue.getClass() != value.getClass()) {
108+
log.warn("target json path {} does not have same type ({} vs {}). ",
109+
action.getTarget(),
110+
oldValue.getClass().getName(),
111+
value.getClass().getName());
112+
return;
113+
}
114+
115+
targetRoot.put(key, deepCopy(value));
116+
}
117+
});
118+
}
119+
120+
private void mergeObject(Map<String, Object> targetRoot, Map<String, Object> actionRoot) {
121+
actionRoot.forEach((key, value) -> {
122+
Object target = targetRoot.get(key);
123+
124+
if (Types.isObject(target) && Types.isObject(value)) {
125+
mergeObject(Types.asObject(target), Types.asObject(value));
126+
127+
} else if (Types.isArray(target)) {
128+
Collection<Object> targetArray = Types.asArray(value);
129+
targetArray.add(deepCopy(value));
130+
} else {
131+
Object oldValue = targetRoot.get(key);
132+
if (oldValue != null && oldValue.getClass() != value.getClass()) {
133+
log.warn("ignoring action property {}. It has not the same ", key);
134+
}
135+
136+
targetRoot.put(key, deepCopy(value));
137+
}
138+
});
139+
}
140+
141+
private Map<String, Object> deepCopy(Map<String, Object> object) {
142+
return (Map<String, Object>) deepCopy((Object)object);
143+
}
144+
145+
private Object deepCopy (Object object) {
146+
if (Types.isObject(object)) {
147+
return deepCopyMap(Types.asObject(object));
148+
149+
} else if (Types.isArray(object)) {
150+
return deepCopyArray(Types.asArray(object));
151+
152+
} else {
153+
return object;
154+
}
155+
}
156+
157+
private Map<String, Object> deepCopyMap(Map<String, Object> source) {
158+
Map<String, Object> copy = new LinkedHashMap<>(source.size());
159+
source.forEach((key, value) -> {
160+
copy.put(key, deepCopy(value));
161+
});
162+
return copy;
163+
}
164+
165+
private Collection<Object> deepCopyArray(Collection<Object> source) {
166+
Collection<Object> copy = new ArrayList<>(source.size());
167+
source.forEach(item -> {
168+
copy.add(deepCopy(item));
169+
});
170+
return copy;
171+
}
172+
173+
private static class OverlayJsonProvider extends AbstractJsonProvider {
174+
175+
@Override
176+
public Object parse(String json) throws InvalidJsonException {
177+
throw new NotImplementedException();
178+
}
179+
180+
@Override
181+
public Object parse(InputStream jsonStream, String charset) throws InvalidJsonException {
182+
throw new NotImplementedException();
183+
}
184+
185+
@Override
186+
public String toJson(Object obj) {
187+
throw new NotImplementedException();
188+
}
189+
190+
@Override
191+
public List<Object> createArray() {
192+
return new LinkedList<>();
193+
}
194+
195+
@Override
196+
public Object createMap() {
197+
return new LinkedHashMap<String, Object>();
198+
}
199+
}
200+
201+
private static class OverlayMappingProvider implements MappingProvider {
202+
203+
@Override
204+
public <T> T map(Object source, Class<T> targetType, Configuration configuration) {
205+
throw new NotImplementedException();
206+
}
207+
208+
@Override
209+
public <T> T map(Object source, TypeRef<T> targetType, Configuration configuration) {
210+
throw new NotImplementedException();
211+
}
212+
}
213+
}

openapi-parser/src/main/java/io/openapiparser/OverlayParser.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ private OverlayResult parseVersion(URI baseUri, Object document) {
5353
Scope scope = Scope.empty();
5454
return new OverlayResult10(
5555
new Context(scope, new ReferenceRegistry()),
56-
Bucket.createBucket(scope, document),
57-
documents);
56+
Bucket.createBucket(scope, document));
5857
} else {
5958
throw new UnknownVersionException(version);
6059
}

openapi-parser/src/main/java/io/openapiparser/OverlayResult10.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,11 @@ public class OverlayResult10 implements OverlayResult {
2323
private final Context context;
2424
private final Bucket root;
2525

26-
private final DocumentStore documents;
27-
2826
private Collection<ValidationError> validationErrors = List.of();
2927

30-
public OverlayResult10 (Context context, Bucket root, DocumentStore documents) {
28+
public OverlayResult10 (Context context, Bucket root) {
3129
this.context = context;
3230
this.root = root;
33-
this.documents = documents;
3431
}
3532

3633
@Override

0 commit comments

Comments
 (0)