This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 3dc8ae26ea Fix bean copy() shallow-copying nested beans in OpenAPI-v3
DOM (TODO-266)
3dc8ae26ea is described below
commit 3dc8ae26ea53d0b21b3d13209aa8719fd991f572
Author: James Bognar <[email protected]>
AuthorDate: Fri Jul 24 12:35:14 2026 -0400
Fix bean copy() shallow-copying nested beans in OpenAPI-v3 DOM (TODO-266)
copy() on Components/Operation/PathItem/Callback/Parameter re-wrapped
Map/List containers via the shallow single-arg overload while sharing the
nested bean instances, aliasing them between the original and the copy.
Switched the 15 affected sites to the deep-copy mapper overload
(Type::copy). Added 5 regression tests asserting nested container
elements are distinct after copy() and mutations don't cross-leak.
Co-authored-by: Cursor <[email protected]>
---
.../org/apache/juneau/bean/openapi3/Callback.java | 2 +-
.../apache/juneau/bean/openapi3/Components.java | 18 +++++++-------
.../org/apache/juneau/bean/openapi3/Operation.java | 4 ++--
.../org/apache/juneau/bean/openapi3/Parameter.java | 2 +-
.../org/apache/juneau/bean/openapi3/PathItem.java | 4 ++--
.../apache/juneau/bean/openapi3/Callback_Test.java | 12 ++++++++++
.../juneau/bean/openapi3/Components_Test.java | 28 ++++++++++++++++++++++
.../juneau/bean/openapi3/Operation_Test.java | 14 +++++++++++
.../juneau/bean/openapi3/Parameter_Test.java | 12 ++++++++++
.../apache/juneau/bean/openapi3/PathItem_Test.java | 14 +++++++++++
10 files changed, 95 insertions(+), 15 deletions(-)
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Callback.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Callback.java
index 3793eb8835..d472da27a0 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Callback.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Callback.java
@@ -96,7 +96,7 @@ public class Callback extends OpenApiElement {
*/
public Callback(Callback copyFrom) {
super(copyFrom);
- this.callbacks = cp(copyFrom.callbacks);
+ this.callbacks = cp(copyFrom.callbacks, PathItem::copy);
}
/**
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Components.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Components.java
index 0030547a36..f95e5185c7 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Components.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Components.java
@@ -122,15 +122,15 @@ public class Components extends OpenApiElement {
*/
public Components(Components copyFrom) {
super(copyFrom);
- this.schemas = cp(copyFrom.schemas);
- this.responses = cp(copyFrom.responses);
- this.parameters = cp(copyFrom.parameters);
- this.examples = cp(copyFrom.examples);
- this.requestBodies = cp(copyFrom.requestBodies);
- this.headers = cp(copyFrom.headers);
- this.securitySchemes = cp(copyFrom.securitySchemes);
- this.links = cp(copyFrom.links);
- this.callbacks = cp(copyFrom.callbacks);
+ this.schemas = cp(copyFrom.schemas, SchemaInfo::copy);
+ this.responses = cp(copyFrom.responses, Response::copy);
+ this.parameters = cp(copyFrom.parameters, Parameter::copy);
+ this.examples = cp(copyFrom.examples, Example::copy);
+ this.requestBodies = cp(copyFrom.requestBodies,
RequestBodyInfo::copy);
+ this.headers = cp(copyFrom.headers, HeaderInfo::copy);
+ this.securitySchemes = cp(copyFrom.securitySchemes,
SecuritySchemeInfo::copy);
+ this.links = cp(copyFrom.links, Link::copy);
+ this.callbacks = cp(copyFrom.callbacks, Callback::copy);
}
/**
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Operation.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Operation.java
index 3b8a90c7c0..847381ee9a 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Operation.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Operation.java
@@ -150,8 +150,8 @@ public class Operation extends OpenApiElement {
this.externalDocs = copyOf(copyFrom.externalDocs);
parameters.addAll(copyOf(copyFrom.parameters, Parameter::copy));
this.requestBody = copyOf(copyFrom.requestBody);
- responses.putAll(copyFrom.responses);
- callbacks.putAll(copyFrom.callbacks);
+ responses.putAll(copyOf(copyFrom.responses, Response::copy));
+ callbacks.putAll(copyOf(copyFrom.callbacks, Callback::copy));
this.deprecated = copyFrom.deprecated;
security.addAll(copyOf(copyFrom.security,
SecurityRequirement::copy));
servers.addAll(copyOf(copyFrom.servers, Server::copy));
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
index 8af46b9c36..1831707ba5 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/Parameter.java
@@ -139,7 +139,7 @@ public class Parameter extends OpenApiElement {
this.allowReserved = copyFrom.allowReserved;
this.schema = copyOf(copyFrom.schema);
this.example = copyFrom.example;
- this.examples = copyOf(copyFrom.examples);
+ this.examples = copyOf(copyFrom.examples, Example::copy);
}
/**
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
index 8c3d206a2a..444d074261 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/main/java/org/apache/juneau/bean/openapi3/PathItem.java
@@ -137,8 +137,8 @@ public class PathItem extends OpenApiElement {
this.head = copyOf(copyFrom.head);
this.patch = copyOf(copyFrom.patch);
this.trace = copyOf(copyFrom.trace);
- this.servers = copyOf(copyFrom.servers);
- this.parameters = copyOf(copyFrom.parameters);
+ this.servers = copyOf(copyFrom.servers, Server::copy);
+ this.parameters = copyOf(copyFrom.parameters, Parameter::copy);
}
/**
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Callback_Test.java
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Callback_Test.java
index 3b1c05dfd3..0a1ceedebe 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Callback_Test.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Callback_Test.java
@@ -126,6 +126,18 @@ class Callback_Test extends TestBase {
"{{{a2}},{{b2}}}"
);
}
+
+ @Test void a14_copyDeepCopiesMapValues() {
+ var orig = bean()
+ .addCallback("a1",
pathItem().setGet(operation().setSummary("orig")));
+
+ var copy = orig.copy();
+
+ assertNotSame(orig.getCallbacks().get("a1"),
copy.getCallbacks().get("a1"));
+
+
copy.getCallbacks().get("a1").setGet(operation().setSummary("mutated"));
+ assertEquals("orig",
orig.getCallbacks().get("a1").getGet().getSummary());
+ }
}
@Nested class B_emptyTests extends TestBase {
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Components_Test.java
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Components_Test.java
index 9cbc714c3f..7cfe732b5d 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Components_Test.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Components_Test.java
@@ -112,6 +112,34 @@ class Components_Test extends TestBase {
assertDoesNotThrow(() -> bean().set("foo", "bar"));
}
+
+ @Test void a12_copyDeepCopiesMapValues() {
+ var orig = bean()
+ .setSchemas(map("a1",
schemaInfo().setType("orig")))
+ .setResponses(map("b1", response("orig")))
+ .setParameters(map("c1", parameter("orig",
"orig")))
+ .setExamples(map("d1",
example().setSummary("orig")))
+ .setRequestBodies(map("e1",
requestBodyInfo().setDescription("orig")))
+ .setHeaders(map("f1",
headerInfo(schemaInfo("orig"))))
+ .setSecuritySchemes(map("g1",
securitySchemeInfo("orig")))
+ .setLinks(map("h1",
link().setOperationId("orig")))
+ .setCallbacks(map("i1", callback()));
+
+ var copy = orig.copy();
+
+ assertNotSame(orig.getSchemas().get("a1"),
copy.getSchemas().get("a1"));
+ assertNotSame(orig.getResponses().get("b1"),
copy.getResponses().get("b1"));
+ assertNotSame(orig.getParameters().get("c1"),
copy.getParameters().get("c1"));
+ assertNotSame(orig.getExamples().get("d1"),
copy.getExamples().get("d1"));
+ assertNotSame(orig.getRequestBodies().get("e1"),
copy.getRequestBodies().get("e1"));
+ assertNotSame(orig.getHeaders().get("f1"),
copy.getHeaders().get("f1"));
+ assertNotSame(orig.getSecuritySchemes().get("g1"),
copy.getSecuritySchemes().get("g1"));
+ assertNotSame(orig.getLinks().get("h1"),
copy.getLinks().get("h1"));
+ assertNotSame(orig.getCallbacks().get("i1"),
copy.getCallbacks().get("i1"));
+
+ copy.getSchemas().get("a1").setType("mutated");
+ assertEquals("orig",
orig.getSchemas().get("a1").getType());
+ }
}
@Nested class B_emptyTests extends TestBase {
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Operation_Test.java
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Operation_Test.java
index 1907650a99..2fa1aee390 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Operation_Test.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Operation_Test.java
@@ -269,6 +269,20 @@ class Operation_Test extends TestBase {
x.setTags(nullTagList);
});
}
+
+ @Test void a18_copyDeepCopiesMapValues() {
+ var orig = bean()
+ .setResponses(map("200",
response().setDescription("orig")))
+ .addCallback("cb1", callback());
+
+ var copy = orig.copy();
+
+ assertNotSame(orig.getResponses().get("200"),
copy.getResponses().get("200"));
+ assertNotSame(orig.getCallbacks().get("cb1"),
copy.getCallbacks().get("cb1"));
+
+
copy.getResponses().get("200").setDescription("mutated");
+ assertEquals("orig",
orig.getResponses().get("200").getDescription());
+ }
}
@Nested class B_emptyTests extends TestBase {
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
index c66398607c..81d95261ed 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/Parameter_Test.java
@@ -136,6 +136,18 @@ class Parameter_Test extends TestBase {
assertDoesNotThrow(() -> x.setStyle("deepObject"));
assertDoesNotThrow(() -> y.setStyle("invalid"));
}
+
+ @Test void a12_copyDeepCopiesMapValues() {
+ var orig = bean()
+ .setExamples(map("a1",
example().setSummary("orig")));
+
+ var copy = orig.copy();
+
+ assertNotSame(orig.getExamples().get("a1"),
copy.getExamples().get("a1"));
+
+ copy.getExamples().get("a1").setSummary("mutated");
+ assertEquals("orig",
orig.getExamples().get("a1").getSummary());
+ }
}
@Nested class B_emptyTests extends TestBase {
diff --git
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/PathItem_Test.java
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/PathItem_Test.java
index 7e03aa56d0..949aeab9c6 100644
---
a/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/PathItem_Test.java
+++
b/juneau-bean/juneau-bean-openapi-v3/src/test/java/org/apache/juneau/bean/openapi3/PathItem_Test.java
@@ -111,6 +111,20 @@ class PathItem_Test extends TestBase {
assertTrue(bean().strict().isStrict());
assertFalse(bean().strict(false).isStrict());
}
+
+ @Test void a12_copyDeepCopiesListValues() {
+ var orig = bean()
+
.setServers(l(server().setUrl(URI.create("http://example.com"))))
+
.setParameters(l(parameter().setIn("q").setName("orig")));
+
+ var copy = orig.copy();
+
+ assertNotSame(orig.getServers().get(0),
copy.getServers().get(0));
+ assertNotSame(orig.getParameters().get(0),
copy.getParameters().get(0));
+
+ copy.getParameters().get(0).setName("mutated");
+ assertEquals("orig",
orig.getParameters().get(0).getName());
+ }
}
@Nested class B_emptyTests extends TestBase {