From eab71229e7c96e8bb632913df017eee02bf585de Mon Sep 17 00:00:00 2001 From: Jen Basch Date: Thu, 30 Oct 2025 15:53:43 -0700 Subject: [PATCH] Add support for rendering `Bytes` values with `YamlRenderer` (#1276) --- .../ROOT/partials/component-attributes.adoc | 2 +- docs/modules/release-notes/pages/0.30.adoc | 17 +++++++++- .../main/java/org/pkl/core/YamlRenderer.java | 3 +- .../core/stdlib/base/YamlRendererNodes.java | 3 +- .../org/pkl/core/util/yaml/YamlEmitter.java | 7 ++++ .../pkl/core/util/yaml/snake/YamlUtils.java | 12 ++++++- .../input/api/yamlRenderer2.yml.pkl | 3 ++ .../input/api/yamlRenderer3.yml.pkl | 2 ++ .../output/api/yamlRenderer2.yml | 3 ++ .../output/api/yamlRenderer3.yml | 1 + .../kotlin/org/pkl/core/YamlRendererTest.kt | 34 ++++++++++++++++++- 11 files changed, 80 insertions(+), 7 deletions(-) diff --git a/docs/modules/ROOT/partials/component-attributes.adoc b/docs/modules/ROOT/partials/component-attributes.adoc index 47b64028..fb1bd3f5 100644 --- a/docs/modules/ROOT/partials/component-attributes.adoc +++ b/docs/modules/ROOT/partials/component-attributes.adoc @@ -76,7 +76,6 @@ endif::[] :uri-stdlib-protobufModule: {uri-pkl-stdlib-docs}/protobuf :uri-stdlib-pklbinaryModule: {uri-pkl-stdlib-docs}/pklbinary :uri-stdlib-evaluatorSettingsModule: {uri-pkl-stdlib-docs}/EvaluatorSettings -:uri-stdlib-pklbinaryModule: {uri-pkl-stdlib-docs}/pklbinary :uri-stdlib-evaluatorSettingsHttpClass: {uri-stdlib-evaluatorSettingsModule}/Http :uri-stdlib-Boolean: {uri-stdlib-baseModule}/Boolean :uri-stdlib-xor: {uri-stdlib-baseModule}/Boolean#xor() @@ -135,6 +134,7 @@ endif::[] :uri-stdlib-BaseValueRenderer: {uri-stdlib-baseModule}/BaseValueRenderer :uri-stdlib-ValueRenderer: {uri-stdlib-baseModule}/ValueRenderer :uri-stdlib-BytesRenderer: {uri-stdlib-baseModule}/BytesRenderer +:uri-stdlib-YamlRenderer: {uri-stdlib-baseModule}/YamlRenderer :uri-stdlib-PcfRenderer-converters: {uri-stdlib-baseModule}/PcfRenderer#converters :uri-stdlib-Function: {uri-stdlib-baseModule}/Function :uri-stdlib-Function0: {uri-stdlib-baseModule}/Function0 diff --git a/docs/modules/release-notes/pages/0.30.adoc b/docs/modules/release-notes/pages/0.30.adoc index ff6d6ff6..d8a3e20f 100644 --- a/docs/modules/release-notes/pages/0.30.adoc +++ b/docs/modules/release-notes/pages/0.30.adoc @@ -265,6 +265,21 @@ pkl eval --trace-mode pretty myModule.pkl Thanks to https://github.com/ssalevan[@ssalevan] for their contribution to this feature! +=== Better support for `Bytes` when rendering YAML + +Previously, attempting to render a `Bytes` value using {uri-stdlib-YamlRenderer}[`YamlRenderer`] required the use of a link:{uri-stdlib-PcfRenderer-converters}[converter]. +Now, Pkl can natively render YAML containing https://yaml.org/type/binary.html[binary scalars] (https://github.com/apple/pkl/pull/1276[#1276]). + +[source,pkl%tested] +---- +foo { + bar = Bytes(1, 2, 3) +} + +rendered = new YamlRenderer {}.renderValue(foo) // <1> +---- +<1> Result: `bar: !!binary AQID` + [[pkldoc-perf-improvements]] === `pkldoc` performance improvements @@ -364,7 +379,7 @@ See <> for more details. == Miscellaneous [small]#🐸# * Dependency updates (https://github.com/apple/pkl/pull/1184[#1184], https://github.com/apple/pkl/pull/1225[#1225], https://github.com/apple/pkl/pull/1226[#1226], https://github.com/apple/pkl/pull/1228[#1228]). -* Enforce Pkl formatting of stdlib (https://github.com/apple/pkl/pull/1236[#1236], https://github.com/apple/pkl/pull/1253[#1253], https://github.com/apple/pkl/pull/1258[#1258]). +* Enforce Pkl formatting of stdlib (https://github.com/apple/pkl/pull/1236[#1236], https://github.com/apple/pkl/pull/1253[#1253], https://github.com/apple/pkl/pull/1258[#1258], https://github.com/apple/pkl/pull/1278[#1278]). * Add internal IntelliJ plugin that's meant to assist with development of the Pkl codebase itself (https://github.com/apple/pkl/pull/1248[#1248]). * Update CircleCI macOS instance type and Xcode version (https://github.com/apple/pkl/pull/1243[#1243], https://github.com/apple/pkl/pull/1244[#1244]). * Disable multi-jdk testing when running on Windows ARM (https://github.com/apple/pkl/pull/1223[#1223]). diff --git a/pkl-core/src/main/java/org/pkl/core/YamlRenderer.java b/pkl-core/src/main/java/org/pkl/core/YamlRenderer.java index d6633685..5081c3f3 100644 --- a/pkl-core/src/main/java/org/pkl/core/YamlRenderer.java +++ b/pkl-core/src/main/java/org/pkl/core/YamlRenderer.java @@ -153,8 +153,7 @@ final class YamlRenderer implements ValueRenderer { @Override public void visitBytes(byte[] value) { - throw new RendererException( - String.format("Values of type `Bytes` cannot be rendered as YAML. Value: %s", value)); + emitter.emit(YamlUtils.bytesScalar(value)); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/YamlRendererNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/YamlRendererNodes.java index 9b544744..d95ba277 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/YamlRendererNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/YamlRendererNodes.java @@ -180,7 +180,8 @@ public final class YamlRendererNodes { @Override public void visitBytes(VmBytes value) { - cannotRenderTypeAddConverter(value); + if (!builder.isEmpty()) builder.append(' '); + emitter.emit(value.getBytes(), currIndent, false); } @Override diff --git a/pkl-core/src/main/java/org/pkl/core/util/yaml/YamlEmitter.java b/pkl-core/src/main/java/org/pkl/core/util/yaml/YamlEmitter.java index cd0387cf..c0b9f58c 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/yaml/YamlEmitter.java +++ b/pkl-core/src/main/java/org/pkl/core/util/yaml/YamlEmitter.java @@ -15,6 +15,8 @@ */ package org.pkl.core.util.yaml; +import java.util.Base64; + // Useful links: // https://yaml-online-parser.appspot.com // https://github.com/FasterXML/jackson-dataformats-text/pull/201 @@ -210,6 +212,11 @@ public abstract class YamlEmitter { builder.append(value); } + public final void emit(byte[] value, StringBuilder currIndent, boolean isKey) { + builder.append("!!binary "); + emit(Base64.getEncoder().encodeToString(value), currIndent, isKey); + } + public final void emitNull() { builder.append("null"); } diff --git a/pkl-core/src/main/java/org/pkl/core/util/yaml/snake/YamlUtils.java b/pkl-core/src/main/java/org/pkl/core/util/yaml/snake/YamlUtils.java index d481d539..92a0f72d 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/yaml/snake/YamlUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/util/yaml/snake/YamlUtils.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * 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. @@ -33,6 +33,9 @@ public final class YamlUtils { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private static final Optional STRING_TAG = Optional.of(Tag.STR.toString()); + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private static final Optional BINARY_TAG = Optional.of(Tag.BINARY.toString()); + private static final ImplicitTuple TUPLE = new ImplicitTuple(true, true); private YamlUtils() {} @@ -56,6 +59,13 @@ public final class YamlUtils { return new ScalarEvent(Optional.empty(), STRING_TAG, tuple, value, scalarStyle); } + /** Constructs a {@link ScalarEvent} for emitting the given value as YAML binary. */ + public static ScalarEvent bytesScalar(byte[] value) { + var encoded = Base64.getEncoder().encodeToString(value); + return new ScalarEvent( + Optional.empty(), BINARY_TAG, new ImplicitTuple(false, false), encoded, ScalarStyle.PLAIN); + } + /** Constructs a {@link ScalarEvent} for emitting the given value in plain style. */ public static ScalarEvent plainScalar(String value, Tag tag) { return new ScalarEvent( diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer2.yml.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer2.yml.pkl index 17dd48ac..db22afc1 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer2.yml.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer2.yml.pkl @@ -18,6 +18,9 @@ res13 = new Dynamic { name = "pigeon"; age = 30 } res14 = new Person { name = "pigeon" } res15 = null res16 = Pair(1, 2) +res17 = Bytes() +res18 = Bytes(1, 2, 3) +res19 = IntSeq(0, 127).toList().toBytes() output { renderer = new YamlRenderer { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer3.yml.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer3.yml.pkl index df4ce871..cd644e4e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer3.yml.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/yamlRenderer3.yml.pkl @@ -18,6 +18,7 @@ res13 = new Dynamic { name = "pigeon"; age = 30 } res14 = new Person { name = "pigeon"; age = 30 } res15 = null res16 = Pair(1, 2) +res17 = Bytes(1, 2, 3) output { renderer = new YamlRenderer { @@ -38,6 +39,7 @@ output { [Null] = (it) -> "converted" [Pair] = (it) -> "converted" [IntSeq] = (it) -> "converted" + [Bytes] = (it) -> "converted" } } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer2.yml b/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer2.yml index 319f52e3..abe70370 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer2.yml +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer2.yml @@ -37,3 +37,6 @@ res15: String res16: - 2 - 3 +res17: !!binary '' +res18: !!binary AQID +res19: !!binary AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8= diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer3.yml b/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer3.yml index 811ab659..58ca1240 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer3.yml +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/api/yamlRenderer3.yml @@ -13,3 +13,4 @@ res13: converted res14: converted res15: converted res16: converted +res17: converted diff --git a/pkl-core/src/test/kotlin/org/pkl/core/YamlRendererTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/YamlRendererTest.kt index 964bf044..64f5b079 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/YamlRendererTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/YamlRendererTest.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * 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. @@ -146,4 +146,36 @@ class YamlRendererTest { .trimIndent() ) } + + @Test + fun `render byte array values as binary`() { + val evaluator = Evaluator.preconfigured() + val module = + evaluator.evaluate( + ModuleSource.text( + """ + res1 = Bytes() + res2 = Bytes(1, 2, 3) + res3 = IntSeq(0, 127).toList().toBytes() + """ + .trimIndent() + ) + ) + + val writer = StringWriter() + val renderer = ValueRenderers.yaml(writer, 2, true, false) + + renderer.renderDocument(module) + val output = writer.toString() + + assertThat(output.trim()) + .isEqualTo( + """ + res1: !!binary '' + res2: !!binary 'AQID' + res3: !!binary 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=' + """ + .trimIndent() + ) + } }