diff --git a/pkl-config-java/src/main/java/org/pkl/config/java/Config.java b/pkl-config-java/src/main/java/org/pkl/config/java/Config.java index 090d52db..bd119e15 100644 --- a/pkl-config-java/src/main/java/org/pkl/config/java/Config.java +++ b/pkl-config-java/src/main/java/org/pkl/config/java/Config.java @@ -15,22 +15,19 @@ */ package org.pkl.config.java; -import static org.pkl.config.java.ConfigUtils.makeConfig; - import java.io.InputStream; import java.lang.reflect.Type; import org.jspecify.annotations.Nullable; import org.pkl.config.java.mapper.ConversionException; import org.pkl.config.java.mapper.ValueMapper; import org.pkl.core.Evaluator; -import org.pkl.core.PklBinaryDecoder; /** * A root, intermediate, or leaf node in a configuration tree. Child nodes can be obtained by name * using {@link #get(String)}. To consume the node's composite or scalar value, convert the value to * the desired Java type, using one of the provided {@link #as} methods. */ -@SuppressWarnings("unused") +@SuppressWarnings({"DeprecatedIsStillUsed"}) public interface Config { /** * The dot-separated name of this node. For example, the node reached using {@code @@ -76,39 +73,50 @@ public interface Config { T as(JavaType type); /** - * Decode a config from the supplied byte array. + * Decodes a config from the supplied byte array. * - * @return the encoded config + * @return the decoded config + * @deprecated Use {@code ConfigDecoderBuilder...build().decode(bytes)} instead. For a direct + * equivalent, use {@code ConfigDecoder.preconfigured().setValueMapper(mapper).decode(bytes)}. */ + @Deprecated(forRemoval = true) static Config fromPklBinary(byte[] bytes, ValueMapper mapper) { - return makeConfig(PklBinaryDecoder.decode(bytes), mapper); + return ConfigDecoder.preconfigured().setValueMapper(mapper).decode(bytes); } /** - * Decode a config from the supplied byte array using a preconfigured {@link ValueMapper}. + * Decodes a config from the supplied byte array using a preconfigured {@link ValueMapper}. * - * @return the encoded config + * @return the decoded config + * @deprecated Use {@code ConfigDecoder.preconfigured().decode(bytes)} instead. */ + @Deprecated(forRemoval = true) static Config fromPklBinary(byte[] bytes) { - return fromPklBinary(bytes, ValueMapper.preconfigured()); + return ConfigDecoder.preconfigured().decode(bytes); } /** - * Decode a config from the supplied {@link InputStream} using a preconfigured {@link + * Decodes a config from the supplied {@link InputStream} using a preconfigured {@link * ValueMapper}. * - * @return the encoded config + * @return the decoded config + * @deprecated Use {@code ConfigDecoderBuilder...build().decode(inputStream)} instead. For a + * direct equivalent, use {@code + * ConfigDecoder.preconfigured().setValueMapper(mapper).decode(inputStream)}. */ + @Deprecated(forRemoval = true) static Config fromPklBinary(InputStream inputStream, ValueMapper mapper) { - return makeConfig(PklBinaryDecoder.decode(inputStream), mapper); + return ConfigDecoder.preconfigured().setValueMapper(mapper).decode(inputStream); } /** - * Decode a config from the supplied {@link InputStream}. + * Decodes a config from the supplied {@link InputStream}. * - * @return the encoded config + * @return the decoded config + * @deprecated Use {@code ConfigDecoder.preconfigured().decode(inputStream)} instead. */ + @Deprecated(forRemoval = true) static Config fromPklBinary(InputStream inputStream) { - return fromPklBinary(inputStream, ValueMapper.preconfigured()); + return ConfigDecoder.preconfigured().decode(inputStream); } } diff --git a/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoder.java b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoder.java new file mode 100644 index 00000000..f6b0f4e9 --- /dev/null +++ b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoder.java @@ -0,0 +1,62 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.config.java; + +import java.io.InputStream; +import org.pkl.config.java.mapper.ValueMapper; + +/** Decodes Pkl binary data into {@link Config} objects. */ +public interface ConfigDecoder { + + /** + * Returns a preconfigured decoder that uses {@link ValueMapper#preconfigured()}. + * + *

For more control over configuration, use {@link ConfigDecoderBuilder}. + * + * @return a preconfigured decoder + */ + static ConfigDecoder preconfigured() { + return ConfigDecoderBuilder.preconfigured().build(); + } + + ValueMapper getValueMapper(); + + /** + * Returns a copy of this decoder with the supplied value mapper. + * + * @param mapper the value mapper to use + * @return a decoder with the supplied value mapper + */ + ConfigDecoder setValueMapper(ValueMapper mapper); + + /** + * Decodes configuration from the supplied byte array. + * + * @param bytes the data to decode + * @return the decoded configuration + */ + Config decode(byte[] bytes); + + /** + * Decodes configuration from the supplied input stream. + * + *

This method does not close the stream; the caller is responsible for closing it. + * + * @param inputStream the data to decode + * @return the decoded configuration + */ + Config decode(InputStream inputStream); +} diff --git a/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoderBuilder.java b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoderBuilder.java new file mode 100644 index 00000000..e137f706 --- /dev/null +++ b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoderBuilder.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.config.java; + +import org.pkl.config.java.mapper.ValueMapperBuilder; + +/** + * Builder for {@link ConfigDecoder} instances. + * + *

Use {@link #preconfigured()} to obtain a preconfigured builder, or {@link #unconfigured()} for + * full control over its configuration. + */ +public final class ConfigDecoderBuilder { + private ValueMapperBuilder mapperBuilder; + + private ConfigDecoderBuilder(ValueMapperBuilder mapperBuilder) { + this.mapperBuilder = mapperBuilder; + } + + /** + * Returns a preconfigured builder that uses {@link ValueMapperBuilder#preconfigured()}. + * + * @return a preconfigured builder + */ + public static ConfigDecoderBuilder preconfigured() { + return new ConfigDecoderBuilder(ValueMapperBuilder.preconfigured()); + } + + /** + * Returns an unconfigured builder that uses {@link ValueMapperBuilder#unconfigured()}. + * + * @return an unconfigured builder + */ + public static ConfigDecoderBuilder unconfigured() { + return new ConfigDecoderBuilder(ValueMapperBuilder.unconfigured()); + } + + /** + * Returns the value mapper builder used by this decoder builder. + * + * @return the value mapper builder + */ + public ValueMapperBuilder getValueMapperBuilder() { + return mapperBuilder; + } + + /** + * Sets the value mapper builder used by this decoder builder. + * + * @param mapperBuilder the value mapper builder to use + * @return this builder + */ + public ConfigDecoderBuilder setValueMapperBuilder(ValueMapperBuilder mapperBuilder) { + this.mapperBuilder = mapperBuilder; + return this; + } + + /** + * Builds a {@link ConfigDecoder} using the current configuration. + * + * @return the configured decoder + */ + public ConfigDecoder build() { + return new ConfigDecoderImpl(mapperBuilder.build()); + } +} diff --git a/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoderImpl.java b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoderImpl.java new file mode 100644 index 00000000..519cf87e --- /dev/null +++ b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigDecoderImpl.java @@ -0,0 +1,48 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.config.java; + +import java.io.InputStream; +import org.pkl.config.java.mapper.ValueMapper; +import org.pkl.core.PklBinaryDecoder; + +final class ConfigDecoderImpl implements ConfigDecoder { + private final ValueMapper mapper; + + ConfigDecoderImpl(ValueMapper mapper) { + this.mapper = mapper; + } + + @Override + public ValueMapper getValueMapper() { + return mapper; + } + + @Override + public ConfigDecoder setValueMapper(ValueMapper mapper) { + return new ConfigDecoderImpl(mapper); + } + + @Override + public Config decode(byte[] bytes) { + return ConfigUtils.createConfig(PklBinaryDecoder.decode(bytes), mapper); + } + + @Override + public Config decode(InputStream inputStream) { + return ConfigUtils.createConfig(PklBinaryDecoder.decode(inputStream), mapper); + } +} diff --git a/pkl-config-java/src/main/java/org/pkl/config/java/ConfigEvaluatorImpl.java b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigEvaluatorImpl.java index 41c40677..fe2a9a99 100644 --- a/pkl-config-java/src/main/java/org/pkl/config/java/ConfigEvaluatorImpl.java +++ b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigEvaluatorImpl.java @@ -15,7 +15,7 @@ */ package org.pkl.config.java; -import static org.pkl.config.java.ConfigUtils.makeConfig; +import static org.pkl.config.java.ConfigUtils.createConfig; import org.pkl.config.java.mapper.ValueMapper; import org.pkl.core.Evaluator; @@ -39,13 +39,13 @@ final class ConfigEvaluatorImpl implements ConfigEvaluator { @Override public Config evaluateOutputValue(ModuleSource moduleSource) { var value = evaluator.evaluateOutputValue(moduleSource); - return makeConfig(value, mapper); + return createConfig(value, mapper); } @Override public Config evaluateExpression(ModuleSource moduleSource, String expression) { var value = evaluator.evaluateExpression(moduleSource, expression); - return makeConfig(value, mapper); + return createConfig(value, mapper); } @Override diff --git a/pkl-config-java/src/main/java/org/pkl/config/java/ConfigUtils.java b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigUtils.java index 6c911018..09300cac 100644 --- a/pkl-config-java/src/main/java/org/pkl/config/java/ConfigUtils.java +++ b/pkl-config-java/src/main/java/org/pkl/config/java/ConfigUtils.java @@ -19,16 +19,16 @@ import java.util.Map; import org.pkl.config.java.mapper.ValueMapper; import org.pkl.core.Composite; -class ConfigUtils { +final class ConfigUtils { private ConfigUtils() {} - static Config makeConfig(Object decoded, ValueMapper mapper) { - if (decoded instanceof Composite composite) { + static Config createConfig(Object value, ValueMapper mapper) { + if (value instanceof Composite composite) { return new CompositeConfig("", mapper, composite); } - if (decoded instanceof Map map) { + if (value instanceof Map map) { return new MapConfig("", mapper, map); } - return new LeafConfig("", mapper, decoded); + return new LeafConfig("", mapper, value); } } diff --git a/pkl-config-java/src/test/java/org/pkl/config/java/AbstractConfigTest.java b/pkl-config-java/src/test/java/org/pkl/config/java/AbstractConfigTest.java index 43702fec..8a6191d7 100644 --- a/pkl-config-java/src/test/java/org/pkl/config/java/AbstractConfigTest.java +++ b/pkl-config-java/src/test/java/org/pkl/config/java/AbstractConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,22 +26,49 @@ import org.pkl.core.PObject; public abstract class AbstractConfigTest { - private final Config pigeonConfig = getPigeonConfig(); - private final Config pigeonModuleConfig = getPigeonModuleConfig(); - private final Config pairConfig = getPairConfig(); - private final Config mapConfig = getMapConfig(); + protected static final String pigeonText = + """ + pigeon { + age = 30 + friends = List("john", "mary") + address { + street = "Fuzzy St." + } + } + """; - protected abstract Config getPigeonConfig(); + protected abstract Config loadConfig(String text); - protected abstract Config getPigeonModuleConfig(); + private Config loadPigeonConfig() { + return loadConfig(pigeonText); + } - protected abstract Config getPairConfig(); + private Config loadPigeonModuleConfig() { + return loadConfig( + """ + age = 30 + friends = List("john", "mary") + address { street = "Fuzzy St." } + """); + } - protected abstract Config getMapConfig(); + private Config loadPairConfig() { + return loadConfig( + """ + x { first = "file/path"; second = 42 } + """); + } + + private Config loadMapConfig() { + return loadConfig( + """ + x = Map("one", 1, "two", 2) + """); + } @Test public void navigate() { - var pigeon = pigeonConfig.get("pigeon"); + var pigeon = loadPigeonConfig().get("pigeon"); assertThat(pigeon.getQualifiedName()).isEqualTo("pigeon"); assertThat(pigeon.getRawValue()).isInstanceOf(PObject.class); @@ -58,7 +85,7 @@ public abstract class AbstractConfigTest { @Test public void navigateToNonExistingObjectChild() { - var pigeon = pigeonConfig.get("pigeon"); + var pigeon = loadPigeonConfig().get("pigeon"); var t = catchThrowable(() -> pigeon.get("non-existing")); assertThat(t) @@ -70,7 +97,7 @@ public abstract class AbstractConfigTest { @Test public void navigateToNonExistingMapChild() { - var map = mapConfig.get("x"); + var map = loadMapConfig().get("x"); var t = catchThrowable(() -> map.get("non-existing")); assertThat(t) @@ -81,7 +108,7 @@ public abstract class AbstractConfigTest { @Test public void navigateToNonExistingLeafChild() { - var age = pigeonConfig.get("pigeon").get("age"); + var age = loadPigeonConfig().get("pigeon").get("age"); var t = catchThrowable(() -> age.get("non-existing")); assertThat(t) @@ -92,25 +119,25 @@ public abstract class AbstractConfigTest { @Test public void convertObjectToPojoByType() { - Person pigeon = pigeonConfig.get("pigeon").as(Person.class); + Person pigeon = loadPigeonConfig().get("pigeon").as(Person.class); checkPigeon(pigeon); } @Test public void convertObjectToPojoByJavaType() { - var pigeon = pigeonConfig.get("pigeon").as(JavaType.of(Person.class)); + var pigeon = loadPigeonConfig().get("pigeon").as(JavaType.of(Person.class)); checkPigeon(pigeon); } @Test public void convertModuleToPojoByType() { - var pigeon = pigeonModuleConfig.as(Person.class); + var pigeon = loadPigeonModuleConfig().as(Person.class); checkPigeon(pigeon); } @Test public void convertModuleToPojoByJavaType() { - var pigeon = pigeonModuleConfig.as(JavaType.of(Person.class)); + var pigeon = loadPigeonModuleConfig().as(JavaType.of(Person.class)); checkPigeon(pigeon); } @@ -124,13 +151,15 @@ public abstract class AbstractConfigTest { @Test public void convertToParameterizedTypeByType() { Pair pair = - pairConfig.get("x").as(Types.parameterizedType(Pair.class, Path.class, Integer.class)); + loadPairConfig() + .get("x") + .as(Types.parameterizedType(Pair.class, Path.class, Integer.class)); checkPair(pair); } @Test public void convertToParameterizedTypeByJavaType() { - var pair = pairConfig.get("x").as(new JavaType>() {}); + var pair = loadPairConfig().get("x").as(new JavaType>() {}); checkPair(pair); } diff --git a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDecoderBuilderTest.java b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDecoderBuilderTest.java new file mode 100644 index 00000000..677abb23 --- /dev/null +++ b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDecoderBuilderTest.java @@ -0,0 +1,39 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.config.java; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.pkl.config.java.mapper.ConverterFactories; + +public final class ConfigDecoderBuilderTest { + @Test + public void preconfiguredBuilderHasPreconfiguredMapperBuilder() { + var builder = ConfigDecoderBuilder.preconfigured(); + var mapperBuilder = builder.getValueMapperBuilder(); + assertThat(mapperBuilder).isNotNull(); + assertThat(mapperBuilder.getConverterFactories()).isEqualTo(ConverterFactories.all); + } + + @Test + public void unconfiguredBuilderHasUnconfiguredMapperBuilder() { + var builder = ConfigDecoderBuilder.unconfigured(); + var mapperBuilder = builder.getValueMapperBuilder(); + assertThat(mapperBuilder).isNotNull(); + assertThat(mapperBuilder.getConverterFactories()).isEmpty(); + } +} diff --git a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDecoderTest.java b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDecoderTest.java new file mode 100644 index 00000000..f98724ee --- /dev/null +++ b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDecoderTest.java @@ -0,0 +1,78 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.config.java; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayInputStream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.pkl.config.java.mapper.ValueMapper; +import org.pkl.core.Evaluator; +import org.pkl.core.ModuleSource; + +public final class ConfigDecoderTest extends AbstractConfigTest { + private static final Evaluator evaluator = Evaluator.preconfigured(); + + @AfterAll + public static void afterAll() { + evaluator.close(); + } + + private ModuleSource toModuleSource(String text) { + return ModuleSource.text( +""" +import "pkl:pklbinary" +""" + + text + + """ + output { + renderer = new pklbinary.Renderer {} + } + """); + } + + @Override + protected Config loadConfig(String text) { + var bytes = evaluator.evaluateOutputBytes(toModuleSource(text)); + return ConfigDecoder.preconfigured().decode(bytes); + } + + @Test + public void fromInputStream() { + var bytes = evaluator.evaluateOutputBytes(toModuleSource(pigeonText)); + var config = ConfigDecoder.preconfigured().decode(new ByteArrayInputStream(bytes)); + assertThat(config.get("pigeon").get("age").as(Integer.class)).isEqualTo(30); + } + + @Test + public void fromBytesWithOwnValueMapper() { + var bytes = evaluator.evaluateOutputBytes(toModuleSource(pigeonText)); + var config = + ConfigDecoder.preconfigured().setValueMapper(ValueMapper.preconfigured()).decode(bytes); + assertThat(config.get("pigeon").get("age").as(Integer.class)).isEqualTo(30); + } + + @Test + public void fromInputStreamWithOwnValueMapper() { + var bytes = evaluator.evaluateOutputBytes(toModuleSource(pigeonText)); + var config = + ConfigDecoder.preconfigured() + .setValueMapper(ValueMapper.preconfigured()) + .decode(new ByteArrayInputStream(bytes)); + assertThat(config.get("pigeon").get("age").as(Integer.class)).isEqualTo(30); + } +} diff --git a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDeprecatedApiTest.java b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDeprecatedApiTest.java new file mode 100644 index 00000000..32966e0b --- /dev/null +++ b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigDeprecatedApiTest.java @@ -0,0 +1,75 @@ +/* + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.config.java; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayInputStream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.pkl.config.java.mapper.ValueMapper; +import org.pkl.core.Evaluator; +import org.pkl.core.ModuleSource; + +@SuppressWarnings("removal") +public final class ConfigDeprecatedApiTest extends AbstractConfigTest { + private static final Evaluator evaluator = Evaluator.preconfigured(); + + @AfterAll + public static void afterAll() { + evaluator.close(); + } + + private ModuleSource toModuleSource(String text) { + return ModuleSource.text( +""" +import "pkl:pklbinary" +""" + + text + + """ + output { + renderer = new pklbinary.Renderer {} + } + """); + } + + @Override + protected Config loadConfig(String text) { + var bytes = evaluator.evaluateOutputBytes(toModuleSource(text)); + return Config.fromPklBinary(bytes); + } + + @Test + public void fromInputStream() { + var bytes = evaluator.evaluateOutputBytes(toModuleSource(pigeonText)); + var config = Config.fromPklBinary(new ByteArrayInputStream(bytes)); + assertThat(config.get("pigeon").get("age").as(Integer.class)).isEqualTo(30); + } + + @Test + public void fromBytesWithOwnValueMapper() { + var bytes = evaluator.evaluateOutputBytes(toModuleSource(pigeonText)); + var config = Config.fromPklBinary(bytes, ValueMapper.preconfigured()); + assertThat(config.get("pigeon").get("age").as(Integer.class)).isEqualTo(30); + } + + @Test + public void fromInputStreamWithOwnValueMapper() { + var bytes = evaluator.evaluateOutputBytes(toModuleSource(pigeonText)); + var config = Config.fromPklBinary(new ByteArrayInputStream(bytes), ValueMapper.preconfigured()); + assertThat(config.get("pigeon").get("age").as(Integer.class)).isEqualTo(30); + } +} diff --git a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigEvaluatorBuilderTest.java b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigEvaluatorBuilderTest.java index 94e9a503..bb467cb4 100644 --- a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigEvaluatorBuilderTest.java +++ b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigEvaluatorBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; import org.pkl.config.java.mapper.ConverterFactories; import org.pkl.core.SecurityManagers; -public class ConfigEvaluatorBuilderTest { +public final class ConfigEvaluatorBuilderTest { @Test public void preconfiguredBuilderHasPreconfiguredUnderlyingBuilders() { var builder = ConfigEvaluatorBuilder.preconfigured(); diff --git a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigTest.java b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigEvaluatorTest.java similarity index 52% rename from pkl-config-java/src/test/java/org/pkl/config/java/ConfigTest.java rename to pkl-config-java/src/test/java/org/pkl/config/java/ConfigEvaluatorTest.java index c03c0e14..c1b99380 100644 --- a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigTest.java +++ b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigEvaluatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,49 +16,37 @@ package org.pkl.config.java; import static org.assertj.core.api.Assertions.assertThat; -import static org.pkl.core.ModuleSource.text; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; +import org.pkl.core.ModuleSource; -public class ConfigTest extends AbstractConfigTest { +public final class ConfigEvaluatorTest extends AbstractConfigTest { private static final ConfigEvaluator evaluator = ConfigEvaluator.preconfigured(); - private static final String pigeonText = - "pigeon { age = 30; friends = List(\"john\", \"mary\"); address { street = \"Fuzzy St.\" } }"; - - @Override - protected Config getPigeonConfig() { - return evaluator.evaluate(text(pigeonText)); + @AfterAll + public static void afterAll() { + evaluator.close(); } @Override - protected Config getPigeonModuleConfig() { - return evaluator.evaluate( - text("age = 30; friends = List(\"john\", \"mary\"); address { street = \"Fuzzy St.\" }")); - } - - @Override - protected Config getPairConfig() { - return evaluator.evaluate(text("x { first = \"file/path\"; second = 42 }")); - } - - @Override - protected Config getMapConfig() { - return evaluator.evaluate(text("x = Map(\"one\", 1, \"two\", 2)")); + protected Config loadConfig(String text) { + return evaluator.evaluate(ModuleSource.text(text)); } @Test public void evaluateOutputValue() { var valueConfig = evaluator.evaluateOutputValue( - text(pigeonText + "\noutput { value = (outer) { pigeon { age = 99 } } }")); + ModuleSource.text(pigeonText + "\noutput { value = (outer) { pigeon { age = 99 } } }")); var pigeon = valueConfig.get("pigeon").as(Person.class); assertThat(pigeon.age).isEqualTo(99); } @Test public void evaluateExpression() { - var addressConfig = evaluator.evaluateExpression(text(pigeonText), "pigeon.address"); + var addressConfig = + evaluator.evaluateExpression(ModuleSource.text(pigeonText), "pigeon.address"); var address = addressConfig.as(Address.class); assertThat(address.street).isEqualTo("Fuzzy St."); } diff --git a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigPklBinaryDecoderTest.java b/pkl-config-java/src/test/java/org/pkl/config/java/ConfigPklBinaryDecoderTest.java deleted file mode 100644 index 26591429..00000000 --- a/pkl-config-java/src/test/java/org/pkl/config/java/ConfigPklBinaryDecoderTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.pkl.config.java; - -import java.util.Base64; - -public class ConfigPklBinaryDecoderTest extends AbstractConfigTest { - // generate via: pbpaste | ./pkl-cli/build/executable/jpkl eval /dev/stdin -f pkl-binary | base64 - - @Override - protected Config getPigeonConfig() { - // pigeon { age = 30; friends = List("john", "mary"); address { street = "Fuzzy St." } } - return Config.fromPklBinary( - Base64.getDecoder() - .decode( - "lAGkdGVzdNklZmlsZTovLy9Vc2Vycy9qYmFzY2gvc3JjL3BrbC90ZXN0LnBrbJGTEKZwaWdlb26UAadEeW5hbWljqHBrbDpiYXNlk5MQo2FnZR6TEKdmcmllbmRzkgSSpGpvaG6kbWFyeZMQp2FkZHJlc3OUAadEeW5hbWljqHBrbDpiYXNlkZMQpnN0cmVldKlGdXp6eSBTdC4=")); - } - - @Override - protected Config getPigeonModuleConfig() { - // age = 30; friends = List("john", "mary"); address { street = "Fuzzy St." } - return Config.fromPklBinary( - Base64.getDecoder() - .decode( - "lAGlc3RkaW6xZmlsZTovLy9kZXYvc3RkaW6TkxCjYWdlHpMQp2ZyaWVuZHOSBJKkam9obqRtYXJ5kxCnYWRkcmVzc5QBp0R5bmFtaWOocGtsOmJhc2WRkxCmc3RyZWV0qUZ1enp5IFN0Lg==")); - } - - @Override - protected Config getPairConfig() { - // x { first = "file/path"; second = 42 } - return Config.fromPklBinary( - Base64.getDecoder() - .decode( - "lAGlc3RkaW6xZmlsZTovLy9kZXYvc3RkaW6RkxCheJQBp0R5bmFtaWOocGtsOmJhc2WSkxClZmlyc3SpZmlsZS9wYXRokxCmc2Vjb25kKg==")); - } - - @Override - protected Config getMapConfig() { - // x = Map("one", 1, "two", 2) - return Config.fromPklBinary( - Base64.getDecoder().decode("lAGlc3RkaW6xZmlsZTovLy9kZXYvc3RkaW6RkxCheJICgqNvbmUBo3R3bwI=")); - } -} diff --git a/pkl-config-java/src/test/java/org/pkl/config/java/JavaTypeTest.java b/pkl-config-java/src/test/java/org/pkl/config/java/JavaTypeTest.java index db5c0af3..d36a8d4b 100644 --- a/pkl-config-java/src/test/java/org/pkl/config/java/JavaTypeTest.java +++ b/pkl-config-java/src/test/java/org/pkl/config/java/JavaTypeTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test; import org.pkl.config.java.mapper.Reflection; import org.pkl.config.java.mapper.Types; -public class JavaTypeTest { +public final class JavaTypeTest { @Test public void constructOptionalType() { var type = JavaType.optionalOf(String.class); diff --git a/pkl-config-kotlin/src/main/kotlin/org/pkl/config/kotlin/ConfigExtensions.kt b/pkl-config-kotlin/src/main/kotlin/org/pkl/config/kotlin/ConfigExtensions.kt index 1a783e8b..44f26f81 100644 --- a/pkl-config-kotlin/src/main/kotlin/org/pkl/config/kotlin/ConfigExtensions.kt +++ b/pkl-config-kotlin/src/main/kotlin/org/pkl/config/kotlin/ConfigExtensions.kt @@ -18,6 +18,8 @@ package org.pkl.config.kotlin import kotlin.reflect.jvm.javaType import kotlin.reflect.typeOf import org.pkl.config.java.Config +import org.pkl.config.java.ConfigDecoder +import org.pkl.config.java.ConfigDecoderBuilder import org.pkl.config.java.ConfigEvaluator import org.pkl.config.java.ConfigEvaluatorBuilder import org.pkl.config.java.mapper.ConversionException @@ -61,5 +63,20 @@ fun ValueMapperBuilder.forKotlin(): ValueMapperBuilder = fun ConfigEvaluatorBuilder.forKotlin(): ConfigEvaluatorBuilder = setValueMapperBuilder(valueMapperBuilder.forKotlin()) +/** + * Returns a new [ConfigEvaluator] with added conversions and converter factories for Kotlin types. + */ fun ConfigEvaluator.forKotlin(): ConfigEvaluator = setValueMapper(valueMapper.toBuilder().forKotlin().build()) + +/** + * Configures this [ConfigDecoderBuilder] with conversions and converter factories for Kotlin types. + */ +fun ConfigDecoderBuilder.forKotlin(): ConfigDecoderBuilder = + setValueMapperBuilder(valueMapperBuilder.forKotlin()) + +/** + * Returns a new [ConfigDecoder] with added conversions and converter factories for Kotlin types. + */ +fun ConfigDecoder.forKotlin(): ConfigDecoder = + setValueMapper(valueMapper.toBuilder().forKotlin().build()) diff --git a/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigExtensionsTest.kt b/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/AbstractConfigExtensionsTest.kt similarity index 76% rename from pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigExtensionsTest.kt rename to pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/AbstractConfigExtensionsTest.kt index 867c628b..eb74d37e 100644 --- a/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigExtensionsTest.kt +++ b/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/AbstractConfigExtensionsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,32 +18,30 @@ package org.pkl.config.kotlin import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.pkl.config.java.ConfigEvaluator -import org.pkl.config.java.ConfigEvaluatorBuilder +import org.pkl.config.java.Config import org.pkl.config.java.mapper.ConversionException -import org.pkl.config.kotlin.ConfigExtensionsTest.Hobby.READING -import org.pkl.config.kotlin.ConfigExtensionsTest.Hobby.SWIMMING -import org.pkl.core.ModuleSource.text +import org.pkl.config.kotlin.AbstractConfigExtensionsTest.Hobby.READING +import org.pkl.config.kotlin.AbstractConfigExtensionsTest.Hobby.SWIMMING -class ConfigExtensionsTest { - private val evaluator = ConfigEvaluator.preconfigured().forKotlin() +abstract class AbstractConfigExtensionsTest { + + protected abstract fun loadConfig(text: String): Config @Test fun `convert to kotlin classes`() { val config = - evaluator.evaluate( - text( - """ - pigeon { - name = "pigeon" - age = 30 - hobbies = List("swimming", "reading") - address { - street = "Fuzzy St." - } - } + loadConfig( """ - ) + pigeon { + name = "pigeon" + age = 30 + hobbies = List("swimming", "reading") + address { + street = "Fuzzy St." + } + } + """ + .trimIndent() ) val address = config["pigeon"]["address"].to>() @@ -60,9 +58,7 @@ class ConfigExtensionsTest { @Test fun `convert to kotlin class with nullable property`() { // cover ConfigEvaluatorBuilder.preconfigured() - val evaluator = ConfigEvaluatorBuilder.preconfigured().forKotlin().build() - - val config = evaluator.evaluate(text("pigeon { address = null }")) + val config = loadConfig("pigeon { address = null }") val pigeon = config["pigeon"].to() assertThat(pigeon.address).isNull() @@ -71,10 +67,8 @@ class ConfigExtensionsTest { @Test fun `convert to kotlin class with covariant collection property type`() { val config = - evaluator.evaluate( - text( - """pigeon { addresses = List(new Dynamic { street = "Fuzzy St." }, new Dynamic { street = "Other St." }) }""" - ) + loadConfig( + """pigeon { addresses = List(new Dynamic { street = "Fuzzy St." }, new Dynamic { street = "Other St." }) }""" ) config["pigeon"].to() @@ -82,8 +76,7 @@ class ConfigExtensionsTest { @Test fun `convert to nullable type`() { - val config = - evaluator.evaluate(text("""pigeon { address1 { street = "Fuzzy St." }; address2 = null }""")) + val config = loadConfig("""pigeon { address1 { street = "Fuzzy St." }; address2 = null }""") val address1 = config["pigeon"]["address1"].to?>() assertThat(address1).isEqualTo(Address(street = "Fuzzy St.")) @@ -102,16 +95,14 @@ class ConfigExtensionsTest { @Test fun `convert to kotlin class that has defaults for constructor args`() { val config = - evaluator.evaluate( - text( - """ + loadConfig( + """ pigeon { name = "Pigeon" age = 42 hobbies = List() } """ - ) ) val pigeon = config["pigeon"].to() @@ -124,16 +115,14 @@ class ConfigExtensionsTest { @Test fun `convert to java class with multiple constructors`() { val config = - evaluator.evaluate( - text( - """ + loadConfig( + """ pigeon { name = "Pigeon" age = 42 hobbies = List() } """ - ) ) val pigeon = config["pigeon"].to() @@ -145,10 +134,8 @@ class ConfigExtensionsTest { @Test fun `convert list to parameterized list`() { val config = - evaluator.evaluate( - text( - """friends = List(new Dynamic { name = "lilly"}, new Dynamic {name = "bob"}, new Dynamic {name = "susan"})""" - ) + loadConfig( + """friends = List(new Dynamic { name = "lilly"}, new Dynamic {name = "bob"}, new Dynamic {name = "susan"})""" ) val friends = config["friends"].to>() @@ -159,10 +146,8 @@ class ConfigExtensionsTest { @Test fun `convert map to parameterized map`() { val config = - evaluator.evaluate( - text( - """friends = Map("l", new Dynamic { name = "lilly"}, "b", new Dynamic { name = "bob"}, "s", new Dynamic { name = "susan"})""" - ) + loadConfig( + """friends = Map("l", new Dynamic { name = "lilly"}, "b", new Dynamic { name = "bob"}, "s", new Dynamic { name = "susan"})""" ) val friends = config["friends"].to>() @@ -179,9 +164,7 @@ class ConfigExtensionsTest { @Test fun `convert container to parameterized map`() { val config = - evaluator.evaluate( - text("""friends {l { name = "lilly"}; b { name = "bob"}; s { name = "susan"}}""") - ) + loadConfig("""friends {l { name = "lilly"}; b { name = "bob"}; s { name = "susan"}}""") val friends = config["friends"].to>() assertThat(friends) @@ -198,14 +181,12 @@ class ConfigExtensionsTest { fun `convert enum with mangled names`() { val values = MangledNameEnum.entries.map { "\"$it\"" } val config = - evaluator.evaluate( - text( - """ + loadConfig( + """ typealias MangledNameEnum = ${values.joinToString(" | ")} allEnumValues: Set = Set(${values.joinToString(", ")}) """ - .trimIndent() - ) + .trimIndent() ) val allEnumValues = config["allEnumValues"].to>() assertThat(allEnumValues).isEqualTo(MangledNameEnum.entries.toSet()) diff --git a/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigDecoderExtensionsTest.kt b/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigDecoderExtensionsTest.kt new file mode 100644 index 00000000..259cce74 --- /dev/null +++ b/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigDecoderExtensionsTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.config.kotlin + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Test +import org.pkl.config.java.Config +import org.pkl.config.java.ConfigDecoder +import org.pkl.config.java.ConfigDecoderBuilder +import org.pkl.config.java.mapper.ValueMapperBuilder +import org.pkl.core.Evaluator +import org.pkl.core.ModuleSource + +class ConfigDecoderExtensionsTest : AbstractConfigExtensionsTest() { + companion object { + private val evaluator = Evaluator.preconfigured() + + @AfterAll + @JvmStatic + fun afterAll() { + evaluator.close() + } + } + + override fun loadConfig(text: String): Config { + val updatedText = + """ + import "pkl:pklbinary" + $text + output { + renderer = new pklbinary.Renderer {} + } + """ + .trimIndent() + val bytes = evaluator.evaluateOutputBytes(ModuleSource.text(updatedText)) + return ConfigDecoder.preconfigured().forKotlin().decode(bytes) + } + + @Test + fun `ConfigDecoderBuilder_forKotlin configures its valueMapperBuilder accordingly`() { + val builderMapper = ConfigDecoderBuilder.preconfigured().forKotlin().valueMapperBuilder + val referenceMapper = ValueMapperBuilder.preconfigured().forKotlin() + + assertThat(builderMapper.conversions.size).isEqualTo(referenceMapper.conversions.size) + assertThat(builderMapper.converterFactories.size) + .isEqualTo(referenceMapper.converterFactories.size) + } +} diff --git a/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigEvaluatorExtensionsTest.kt b/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigEvaluatorExtensionsTest.kt new file mode 100644 index 00000000..390413ef --- /dev/null +++ b/pkl-config-kotlin/src/test/kotlin/org/pkl/config/kotlin/ConfigEvaluatorExtensionsTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pkl.config.kotlin + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.Test +import org.pkl.config.java.Config +import org.pkl.config.java.ConfigEvaluator +import org.pkl.config.java.ConfigEvaluatorBuilder +import org.pkl.config.java.mapper.ValueMapperBuilder +import org.pkl.core.ModuleSource.text + +class ConfigEvaluatorExtensionsTest : AbstractConfigExtensionsTest() { + companion object { + private val evaluator = ConfigEvaluator.preconfigured().forKotlin() + + @AfterAll + @JvmStatic + fun afterAll() { + evaluator.close() + } + } + + override fun loadConfig(text: String): Config = evaluator.evaluate(text(text)) + + @Test + fun `ConfigEvaluaterBuilder_forKotlin configures its valueMapperBuilder accordingly`() { + val builderMapper = ConfigEvaluatorBuilder.preconfigured().forKotlin().valueMapperBuilder + val referenceMapper = ValueMapperBuilder.preconfigured().forKotlin() + + assertThat(builderMapper.conversions.size).isEqualTo(referenceMapper.conversions.size) + assertThat(builderMapper.converterFactories.size) + .isEqualTo(referenceMapper.converterFactories.size) + } +}