pkl-config-java: Replace Config.fromPklBinary() with ConfigDecoder (#1533)

Motivation:
- `Config` mixes configuration representation with decoding logic
- `Config.fromPklBinary()` does not scale as decoding gains options
(e.g., binary versions or formats)
- The decoding API is inconsistent with `ConfigEvaluator`

Changes:
- Introduce `ConfigDecoder` (with builder) and move
`Config.fromPklBinary()` logic into it
- Deprecate `Config.fromPklBinary()` methods for removal
- Add `ConfigDecoder.forKotlin()` extension function
- Update and improve tests

Result:
- Decoding is separated from `Config` and exposed via a dedicated API
- Decoding can evolve independently (e.g., adding options such as binary
versions or supporting new formats)
- Evaluation and decoding APIs follow a consistent design
This commit is contained in:
odenix
2026-04-20 19:09:42 +01:00
committed by GitHub
parent 07c68239b9
commit 7a75ab57f5
18 changed files with 638 additions and 179 deletions

View File

@@ -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 extends @Nullable Object> T as(JavaType<T> 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);
}
}

View File

@@ -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()}.
*
* <p>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.
*
* <p>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);
}

View File

@@ -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.
*
* <p>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());
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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<Path, Integer> 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<Pair<Path, Integer>>() {});
var pair = loadPairConfig().get("x").as(new JavaType<Pair<Path, Integer>>() {});
checkPair(pair);
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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.");
}

View File

@@ -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="));
}
}

View File

@@ -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);