From 681c7b2885bf8d16e56cde24986fe46cbfd395b9 Mon Sep 17 00:00:00 2001 From: MaxS97 Date: Sun, 13 Jul 2025 21:58:05 +0300 Subject: [PATCH 1/4] Added support for profiles --- .../artemget/entrys/profile/EValProf.java | 117 ++++++++++++++++++ .../artemget/entrys/profile/EValProfTest.java | 19 +++ 2 files changed, 136 insertions(+) create mode 100644 src/main/java/io/github/artemget/entrys/profile/EValProf.java create mode 100644 src/test/java/io/github/artemget/entrys/profile/EValProfTest.java diff --git a/src/main/java/io/github/artemget/entrys/profile/EValProf.java b/src/main/java/io/github/artemget/entrys/profile/EValProf.java new file mode 100644 index 0000000..8d826d2 --- /dev/null +++ b/src/main/java/io/github/artemget/entrys/profile/EValProf.java @@ -0,0 +1,117 @@ +package io.github.artemget.entrys.profile; + +import com.amihaiemil.eoyaml.Yaml; +import com.amihaiemil.eoyaml.YamlMapping; +import com.amihaiemil.eoyaml.YamlNode; +import io.github.artemget.entrys.ESafe; +import io.github.artemget.entrys.Entry; +import io.github.artemget.entrys.EntryException; +import io.github.artemget.entrys.file.EFile; +import io.github.artemget.entrys.file.EVal; +import io.github.artemget.entrys.operation.EContains; +import io.github.artemget.entrys.operation.EFork; +import io.github.artemget.entrys.operation.ESplit; +import io.github.artemget.entrys.operation.EUnwrap; +import io.github.artemget.entrys.system.EEnv; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public final class EValProf extends ESafe { + + public EValProf(final String key) { + this(key, "src/main/resources/application.yaml"); + } + + public EValProf(final String key, final String path) { + this(key, new EFile(path), path); + } + + public EValProf(final String key, final Entry content, final String path) { + super( + () -> { + YamlMapping root; + try { + root = Yaml.createYamlInput(content.value()) + .readYamlMapping(); + } catch (IOException | EntryException exception) { + throw new EntryException( + String.format("Failed to read yaml mapping for key: '%s'", key), + exception + ); + } + String res = null; + YamlNode profileNode = getProfileNode(root); + if (profileNode != null) { + String profile = EValProf.ejected(profileNode); + res = new EVal(key, parsePath(path, profile)).value(); + } else { + res = new EVal(key, content).value(); + } + return res; + }, + () -> String.format("Attribute for key '%s' is null", key) + ); + } + + private static YamlNode getProfileNode(final YamlMapping root) { + final List path = Arrays.asList("entrys","profile"); + YamlNode currentNode = root; + for(String part: path) { + if (currentNode instanceof YamlMapping) { + currentNode = ((YamlMapping) currentNode).value(part); + } else { + return null; + } + } + return currentNode; + } + + private static String ejected(final YamlNode profileNode) throws EntryException { + final String scalar = profileNode.asScalar().value(); + + return new EFork<>( + new EContains(scalar::isBlank), + () -> { + throw new EntryException("Attribute for key 'profile' is empty"); + }, + () -> new EFork<>( + () -> scalar.startsWith("${") && scalar.endsWith("}"), + new EFork<>( + () -> new ESplit(() -> scalar, ":").value().size() >= 2, + new EFork<>( + new EContains(new EEnv(() -> EValProf.selectedEnv(scalar).trim())), + new EEnv(() -> EValProf.selectedEnv(scalar).trim()), + () -> EValProf.joined(scalar) + ), + new EEnv(new EUnwrap(scalar, "${", "}")) + ), + () -> scalar + ).value() + ).value(); + } + + private static String parsePath(final String path, final String profile) { + return path.replace("application", "application-" + profile); + } + + private static String selectedEnv(final String scalar) throws EntryException { + return new ESplit(new EUnwrap(scalar, "${", "}"), ":") + .value().get(0); + } + + private static String joined(final String scalar) throws EntryException { + final List split = new ESplit( + new EUnwrap(scalar, "${", "}"), ":" + ).value(); + final StringBuilder value = new StringBuilder(); + for (int index = 1; index < split.size(); ++index) { + value.append(split.get(index)); + if (index < split.size() - 1) { + value.append(':'); + } + } + return value.toString(); + } +} \ No newline at end of file diff --git a/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java b/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java new file mode 100644 index 0000000..6805785 --- /dev/null +++ b/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java @@ -0,0 +1,19 @@ +package io.github.artemget.entrys.profile; + +import io.github.artemget.entrys.EntryException; +import io.github.artemget.entrys.fake.EFakeErr; +import io.github.artemget.entrys.file.EVal; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +final class EValProfTest { + + @Test + void throwsAtEmptyProfile(){ + Assertions.assertThrows( + EntryException.class, + ()-> new EValProf("profile", new EFakeErr<>().toString()).value(), + "Didnt throw at error getting content" + ); + } +} From 09f66d743f8ba9950be7f279c3c6675ebba4db95 Mon Sep 17 00:00:00 2001 From: MaxS97 Date: Mon, 14 Jul 2025 19:41:41 +0300 Subject: [PATCH 2/4] Reuse EVal instead of duplicating its internals; Add more tests; Devide ex handling; Avoid type casting --- .../artemget/entrys/profile/EValProf.java | 155 +++++------ .../artemget/entrys/profile/package-info.java | 28 ++ .../github/artemget/entrys/file/EValTest.java | 3 +- .../artemget/entrys/profile/EValProfTest.java | 255 +++++++++++++++++- .../artemget/entrys/profile/package-info.java | 28 ++ 5 files changed, 374 insertions(+), 95 deletions(-) create mode 100644 src/main/java/io/github/artemget/entrys/profile/package-info.java create mode 100644 src/test/java/io/github/artemget/entrys/profile/package-info.java diff --git a/src/main/java/io/github/artemget/entrys/profile/EValProf.java b/src/main/java/io/github/artemget/entrys/profile/EValProf.java index 8d826d2..2c112a3 100644 --- a/src/main/java/io/github/artemget/entrys/profile/EValProf.java +++ b/src/main/java/io/github/artemget/entrys/profile/EValProf.java @@ -1,117 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025. Artem Getmanskii + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.github.artemget.entrys.profile; -import com.amihaiemil.eoyaml.Yaml; -import com.amihaiemil.eoyaml.YamlMapping; -import com.amihaiemil.eoyaml.YamlNode; import io.github.artemget.entrys.ESafe; import io.github.artemget.entrys.Entry; import io.github.artemget.entrys.EntryException; import io.github.artemget.entrys.file.EFile; import io.github.artemget.entrys.file.EVal; -import io.github.artemget.entrys.operation.EContains; -import io.github.artemget.entrys.operation.EFork; -import io.github.artemget.entrys.operation.ESplit; -import io.github.artemget.entrys.operation.EUnwrap; -import io.github.artemget.entrys.system.EEnv; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; +/** + * Configuration properties entry. + * By default gets entries from "src/main/resources/application.yaml" + * Supports all yaml types, default values and envs passed via ${ENV}. + * + * @since 0.4.0 + */ public final class EValProf extends ESafe { + /** + * From yaml file at default dir. + * + * @param key Of Entry + */ public EValProf(final String key) { this(key, "src/main/resources/application.yaml"); } + /** + * From yaml file. + * + * @param key Of Entry + * @param path To Yaml File + */ public EValProf(final String key, final String path) { this(key, new EFile(path), path); } + /** + * Main ctor. + * + * @param key Of Entry + * @param content Yaml Content + * @param path Yaml Path + */ public EValProf(final String key, final Entry content, final String path) { super( - () -> { - YamlMapping root; - try { - root = Yaml.createYamlInput(content.value()) - .readYamlMapping(); - } catch (IOException | EntryException exception) { - throw new EntryException( - String.format("Failed to read yaml mapping for key: '%s'", key), - exception - ); - } - String res = null; - YamlNode profileNode = getProfileNode(root); - if (profileNode != null) { - String profile = EValProf.ejected(profileNode); - res = new EVal(key, parsePath(path, profile)).value(); + () -> { + String result; + final String profile; + try { + profile = new EVal("entrys.profile", path).value(); + if (profile.isBlank()) { + throw new EntryException("Attribute for key 'profile' is empty"); } else { - res = new EVal(key, content).value(); + result = new EVal(key, parsePath(path, profile)).value(); } - return res; - }, - () -> String.format("Attribute for key '%s' is null", key) + } catch (final EntryException exception) { + result = new EVal(key, content).value(); + } + return result; + }, + () -> String.format("Attribute for key '%s' is null", key) ); } - private static YamlNode getProfileNode(final YamlMapping root) { - final List path = Arrays.asList("entrys","profile"); - YamlNode currentNode = root; - for(String part: path) { - if (currentNode instanceof YamlMapping) { - currentNode = ((YamlMapping) currentNode).value(part); - } else { - return null; - } - } - return currentNode; - } - - private static String ejected(final YamlNode profileNode) throws EntryException { - final String scalar = profileNode.asScalar().value(); - - return new EFork<>( - new EContains(scalar::isBlank), - () -> { - throw new EntryException("Attribute for key 'profile' is empty"); - }, - () -> new EFork<>( - () -> scalar.startsWith("${") && scalar.endsWith("}"), - new EFork<>( - () -> new ESplit(() -> scalar, ":").value().size() >= 2, - new EFork<>( - new EContains(new EEnv(() -> EValProf.selectedEnv(scalar).trim())), - new EEnv(() -> EValProf.selectedEnv(scalar).trim()), - () -> EValProf.joined(scalar) - ), - new EEnv(new EUnwrap(scalar, "${", "}")) - ), - () -> scalar - ).value() - ).value(); - } - private static String parsePath(final String path, final String profile) { - return path.replace("application", "application-" + profile); - } - - private static String selectedEnv(final String scalar) throws EntryException { - return new ESplit(new EUnwrap(scalar, "${", "}"), ":") - .value().get(0); - } - - private static String joined(final String scalar) throws EntryException { - final List split = new ESplit( - new EUnwrap(scalar, "${", "}"), ":" - ).value(); - final StringBuilder value = new StringBuilder(); - for (int index = 1; index < split.size(); ++index) { - value.append(split.get(index)); - if (index < split.size() - 1) { - value.append(':'); - } - } - return value.toString(); + return path.replace("application", String.format("application%s", profile)); } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/artemget/entrys/profile/package-info.java b/src/main/java/io/github/artemget/entrys/profile/package-info.java new file mode 100644 index 0000000..e9b9346 --- /dev/null +++ b/src/main/java/io/github/artemget/entrys/profile/package-info.java @@ -0,0 +1,28 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025. Artem Getmanskii + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Profile operation directory. + */ +package io.github.artemget.entrys.profile; diff --git a/src/test/java/io/github/artemget/entrys/file/EValTest.java b/src/test/java/io/github/artemget/entrys/file/EValTest.java index 57f6836..b88cb51 100644 --- a/src/test/java/io/github/artemget/entrys/file/EValTest.java +++ b/src/test/java/io/github/artemget/entrys/file/EValTest.java @@ -33,6 +33,7 @@ /** * Test cases for {@link io.github.artemget.entrys.json.EJsonArr}. + * * @since 0.4.0 */ @SuppressWarnings({"PMD.AvoidDuplicateLiterals", "PMD.TooManyMethods"}) @@ -165,7 +166,7 @@ void parsesAnotherArray() throws EntryException { @Test void parsesStringWrap() throws EntryException { Assertions.assertEquals( - " First line.\n Second line.\n", + " First line.\n Second line.\n", new EVal( "description", new EFake<>("description: >\n First line.\n Second line.") diff --git a/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java b/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java index 6805785..856aad5 100644 --- a/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java +++ b/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java @@ -1,19 +1,266 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025. Artem Getmanskii + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.github.artemget.entrys.profile; import io.github.artemget.entrys.EntryException; +import io.github.artemget.entrys.fake.EFake; import io.github.artemget.entrys.fake.EFakeErr; import io.github.artemget.entrys.file.EVal; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +/** + * Test cases for {@link io.github.artemget.entrys.profile.EValProf}. + * + * @since 0.4.0 + */ +@SuppressWarnings({"PMD.AvoidDuplicateLiterals", "PMD.TooManyMethods"}) final class EValProfTest { @Test - void throwsAtEmptyProfile(){ + void throwsAtProfileIsBlank() { + Assertions.assertThrows( + EntryException.class, + () -> new EValProf("entrys.profile", new EFakeErr<>(), null).value(), + "Profile node is blank" + ); + } + + @Test + void throwsAtEmptyContent() { + Assertions.assertThrows( + EntryException.class, + () -> new EValProf("abc", new EFakeErr<>(), null).value(), + "Didnt throw at error getting content" + ); + } + + @Test + void parsesString() throws EntryException { + Assertions.assertEquals( + "123", + new EValProf("age", new EFake<>("age: \"123\""), null).value(), + "String not parsed" + ); + } + + @Test + void parsesInteger() throws EntryException { + Assertions.assertEquals( + Integer.MAX_VALUE, + Integer.parseInt( + new EValProf( + "age", + new EFake<>(String.format("age: %s", Integer.MAX_VALUE)), + null + ).value() + ), + "Integer not parsed" + ); + } + + @Test + void parsesLong() throws EntryException { + Assertions.assertEquals( + Long.MAX_VALUE, + Long.parseLong( + new EValProf( + "age", + new EFake<>(String.format("age: %s", Long.MAX_VALUE)), + null + ).value() + ), + "Long not parsed" + ); + } + + @Test + void parsesFloat() throws EntryException { + Assertions.assertEquals( + Float.MAX_VALUE, + Float.parseFloat( + new EValProf( + "age", + new EFake<>( + String.format("age: %s", Float.MAX_VALUE) + ), + null + ).value() + ), + "Float not parsed" + ); + } + + @Test + void parsesBoolean() throws EntryException { + Assertions.assertTrue( + Boolean.parseBoolean( + new EValProf( + "age", + new EFake<>( + "age: true" + ), + null + ).value() + ), + "Boolean not parsed" + ); + } + + @Test + void throwsAtNullValue() { + Assertions.assertThrows( + EntryException.class, + () -> new EValProf("age", new EFake<>("age: null"), null).value(), + "Didnt throw at null value" + ); + } + + @Test + void throwsAtNullAttribute() { Assertions.assertThrows( - EntryException.class, - ()-> new EValProf("profile", new EFakeErr<>().toString()).value(), - "Didnt throw at error getting content" + EntryException.class, + () -> new EValProf("age", new EFake<>("age2: null"), null).value(), + "Didnt throw at null value" + ); + } + + @Test + void parsesInnerNode() throws EntryException { + Assertions.assertEquals( + "123", + new EValProf( + "person.age", + new EFake<>( + "person:\n age: \"123\"" + ), + null + ).value(), + "Inner node not parsed" + ); + } + + @Test + void parsesArray() throws EntryException { + Assertions.assertEquals( + "123;321", + new EValProf("ages", new EFake<>("ages: [ 123, 321 ]"), null).value(), + "Array node not parsed" + ); + } + + @Test + void parsesEmptyArray() throws EntryException { + Assertions.assertEquals( + "", + new EValProf( + "ages", + new EFake<>( + "ages: []" + ), + null + ).value(), + "Empty array not parsed" + ); + } + + @Test + void parsesAnotherArray() throws EntryException { + Assertions.assertEquals( + "123;321", + new EValProf( + "ages", + new EFake<>("ages:\n - 123\n - 321"), + null + ).value(), + "Array not parsed" + ); + } + + @Test + void parsesStringWrap() throws EntryException { + Assertions.assertEquals( + " First line.\n Second line.\n", + new EVal( + "description", + new EFake<>("description: >\n First line.\n Second line.") + ).value(), + "String wrap not parsed" + ); + } + + @Test + void parsesDefaultValueAtMissingEnv() throws EntryException { + Assertions.assertEquals( + "123", + new EValProf( + "age", + new EFake<>( + "age: ${my_env:123}" + ), + null + ).value(), + "Not parsed default value" + ); + } + + @Test + void parsesDefaultValueWithDelimiterAtMissingEnv() throws EntryException { + Assertions.assertEquals( + "https://192.168.0.1:8080", + new EValProf( + "age", + new EFake<>( + "age: ${my_env:https://192.168.0.1:8080}" + ), + null + ).value(), + "Not parsed default value with delimiter" + ); + } + + @Test + void parsesEnv() throws Exception { + Assertions.assertEquals( + "321", + new EnvironmentVariables("my_env", "321").execute( + () -> new EValProf("age", new EFake<>("age: ${my_env}"), null).value() + ), + "Not parsed default value" + ); + } + + @Test + void parsesEnvOverwritesDefaultValue() throws Exception { + Assertions.assertEquals( + "111", + new EnvironmentVariables("my_env", "111").execute( + () -> new EValProf("age", new EFake<>("age: ${my_env:123}"), null).value() + ), + "Not parsed default value" ); } } diff --git a/src/test/java/io/github/artemget/entrys/profile/package-info.java b/src/test/java/io/github/artemget/entrys/profile/package-info.java new file mode 100644 index 0000000..b6f62e5 --- /dev/null +++ b/src/test/java/io/github/artemget/entrys/profile/package-info.java @@ -0,0 +1,28 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025. Artem Getmanskii + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Tests for entries that read stuff from files with support profiles. + */ +package io.github.artemget.entrys.profile; From c5e6075abf57ddefef5837f22d2906a545350079 Mon Sep 17 00:00:00 2001 From: MaxS97 Date: Fri, 18 Jul 2025 00:56:25 +0300 Subject: [PATCH 3/4] Create EYaml, tests for EValProf --- .../artemget/entrys/profile/EValProf.java | 25 +- .../io/github/artemget/entrys/yaml/EYaml.java | 160 +++++++++++++ .../artemget/entrys/yaml/package-info.java | 28 +++ .../artemget/entrys/profile/EValProfTest.java | 219 ++---------------- 4 files changed, 212 insertions(+), 220 deletions(-) create mode 100644 src/main/java/io/github/artemget/entrys/yaml/EYaml.java create mode 100644 src/main/java/io/github/artemget/entrys/yaml/package-info.java diff --git a/src/main/java/io/github/artemget/entrys/profile/EValProf.java b/src/main/java/io/github/artemget/entrys/profile/EValProf.java index 2c112a3..d0b1cb7 100644 --- a/src/main/java/io/github/artemget/entrys/profile/EValProf.java +++ b/src/main/java/io/github/artemget/entrys/profile/EValProf.java @@ -26,9 +26,9 @@ import io.github.artemget.entrys.ESafe; import io.github.artemget.entrys.Entry; -import io.github.artemget.entrys.EntryException; import io.github.artemget.entrys.file.EFile; -import io.github.artemget.entrys.file.EVal; +import io.github.artemget.entrys.operation.EContains; +import io.github.artemget.entrys.yaml.EYaml; /** * Configuration properties entry. @@ -63,22 +63,17 @@ public EValProf(final String key, final String path) { * * @param key Of Entry * @param content Yaml Content - * @param path Yaml Path + * @param path String Path */ public EValProf(final String key, final Entry content, final String path) { super( () -> { - String result; - final String profile; - try { - profile = new EVal("entrys.profile", path).value(); - if (profile.isBlank()) { - throw new EntryException("Attribute for key 'profile' is empty"); - } else { - result = new EVal(key, parsePath(path, profile)).value(); - } - } catch (final EntryException exception) { - result = new EVal(key, content).value(); + final String result; + if (new EContains(new EYaml("entrys.profile", path)).value()) { + final String profile = new EYaml("entrys.profile", path).value(); + result = new EYaml(key, parsePath(path, profile)).value(); + } else { + result = new EYaml(key, content).value(); } return result; }, @@ -87,6 +82,6 @@ public EValProf(final String key, final Entry content, final String path } private static String parsePath(final String path, final String profile) { - return path.replace("application", String.format("application%s", profile)); + return path.replace("application", String.format("application-%s", profile)); } } diff --git a/src/main/java/io/github/artemget/entrys/yaml/EYaml.java b/src/main/java/io/github/artemget/entrys/yaml/EYaml.java new file mode 100644 index 0000000..c3af5f8 --- /dev/null +++ b/src/main/java/io/github/artemget/entrys/yaml/EYaml.java @@ -0,0 +1,160 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025. Artem Getmanskii + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.github.artemget.entrys.yaml; + +import com.amihaiemil.eoyaml.Node; +import com.amihaiemil.eoyaml.Yaml; +import com.amihaiemil.eoyaml.YamlMapping; +import com.amihaiemil.eoyaml.YamlNode; +import io.github.artemget.entrys.ESafe; +import io.github.artemget.entrys.Entry; +import io.github.artemget.entrys.EntryException; +import io.github.artemget.entrys.EntryExceptionUnchecked; +import io.github.artemget.entrys.file.EFile; +import io.github.artemget.entrys.operation.EContains; +import io.github.artemget.entrys.operation.EFork; +import io.github.artemget.entrys.operation.ESplit; +import io.github.artemget.entrys.operation.EUnwrap; +import io.github.artemget.entrys.system.EEnv; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Configuration properties entry. + * By default gets entries from "src/main/resources/application.yaml" + * Supports all yaml types, default values and envs passed via ${ENV}. + * + * @since 0.4.0 + */ +public class EYaml extends ESafe { + + /** + * From yaml file at default dir. + * + * @param key Of Entry + */ + public EYaml(final String key) { + this(key, "src/main/resources/application.yaml"); + } + + /** + * From yaml file. + * + * @param key Of Entry + * @param path To Yaml File + */ + public EYaml(final String key, final String path) { + this(key, new EFile(path)); + } + + /** + * Main ctor. + * + * @param key Of Entry + * @param content Yaml Content + */ + public EYaml(final String key, final Entry content) { + super( + () -> { + YamlMapping root = fileToYaml(content, key); + String res = null; + final List elements = new ESplit(() -> key, ".").value(); + for (int element = 0; element < elements.size(); ++element) { + if (element == elements.size() - 1) { + final YamlNode value = root.value(elements.get(element)); + if (value.isEmpty()) { + throw new EntryExceptionUnchecked( + String.format("Empty value for key: %s", key) + ); + } + if (Node.SCALAR == value.type()) { + res = EYaml.ejected(value); + } else if (Node.SEQUENCE == value.type()) { + res = value.asSequence() + .children().stream() + .map(node -> node.asScalar().value()) + .collect(Collectors.joining(";")); + } + } + root = root.yamlMapping(elements.get(element)); + } + return res; + }, + () -> String.format("Attribute for key '%s' is null", key) + ); + } + + private static YamlMapping fileToYaml(final Entry content, final String key) + throws EntryException { + final YamlMapping root; + try { + root = Yaml.createYamlInput(content.value()) + .readYamlMapping(); + } catch (final IOException | EntryException exception) { + throw new EntryException( + String.format("Failed to read yaml mapping for key: '%s'", key), + exception + ); + } + return root; + } + + private static String ejected(final YamlNode node) throws EntryException { + final String scalar = node.asScalar().value(); + return new EFork<>( + () -> scalar.startsWith("${") && scalar.endsWith("}"), + new EFork<>( + () -> new ESplit(() -> scalar, ":").value().size() >= 2, + new EFork<>( + new EContains(new EEnv(() -> EYaml.selectedEnv(scalar).trim())), + new EEnv(() -> EYaml.selectedEnv(scalar).trim()), + () -> EYaml.joined(scalar) + ), + new EEnv(new EUnwrap(scalar, "${", "}")) + ), + () -> scalar + ).value(); + } + + private static String selectedEnv(final String scalar) throws EntryException { + return new ESplit(new EUnwrap(scalar, "${", "}"), ":") + .value().get(0); + } + + private static String joined(final String scalar) throws EntryException { + final List split = new ESplit( + new EUnwrap(scalar, "${", "}"), ":" + ).value(); + final StringBuilder value = new StringBuilder(); + for (int index = 1; index < split.size(); ++index) { + value.append(split.get(index)); + if (index < split.size() - 1) { + value.append(':'); + } + } + return value.toString(); + } +} diff --git a/src/main/java/io/github/artemget/entrys/yaml/package-info.java b/src/main/java/io/github/artemget/entrys/yaml/package-info.java new file mode 100644 index 0000000..ea1266f --- /dev/null +++ b/src/main/java/io/github/artemget/entrys/yaml/package-info.java @@ -0,0 +1,28 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025. Artem Getmanskii + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Yaml operation directory. + */ +package io.github.artemget.entrys.yaml; diff --git a/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java b/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java index 856aad5..5ae7121 100644 --- a/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java +++ b/src/test/java/io/github/artemget/entrys/profile/EValProfTest.java @@ -25,12 +25,12 @@ package io.github.artemget.entrys.profile; import io.github.artemget.entrys.EntryException; +import io.github.artemget.entrys.EntryExceptionUnchecked; import io.github.artemget.entrys.fake.EFake; import io.github.artemget.entrys.fake.EFakeErr; import io.github.artemget.entrys.file.EVal; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; /** * Test cases for {@link io.github.artemget.entrys.profile.EValProf}. @@ -43,224 +43,33 @@ final class EValProfTest { @Test void throwsAtProfileIsBlank() { Assertions.assertThrows( - EntryException.class, - () -> new EValProf("entrys.profile", new EFakeErr<>(), null).value(), + EntryExceptionUnchecked.class, + () -> new EValProf( + "entrys.profile", new EFake<>("entrys:\n profile: \"\""), + null + ).value(), "Profile node is blank" ); } @Test - void throwsAtEmptyContent() { - Assertions.assertThrows( - EntryException.class, - () -> new EValProf("abc", new EFakeErr<>(), null).value(), - "Didnt throw at error getting content" - ); - } - - @Test - void parsesString() throws EntryException { - Assertions.assertEquals( - "123", - new EValProf("age", new EFake<>("age: \"123\""), null).value(), - "String not parsed" - ); - } - - @Test - void parsesInteger() throws EntryException { - Assertions.assertEquals( - Integer.MAX_VALUE, - Integer.parseInt( - new EValProf( - "age", - new EFake<>(String.format("age: %s", Integer.MAX_VALUE)), - null - ).value() - ), - "Integer not parsed" - ); - } - - @Test - void parsesLong() throws EntryException { - Assertions.assertEquals( - Long.MAX_VALUE, - Long.parseLong( - new EValProf( - "age", - new EFake<>(String.format("age: %s", Long.MAX_VALUE)), - null - ).value() - ), - "Long not parsed" - ); - } - - @Test - void parsesFloat() throws EntryException { - Assertions.assertEquals( - Float.MAX_VALUE, - Float.parseFloat( - new EValProf( - "age", - new EFake<>( - String.format("age: %s", Float.MAX_VALUE) - ), - null - ).value() - ), - "Float not parsed" - ); - } - - @Test - void parsesBoolean() throws EntryException { - Assertions.assertTrue( - Boolean.parseBoolean( - new EValProf( - "age", - new EFake<>( - "age: true" - ), - null - ).value() - ), - "Boolean not parsed" - ); - } - - @Test - void throwsAtNullValue() { + void throwsAtProfileIsNull() { Assertions.assertThrows( EntryException.class, - () -> new EValProf("age", new EFake<>("age: null"), null).value(), - "Didnt throw at null value" - ); - } - - @Test - void throwsAtNullAttribute() { - Assertions.assertThrows( - EntryException.class, - () -> new EValProf("age", new EFake<>("age2: null"), null).value(), - "Didnt throw at null value" - ); - } - - @Test - void parsesInnerNode() throws EntryException { - Assertions.assertEquals( - "123", - new EValProf( - "person.age", - new EFake<>( - "person:\n age: \"123\"" - ), - null - ).value(), - "Inner node not parsed" - ); - } - - @Test - void parsesArray() throws EntryException { - Assertions.assertEquals( - "123;321", - new EValProf("ages", new EFake<>("ages: [ 123, 321 ]"), null).value(), - "Array node not parsed" - ); - } - - @Test - void parsesEmptyArray() throws EntryException { - Assertions.assertEquals( - "", - new EValProf( - "ages", - new EFake<>( - "ages: []" - ), - null - ).value(), - "Empty array not parsed" - ); - } - - @Test - void parsesAnotherArray() throws EntryException { - Assertions.assertEquals( - "123;321", - new EValProf( - "ages", - new EFake<>("ages:\n - 123\n - 321"), - null - ).value(), - "Array not parsed" + () -> new EValProf("entrys.profile", new EFakeErr<>(), null).value(), + "Profile node is null" ); } @Test - void parsesStringWrap() throws EntryException { + void getValueIfProfileIsNull() throws EntryException { Assertions.assertEquals( - " First line.\n Second line.\n", + "8080", new EVal( - "description", - new EFake<>("description: >\n First line.\n Second line.") + "port", + new EFake<>("port: 8080") ).value(), - "String wrap not parsed" - ); - } - - @Test - void parsesDefaultValueAtMissingEnv() throws EntryException { - Assertions.assertEquals( - "123", - new EValProf( - "age", - new EFake<>( - "age: ${my_env:123}" - ), - null - ).value(), - "Not parsed default value" - ); - } - - @Test - void parsesDefaultValueWithDelimiterAtMissingEnv() throws EntryException { - Assertions.assertEquals( - "https://192.168.0.1:8080", - new EValProf( - "age", - new EFake<>( - "age: ${my_env:https://192.168.0.1:8080}" - ), - null - ).value(), - "Not parsed default value with delimiter" - ); - } - - @Test - void parsesEnv() throws Exception { - Assertions.assertEquals( - "321", - new EnvironmentVariables("my_env", "321").execute( - () -> new EValProf("age", new EFake<>("age: ${my_env}"), null).value() - ), - "Not parsed default value" - ); - } - - @Test - void parsesEnvOverwritesDefaultValue() throws Exception { - Assertions.assertEquals( - "111", - new EnvironmentVariables("my_env", "111").execute( - () -> new EValProf("age", new EFake<>("age: ${my_env:123}"), null).value() - ), - "Not parsed default value" + "Profile node is null" ); } } From 57383bb0ee08150881e9d3540bb72218eca0cbaa Mon Sep 17 00:00:00 2001 From: MaxS97 Date: Sun, 20 Jul 2025 14:57:15 +0300 Subject: [PATCH 4/4] Replace logic from EVal --- .../io/github/artemget/entrys/file/EVal.java | 80 +------------------ .../github/artemget/entrys/file/EValTest.java | 11 +-- 2 files changed, 8 insertions(+), 83 deletions(-) diff --git a/src/main/java/io/github/artemget/entrys/file/EVal.java b/src/main/java/io/github/artemget/entrys/file/EVal.java index 4214455..878ac1a 100644 --- a/src/main/java/io/github/artemget/entrys/file/EVal.java +++ b/src/main/java/io/github/artemget/entrys/file/EVal.java @@ -24,21 +24,9 @@ package io.github.artemget.entrys.file; -import com.amihaiemil.eoyaml.Node; -import com.amihaiemil.eoyaml.Yaml; -import com.amihaiemil.eoyaml.YamlMapping; -import com.amihaiemil.eoyaml.YamlNode; import io.github.artemget.entrys.ESafe; import io.github.artemget.entrys.Entry; -import io.github.artemget.entrys.EntryException; -import io.github.artemget.entrys.operation.EContains; -import io.github.artemget.entrys.operation.EFork; -import io.github.artemget.entrys.operation.ESplit; -import io.github.artemget.entrys.operation.EUnwrap; -import io.github.artemget.entrys.system.EEnv; -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; +import io.github.artemget.entrys.yaml.EYaml; /** * Configuration properties entry. @@ -77,72 +65,8 @@ public EVal(final String key, final String path) { @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors") public EVal(final String key, final Entry content) { super( - () -> { - YamlMapping root; - try { - root = Yaml.createYamlInput(content.value()) - .readYamlMapping(); - } catch (final IOException | EntryException exception) { - throw new EntryException( - String.format("Failed to read yaml mapping for key: '%s'", key), - exception - ); - } - String res = null; - final List elements = new ESplit(() -> key, ".").value(); - for (int element = 0; element < elements.size(); ++element) { - if (element == elements.size() - 1) { - final YamlNode value = root.value(elements.get(element)); - if (Node.SCALAR == value.type()) { - res = EVal.ejected(value); - } else if (Node.SEQUENCE == value.type()) { - res = value.asSequence() - .children().stream() - .map(node -> node.asScalar().value()) - .collect(Collectors.joining(";")); - } - } - root = root.yamlMapping(elements.get(element)); - } - return res; - }, + () -> new EYaml(key, content).value(), () -> String.format("Attribute for key '%s' is null", key) ); } - - private static String ejected(final YamlNode node) throws EntryException { - final String scalar = node.asScalar().value(); - return new EFork<>( - () -> scalar.startsWith("${") && scalar.endsWith("}"), - new EFork<>( - () -> new ESplit(() -> scalar, ":").value().size() >= 2, - new EFork<>( - new EContains(new EEnv(() -> EVal.selectedEnv(scalar).trim())), - new EEnv(() -> EVal.selectedEnv(scalar).trim()), - () -> EVal.joined(scalar) - ), - new EEnv(new EUnwrap(scalar, "${", "}")) - ), - () -> scalar - ).value(); - } - - private static String selectedEnv(final String scalar) throws EntryException { - return new ESplit(new EUnwrap(scalar, "${", "}"), ":") - .value().get(0); - } - - private static String joined(final String scalar) throws EntryException { - final List split = new ESplit( - new EUnwrap(scalar, "${", "}"), ":" - ).value(); - final StringBuilder value = new StringBuilder(); - for (int index = 1; index < split.size(); ++index) { - value.append(split.get(index)); - if (index < split.size() - 1) { - value.append(':'); - } - } - return value.toString(); - } } diff --git a/src/test/java/io/github/artemget/entrys/file/EValTest.java b/src/test/java/io/github/artemget/entrys/file/EValTest.java index b88cb51..7ead671 100644 --- a/src/test/java/io/github/artemget/entrys/file/EValTest.java +++ b/src/test/java/io/github/artemget/entrys/file/EValTest.java @@ -25,6 +25,7 @@ package io.github.artemget.entrys.file; import io.github.artemget.entrys.EntryException; +import io.github.artemget.entrys.EntryExceptionUnchecked; import io.github.artemget.entrys.fake.EFake; import io.github.artemget.entrys.fake.EFakeErr; import org.junit.jupiter.api.Assertions; @@ -101,7 +102,7 @@ void parsesBoolean() throws EntryException { @Test void throwsAtNullValue() { Assertions.assertThrows( - EntryException.class, + EntryExceptionUnchecked.class, () -> new EVal("age", new EFake<>("age: null")).value(), "Didnt throw at null value" ); @@ -139,9 +140,9 @@ void parsesArray() throws EntryException { @Test void parsesEmptyArray() throws EntryException { - Assertions.assertEquals( - "", - new EVal( + Assertions.assertThrows( + EntryExceptionUnchecked.class, + () -> new EVal( "ages", new EFake<>( "ages: []" @@ -166,7 +167,7 @@ void parsesAnotherArray() throws EntryException { @Test void parsesStringWrap() throws EntryException { Assertions.assertEquals( - " First line.\n Second line.\n", + " First line.\n Second line.\n", new EVal( "description", new EFake<>("description: >\n First line.\n Second line.")