-
Notifications
You must be signed in to change notification settings - Fork 748
Fix export of list attributes with custom Stapler converters #2789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 5 commits
04d7098
fbd5282
d170186
1125720
164cfd7
45cc362
1be85e6
2b860c0
c2ec615
073d56f
7ac085a
d2a0cdf
7873454
ddfe528
f323bdb
5eab1ff
8c91784
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,12 +30,17 @@ | |
| import io.jenkins.plugins.casc.model.Sequence; | ||
| import java.io.PrintWriter; | ||
| import java.io.StringWriter; | ||
| import java.util.Arrays; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.Set; | ||
| import java.util.logging.Level; | ||
| import java.util.logging.Logger; | ||
| import javax.annotation.ParametersAreNonnullByDefault; | ||
| import javax.annotation.PostConstruct; | ||
| import org.apache.commons.beanutils.ConversionException; | ||
| import org.apache.commons.beanutils.Converter; | ||
| import org.junit.jupiter.api.AfterAll; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
@@ -426,4 +431,225 @@ public ArrayConstructor(Foo[] anArray) { | |
| this.anArray = anArray; | ||
| } | ||
| } | ||
|
|
||
| @SuppressWarnings("ClassCanBeRecord") | ||
| public static class CustomItem { | ||
| private final String value; | ||
|
|
||
| @DataBoundConstructor | ||
| public CustomItem(String value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| public String getValue() { | ||
| return value; | ||
| } | ||
|
|
||
| @SuppressWarnings("unused") | ||
| public static class StaplerConverterImpl implements Converter { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This converter doesn't seem to be doing anything, I deleted it and the tests still passed. I would expect this to actually do something and have the tests rely on the behaviour.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That StaplerConverterImpl was a leftover from my earlier attempts to increase coverage. I have completely removed the unused converter in the latest commit.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't the whole point of this to have a customer converter though and demonstrate that it gets called for each item in the list rather than the list gets passed to it? |
||
| @SuppressWarnings("unchecked") | ||
| @Override | ||
| public <T> T convert(Class<T> type, Object value) { | ||
| if (type == String.class && value instanceof CustomItem) { | ||
| return (T) ("converted-" + ((CustomItem) value).getValue()); | ||
| } | ||
| if (type == CustomItem.class && value instanceof String) { | ||
| return (T) new CustomItem(((String) value).replace("converted-", "")); | ||
| } | ||
| if (type.isInstance(value)) { | ||
| return (T) value; | ||
| } | ||
| throw new ConversionException( | ||
| "Unsupported conversion from " + value.getClass().getName() + " to " + type.getName()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @SuppressWarnings("ClassCanBeRecord") | ||
| public static class CustomItemListHolder { | ||
| private final List<CustomItem> items; | ||
|
|
||
| @DataBoundConstructor | ||
| public CustomItemListHolder(List<CustomItem> items) { | ||
| this.items = items; | ||
| } | ||
|
|
||
| @SuppressWarnings("unused") | ||
| public List<CustomItem> getItems() { | ||
| return items; | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| @Issue("https://github.com/jenkinsci/configuration-as-code-plugin/issues/2346") | ||
| void exportWithCustomConverterIteratesOverList() throws Exception { | ||
| List<CustomItem> list = Arrays.asList(new CustomItem("A"), new CustomItem("B")); | ||
| CustomItemListHolder holder = new CustomItemListHolder(list); | ||
|
|
||
| ConfiguratorRegistry registry = ConfiguratorRegistry.get(); | ||
| final Configurator<Object> c = registry.lookupOrFail(CustomItemListHolder.class); | ||
| final ConfigurationContext context = new ConfigurationContext(registry); | ||
|
|
||
| CNode node = c.describe(holder, context); | ||
|
|
||
| assertNotNull(node); | ||
| assertInstanceOf(Mapping.class, node); | ||
| Mapping map = (Mapping) node; | ||
|
|
||
| assertEquals( | ||
| "- value: \"A\"\n- value: \"B\"", | ||
| Util.toYamlString(map.get("items")).trim()); | ||
| } | ||
|
|
||
| @SuppressWarnings("ClassCanBeRecord") | ||
| public static class CustomItemSetHolder { | ||
| private final Set<CustomItem> items; | ||
|
|
||
| @DataBoundConstructor | ||
| public CustomItemSetHolder(Set<CustomItem> items) { | ||
| this.items = items; | ||
| } | ||
|
|
||
| @SuppressWarnings("unused") | ||
| public Set<CustomItem> getItems() { | ||
| return items; | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void exportWithCustomConverterIteratesOverSet() throws Exception { | ||
| Set<CustomItem> set = new HashSet<>(); | ||
| set.add(new CustomItem("A")); | ||
| set.add(new CustomItem("B")); | ||
|
|
||
| CustomItemSetHolder holder = new CustomItemSetHolder(set); | ||
|
|
||
| ConfiguratorRegistry registry = ConfiguratorRegistry.get(); | ||
| Configurator<Object> c = registry.lookupOrFail(CustomItemSetHolder.class); | ||
|
|
||
| CNode node = c.describe(holder, new ConfigurationContext(registry)); | ||
| Mapping map = (Mapping) node; | ||
|
|
||
| String yaml = | ||
| Util.toYamlString(Objects.requireNonNull(map).get("items")).trim(); | ||
|
|
||
| assertTrue(yaml.contains("value: \"A\"")); | ||
| assertTrue(yaml.contains("value: \"B\"")); | ||
| } | ||
|
|
||
| @SuppressWarnings("ClassCanBeRecord") | ||
| public static class CustomItemArrayHolder { | ||
| private final CustomItem[] items; | ||
|
|
||
| @DataBoundConstructor | ||
| public CustomItemArrayHolder(CustomItem[] items) { | ||
| this.items = items; | ||
| } | ||
|
|
||
| @SuppressWarnings("unused") | ||
| public CustomItem[] getItems() { | ||
| return items; | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void exportWithCustomConverterIteratesOverArray() throws Exception { | ||
| CustomItem[] array = {new CustomItem("A"), new CustomItem("B")}; | ||
|
|
||
| CustomItemArrayHolder holder = new CustomItemArrayHolder(array); | ||
|
|
||
| ConfiguratorRegistry registry = ConfiguratorRegistry.get(); | ||
| Configurator<Object> c = registry.lookupOrFail(CustomItemArrayHolder.class); | ||
|
|
||
| CNode node = c.describe(holder, new ConfigurationContext(registry)); | ||
| Mapping map = (Mapping) node; | ||
|
|
||
| assertEquals( | ||
| "- value: \"A\"\n- value: \"B\"", | ||
| Util.toYamlString(Objects.requireNonNull(map).get("items")).trim()); | ||
| } | ||
|
|
||
| @Test | ||
| void exportWithSingleElementList() throws Exception { | ||
| CustomItemListHolder holder = new CustomItemListHolder(List.of(new CustomItem("A"))); | ||
|
|
||
| ConfiguratorRegistry registry = ConfiguratorRegistry.get(); | ||
| Configurator<Object> c = registry.lookupOrFail(CustomItemListHolder.class); | ||
|
|
||
| CNode node = c.describe(holder, new ConfigurationContext(registry)); | ||
| Mapping map = (Mapping) node; | ||
|
|
||
| assertEquals( | ||
| "- value: \"A\"", | ||
| Util.toYamlString(Objects.requireNonNull(map).get("items")).trim()); | ||
| } | ||
|
|
||
| @Test | ||
| void exportWithCustomConverterIteratesOverListWithNull() throws Exception { | ||
| List<CustomItem> list = Arrays.asList(new CustomItem("A"), null); | ||
| CustomItemListHolder holder = new CustomItemListHolder(list); | ||
|
|
||
| ConfiguratorRegistry registry = ConfiguratorRegistry.get(); | ||
| Configurator<Object> c = registry.lookupOrFail(CustomItemListHolder.class); | ||
|
|
||
| CNode node = c.describe(holder, new ConfigurationContext(registry)); | ||
|
|
||
| assertNotNull(node, "Node should not be null"); | ||
| assertInstanceOf(Mapping.class, node, "Node should be exported as a Mapping"); | ||
|
|
||
| String yaml = Util.toYamlString(node); | ||
| assertTrue(yaml.contains("A"), "The valid item 'A' should be present in the exported YAML"); | ||
| } | ||
|
|
||
| @Test | ||
| void configureIteratesOverList() { | ||
| Mapping config = new Mapping(); | ||
| Sequence items = new Sequence(); | ||
|
|
||
| Mapping itemA = new Mapping(); | ||
| itemA.put("value", "A"); | ||
| items.add(itemA); | ||
|
|
||
| Mapping itemB = new Mapping(); | ||
| itemB.put("value", "B"); | ||
| items.add(itemB); | ||
|
|
||
| config.put("items", items); | ||
|
|
||
| ConfiguratorRegistry registry = ConfiguratorRegistry.get(); | ||
| CustomItemListHolder configured = (CustomItemListHolder) | ||
| registry.lookupOrFail(CustomItemListHolder.class).configure(config, new ConfigurationContext(registry)); | ||
|
|
||
| assertNotNull(configured); | ||
| assertEquals(2, configured.getItems().size()); | ||
| assertEquals("A", configured.getItems().get(0).getValue()); | ||
| assertEquals("B", configured.getItems().get(1).getValue()); | ||
| } | ||
|
|
||
| @Test | ||
| void configureConvertsListToSet() { | ||
| Mapping config = new Mapping(); | ||
| Sequence items = new Sequence(); | ||
|
|
||
| Mapping itemA = new Mapping(); | ||
| itemA.put("value", "A"); | ||
| items.add(itemA); | ||
|
|
||
| Mapping itemB = new Mapping(); | ||
| itemB.put("value", "B"); | ||
| items.add(itemB); | ||
|
|
||
| config.put("items", items); | ||
|
|
||
| ConfiguratorRegistry registry = ConfiguratorRegistry.get(); | ||
| CustomItemSetHolder configured = (CustomItemSetHolder) | ||
| registry.lookupOrFail(CustomItemSetHolder.class).configure(config, new ConfigurationContext(registry)); | ||
|
|
||
| assertNotNull(configured); | ||
| assertEquals(2, configured.getItems().size()); | ||
| assertNotNull(configured.getItems()); | ||
|
|
||
| assertTrue(configured.getItems().stream().anyMatch(i -> "A".equals(i.getValue()))); | ||
| assertTrue(configured.getItems().stream().anyMatch(i -> "B".equals(i.getValue()))); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.