From 22558f7d3effccb982ee81fb7f6eee4b9f930857 Mon Sep 17 00:00:00 2001 From: Valentin Delaye Date: Sun, 17 May 2026 06:38:47 +0200 Subject: [PATCH] Add YAML utils Signed-off-by: Valentin Delaye --- pom.xml | 4 + src/main/java/land/oras/utils/YamlUtils.java | 99 ++++++++++++++++ .../java/land/oras/utils/YamlUtilsTest.java | 107 ++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 src/main/java/land/oras/utils/YamlUtils.java create mode 100644 src/test/java/land/oras/utils/YamlUtilsTest.java diff --git a/pom.xml b/pom.xml index f236b568..f5395f88 100644 --- a/pom.xml +++ b/pom.xml @@ -250,6 +250,10 @@ tools.jackson.dataformat jackson-dataformat-toml + + tools.jackson.dataformat + jackson-dataformat-yaml + diff --git a/src/main/java/land/oras/utils/YamlUtils.java b/src/main/java/land/oras/utils/YamlUtils.java new file mode 100644 index 00000000..20aa7075 --- /dev/null +++ b/src/main/java/land/oras/utils/YamlUtils.java @@ -0,0 +1,99 @@ +/*- + * =LICENSE= + * ORAS Java SDK + * === + * Copyright (C) 2024 - 2025 ORAS + * === + * 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 + * + * http://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. + * =LICENSEEND= + */ + +package land.oras.utils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import land.oras.exception.OrasException; +import org.jspecify.annotations.NullMarked; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.dataformat.yaml.YAMLMapper; + +/** + * Utility class for YAML operations. + * Use Jackson 3 internally for YAML operations + */ +@NullMarked +public final class YamlUtils { + + /** + * TOML mapper instance + */ + private static final ObjectMapper yamlMapper; + + /** + * Utils class + */ + private YamlUtils() { + // Hide constructor + } + + static { + yamlMapper = YAMLMapper.builder().build(); + } + + /** + * Convert an object to a YAML string + * @param object The object to convert + * @return The YAML string + */ + public static String toYaml(Object object) { + try { + return yamlMapper.writeValueAsString(object); + } catch (JacksonException e) { + throw new OrasException("Unable to convert object to YAML string", e); + } + } + + /** + * Convert a YAML string to an object + * @param yaml The YAML string + * @param clazz The class of the object + * @param The type of the object + * @return The object + */ + public static T fromYaml(String yaml, Class clazz) { + try { + return yamlMapper.readValue(yaml, clazz); + } catch (JacksonException e) { + throw new OrasException("Unable to parse YAML string", e); + } + } + + /** + * Read a YAML file and convert its contents to an object. + * The file at the given {@code path} is read as UTF-8 YAML and deserialized into the specified type. + * @param path The path to the YAML file + * @param clazz The class of the object + * @param The type of the object + * @return The object deserialized from the YAML file + */ + public static T fromYaml(Path path, Class clazz) { + try { + return yamlMapper.readValue(Files.readString(path, StandardCharsets.UTF_8), clazz); + } catch (IOException | JacksonException e) { + throw new OrasException("Unable to read YAML from file", e); + } + } +} diff --git a/src/test/java/land/oras/utils/YamlUtilsTest.java b/src/test/java/land/oras/utils/YamlUtilsTest.java new file mode 100644 index 00000000..571eb6bd --- /dev/null +++ b/src/test/java/land/oras/utils/YamlUtilsTest.java @@ -0,0 +1,107 @@ +/*- + * =LICENSE= + * ORAS Java SDK + * === + * Copyright (C) 2024 - 2025 ORAS + * === + * 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 + * + * http://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. + * =LICENSEEND= + */ + +package land.oras.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import land.oras.exception.OrasException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +@Execution(ExecutionMode.CONCURRENT) +class YamlUtilsTest { + + /** + * Temporary dir + */ + @TempDir(cleanup = CleanupMode.ON_SUCCESS) + private static Path dir; + + @Test + void failToParseYamlString() { + assertThrows(OrasException.class, () -> YamlUtils.fromYaml("not a yaml: too, bar: test", Object.class)); + } + + @Test + @SuppressWarnings("unchecked") + void shouldParseYaml() { + String yamlMap = + """ + --- + key1: "value1" + key2: "value2" + """; + Map map = YamlUtils.fromYaml(yamlMap, Map.class); + assertNotNull(map); + assertEquals(2, map.size()); + assertEquals("value1", map.get("key1")); + assertEquals("value2", map.get("key2")); + } + + @Test + @SuppressWarnings("unchecked") + void shouldParseYamlFile() throws IOException { + String yamlMap = + """ + --- + key1: "value1" + key2: "value2" + """; + Path yamlFile = dir.resolve("test.yaml"); + Files.writeString(yamlFile, yamlMap); + Map map = YamlUtils.fromYaml(yamlFile, Map.class); + assertNotNull(map); + assertEquals(2, map.size()); + assertEquals("value1", map.get("key1")); + assertEquals("value2", map.get("key2")); + } + + @Test + @SuppressWarnings("unchecked") + void shouldFailToParseYamlFile() { + assertThrows(OrasException.class, () -> YamlUtils.fromYaml(Path.of("foo.yaml"), Map.class)); + } + + @Test + void shouldConvertToYaml() { + Map map = new LinkedHashMap<>(); + map.put("key1", "value1"); + map.put("key2", "value2"); + String yaml = YamlUtils.toYaml(map); + assertNotNull(yaml); + String expected = """ + --- + key1: "value1" + key2: "value2" + """; + assertEquals(expected, yaml); + } +}