From 1f2703d633707204b14d69571eafb99be869c20d Mon Sep 17 00:00:00 2001 From: Elvis Souza Date: Mon, 22 Sep 2025 12:57:21 -0300 Subject: [PATCH 1/4] feat: convert environment variables to config v3 --- .../com/mageddo/dataformat/env/EnvMapper.java | 170 ++++++++++++++++++ .../dataformatv3/converter/EnvConverter.java | 25 ++- .../mageddo/dataformat/env/EnvMapperTest.java | 27 +++ .../converter/EnvConverterTest.java | 26 +++ .../templates/ConfigV3EnvTemplates.java | 44 +++++ 5 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/mageddo/dataformat/env/EnvMapper.java create mode 100644 src/test/java/com/mageddo/dataformat/env/EnvMapperTest.java create mode 100644 src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverterTest.java create mode 100644 src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3EnvTemplates.java diff --git a/src/main/java/com/mageddo/dataformat/env/EnvMapper.java b/src/main/java/com/mageddo/dataformat/env/EnvMapper.java new file mode 100644 index 000000000..9f8ddbe38 --- /dev/null +++ b/src/main/java/com/mageddo/dataformat/env/EnvMapper.java @@ -0,0 +1,170 @@ +package com.mageddo.dataformat.env; + +import com.mageddo.json.JsonUtils; +import lombok.NoArgsConstructor; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Singleton +@NoArgsConstructor(onConstructor_ = @Inject) +public class EnvMapper { + + private static final Pattern ARRAY_INDEX_PATTERN = Pattern.compile("(.+)_([0-9]+)$"); + private static final Pattern INTEGER_PATTERN = Pattern.compile("-?[0-9]+"); + + public String toJson(Map env, String varsPrefix) { + final var root = new LinkedHashMap(); + env.entrySet() + .stream() + .filter(entry -> entry.getKey() != null && entry.getKey().startsWith(varsPrefix)) + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> this.insert(root, entry.getKey().substring(varsPrefix.length()), entry.getValue())); + return JsonUtils.writeValueAsString(root); + } + + @SuppressWarnings("unchecked") + private void insert(Map root, String rawKey, String rawValue) { + final var segments = this.parseSegments(rawKey); + var current = root; + for (var index = 0; index < segments.size(); index++) { + final var segment = segments.get(index); + final var last = index == segments.size() - 1; + if (segment.hasIndex()) { + final var list = this.getOrCreateList(current, segment.name()); + this.ensureSize(list, segment.index()); + if (last) { + list.set(segment.index(), this.convertValue(rawValue)); + } else { + final var next = list.get(segment.index()); + if (next instanceof Map) { + current = (Map) next; + } else { + final var newMap = new LinkedHashMap(); + list.set(segment.index(), newMap); + current = newMap; + } + } + } else { + if (last) { + current.put(segment.name(), this.convertValue(rawValue)); + } else { + final var next = current.get(segment.name()); + if (next instanceof Map) { + current = (Map) next; + } else { + final var newMap = new LinkedHashMap(); + current.put(segment.name(), newMap); + current = newMap; + } + } + } + } + } + + private List parseSegments(String rawKey) { + final var segments = new ArrayList(); + final var keys = rawKey.split("__"); + for (final var key : keys) { + segments.add(this.parseSegment(key)); + } + return segments; + } + + private PathSegment parseSegment(String segment) { + final Matcher matcher = ARRAY_INDEX_PATTERN.matcher(segment); + if (matcher.matches()) { + final var property = this.toCamelCase(matcher.group(1)); + final var index = Integer.parseInt(matcher.group(2)); + return new PathSegment(property, index); + } + return new PathSegment(this.toCamelCase(segment), null); + } + + private List getOrCreateList(Map current, String key) { + final var existing = current.get(key); + if (existing instanceof List) { + return (List) existing; + } + final var list = new ArrayList(); + current.put(key, list); + return list; + } + + private void ensureSize(List list, int index) { + while (list.size() <= index) { + list.add(null); + } + } + + private Object convertValue(String rawValue) { + if (rawValue == null) { + return null; + } + final var value = rawValue.trim(); + if (value.isEmpty()) { + return ""; + } + if ("null".equalsIgnoreCase(value)) { + return null; + } + if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { + return Boolean.valueOf(value); + } + if (INTEGER_PATTERN.matcher(value).matches()) { + try { + return Integer.valueOf(value); + } catch (NumberFormatException e) { + return Long.valueOf(value); + } + } + return rawValue; + } + + private String toCamelCase(String value) { + final var lower = value.toLowerCase(Locale.ROOT); + final var tokens = lower.split("_"); + final var builder = new StringBuilder(); + for (var index = 0; index < tokens.length; index++) { + final var token = tokens[index]; + if (token.isEmpty()) { + continue; + } + if (index == 0) { + builder.append(token); + } else { + builder.append(Character.toUpperCase(token.charAt(0))).append(token.substring(1)); + } + } + return builder.toString(); + } + + private static final class PathSegment { + private final String name; + private final Integer index; + + PathSegment(String name, Integer index) { + this.name = name; + this.index = index; + } + + String name() { + return this.name; + } + + Integer index() { + return this.index; + } + + boolean hasIndex() { + return this.index != null; + } + } +} diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java b/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java index d1817e9c3..7b881fc66 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java +++ b/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java @@ -1,20 +1,39 @@ package com.mageddo.dnsproxyserver.config.provider.dataformatv3.converter; +import com.mageddo.dataformat.env.EnvMapper; import com.mageddo.dnsproxyserver.config.provider.dataformatv3.ConfigV3; +import lombok.RequiredArgsConstructor; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; + +@Singleton +@RequiredArgsConstructor(onConstructor_ = @Inject) public class EnvConverter implements Converter { + + private static final String PREFIX = "DPS__"; + + private final EnvMapper envMapper; + private final JsonConverter jsonConverter; + @Override public ConfigV3 parse() { - return null; + return this.parse(System.getenv()); + } + + ConfigV3 parse(Map env) { + final var json = this.envMapper.toJson(env, PREFIX); + return this.jsonConverter.parse(json); } @Override public String serialize(ConfigV3 config) { - return ""; + return this.jsonConverter.serialize(config); } @Override public int priority() { - return 0; + return 2; } } diff --git a/src/test/java/com/mageddo/dataformat/env/EnvMapperTest.java b/src/test/java/com/mageddo/dataformat/env/EnvMapperTest.java new file mode 100644 index 000000000..8078e4b7b --- /dev/null +++ b/src/test/java/com/mageddo/dataformat/env/EnvMapperTest.java @@ -0,0 +1,27 @@ +package com.mageddo.dataformat.env; + +import com.mageddo.dnsproxyserver.config.provider.dataformatv3.templates.ConfigV3EnvTemplates; +import com.mageddo.dnsproxyserver.config.provider.dataformatv3.templates.ConfigV3Templates; +import com.mageddo.json.JsonUtils; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EnvMapperTest { + + private final EnvMapper mapper = new EnvMapper(); + + @Test + void mustConvertEnvVariablesToJsonStructure() { + // Arrange + final var env = ConfigV3EnvTemplates.build(); + final var expected = JsonUtils.readTree(ConfigV3Templates.buildJson()); + + // Act + final var json = this.mapper.toJson(env, "DPS__"); + final var actual = JsonUtils.readTree(json); + + // Assert + assertEquals(expected, actual); + } +} diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverterTest.java b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverterTest.java new file mode 100644 index 000000000..d7192e4b6 --- /dev/null +++ b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverterTest.java @@ -0,0 +1,26 @@ +package com.mageddo.dnsproxyserver.config.provider.dataformatv3.converter; + +import com.mageddo.dataformat.env.EnvMapper; +import com.mageddo.dnsproxyserver.config.provider.dataformatv3.templates.ConfigV3EnvTemplates; +import com.mageddo.dnsproxyserver.config.provider.dataformatv3.templates.ConfigV3Templates; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EnvConverterTest { + + private final EnvConverter converter = new EnvConverter(new EnvMapper(), new JsonConverter()); + + @Test + void mustParseEnvironmentIntoConfig() { + // Arrange + final var env = ConfigV3EnvTemplates.build(); + final var expected = new ConfigV3Templates().build(); + + // Act + final var actual = this.converter.parse(env); + + // Assert + assertEquals(expected, actual); + } +} diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3EnvTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3EnvTemplates.java new file mode 100644 index 000000000..6d3328e56 --- /dev/null +++ b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3EnvTemplates.java @@ -0,0 +1,44 @@ +package com.mageddo.dnsproxyserver.config.provider.dataformatv3.templates; + +import java.util.LinkedHashMap; +import java.util.Map; + +public final class ConfigV3EnvTemplates { + + private ConfigV3EnvTemplates() { + } + + public static Map build() { + final var env = new LinkedHashMap(); + env.put("DPS__VERSION", "3"); + env.put("DPS__SERVER__DNS__PORT", "53"); + env.put("DPS__SERVER__DNS__NO_ENTRIES_RESPONSE_CODE", "3"); + env.put("DPS__SERVER__WEB__PORT", "5380"); + env.put("DPS__SERVER__PROTOCOL", "UDP_TCP"); + env.put("DPS__SOLVER__REMOTE__ACTIVE", "true"); + env.put("DPS__SOLVER__REMOTE__DNS_SERVERS_0", "8.8.8.8"); + env.put("DPS__SOLVER__REMOTE__DNS_SERVERS_1", "4.4.4.4:53"); + env.put("DPS__SOLVER__REMOTE__CIRCUIT_BREAKER__NAME", "STATIC_THRESHOLD"); + env.put("DPS__SOLVER__DOCKER__REGISTER_CONTAINER_NAMES", "false"); + env.put("DPS__SOLVER__DOCKER__DOMAIN", "docker"); + env.put("DPS__SOLVER__DOCKER__HOST_MACHINE_FALLBACK", "true"); + env.put("DPS__SOLVER__DOCKER__DPS_NETWORK__NAME", "dps"); + env.put("DPS__SOLVER__DOCKER__DPS_NETWORK__AUTO_CREATE", "false"); + env.put("DPS__SOLVER__DOCKER__DPS_NETWORK__AUTO_CONNECT", "false"); + env.put("DPS__SOLVER__DOCKER__DOCKER_DAEMON_URI", "null"); + env.put("DPS__SOLVER__SYSTEM__HOST_MACHINE_HOSTNAME", "host.docker"); + env.put("DPS__SOLVER__LOCAL__ACTIVE_ENV", ""); + env.put("DPS__SOLVER__LOCAL__ENVS_0__NAME", ""); + env.put("DPS__SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__TYPE", "A"); + env.put("DPS__SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__HOSTNAME", "github.com"); + env.put("DPS__SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__IP", "192.168.0.1"); + env.put("DPS__SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__TTL", "255"); + env.put("DPS__SOLVER__STUB__DOMAIN_NAME", "stub"); + env.put("DPS__DEFAULT_DNS__ACTIVE", "true"); + env.put("DPS__DEFAULT_DNS__RESOLV_CONF__PATHS", "/host/etc/systemd/resolved.conf,/host/etc/resolv.conf,/etc/systemd/resolved.conf,/etc/resolv.conf"); + env.put("DPS__DEFAULT_DNS__RESOLV_CONF__OVERRIDE_NAME_SERVERS", "true"); + env.put("DPS__LOG__LEVEL", "DEBUG"); + env.put("DPS__LOG__FILE", "console"); + return env; + } +} From 5557052d33931c81b2dd08ade5d80a405be55064 Mon Sep 17 00:00:00 2001 From: Elvis de Freitas Date: Mon, 22 Sep 2025 13:01:13 -0300 Subject: [PATCH 2/4] adjusts --- .../dataformatv3/converter/EnvConverter.java | 4 +- .../mageddo/dataformat/env/EnvMapperTest.java | 2 +- .../converter/EnvConverterTest.java | 2 +- .../templates/ConfigV3EnvTemplates.java | 58 +++++++++---------- .../templates/ConfigV3Templates.java | 2 +- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java b/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java index 7b881fc66..9aa9c9500 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java +++ b/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java @@ -12,7 +12,7 @@ @RequiredArgsConstructor(onConstructor_ = @Inject) public class EnvConverter implements Converter { - private static final String PREFIX = "DPS__"; + private static final String PREFIX = "DPS_"; private final EnvMapper envMapper; private final JsonConverter jsonConverter; @@ -29,7 +29,7 @@ ConfigV3 parse(Map env) { @Override public String serialize(ConfigV3 config) { - return this.jsonConverter.serialize(config); + throw new UnsupportedOperationException(); } @Override diff --git a/src/test/java/com/mageddo/dataformat/env/EnvMapperTest.java b/src/test/java/com/mageddo/dataformat/env/EnvMapperTest.java index 8078e4b7b..cd9676a45 100644 --- a/src/test/java/com/mageddo/dataformat/env/EnvMapperTest.java +++ b/src/test/java/com/mageddo/dataformat/env/EnvMapperTest.java @@ -18,7 +18,7 @@ void mustConvertEnvVariablesToJsonStructure() { final var expected = JsonUtils.readTree(ConfigV3Templates.buildJson()); // Act - final var json = this.mapper.toJson(env, "DPS__"); + final var json = this.mapper.toJson(env, "DPS_"); final var actual = JsonUtils.readTree(json); // Assert diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverterTest.java b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverterTest.java index d7192e4b6..29edb19bf 100644 --- a/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverterTest.java +++ b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverterTest.java @@ -14,8 +14,8 @@ class EnvConverterTest { @Test void mustParseEnvironmentIntoConfig() { // Arrange + final var expected = ConfigV3Templates.build(); final var env = ConfigV3EnvTemplates.build(); - final var expected = new ConfigV3Templates().build(); // Act final var actual = this.converter.parse(env); diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3EnvTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3EnvTemplates.java index 6d3328e56..6f6fa60c3 100644 --- a/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3EnvTemplates.java +++ b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3EnvTemplates.java @@ -10,35 +10,35 @@ private ConfigV3EnvTemplates() { public static Map build() { final var env = new LinkedHashMap(); - env.put("DPS__VERSION", "3"); - env.put("DPS__SERVER__DNS__PORT", "53"); - env.put("DPS__SERVER__DNS__NO_ENTRIES_RESPONSE_CODE", "3"); - env.put("DPS__SERVER__WEB__PORT", "5380"); - env.put("DPS__SERVER__PROTOCOL", "UDP_TCP"); - env.put("DPS__SOLVER__REMOTE__ACTIVE", "true"); - env.put("DPS__SOLVER__REMOTE__DNS_SERVERS_0", "8.8.8.8"); - env.put("DPS__SOLVER__REMOTE__DNS_SERVERS_1", "4.4.4.4:53"); - env.put("DPS__SOLVER__REMOTE__CIRCUIT_BREAKER__NAME", "STATIC_THRESHOLD"); - env.put("DPS__SOLVER__DOCKER__REGISTER_CONTAINER_NAMES", "false"); - env.put("DPS__SOLVER__DOCKER__DOMAIN", "docker"); - env.put("DPS__SOLVER__DOCKER__HOST_MACHINE_FALLBACK", "true"); - env.put("DPS__SOLVER__DOCKER__DPS_NETWORK__NAME", "dps"); - env.put("DPS__SOLVER__DOCKER__DPS_NETWORK__AUTO_CREATE", "false"); - env.put("DPS__SOLVER__DOCKER__DPS_NETWORK__AUTO_CONNECT", "false"); - env.put("DPS__SOLVER__DOCKER__DOCKER_DAEMON_URI", "null"); - env.put("DPS__SOLVER__SYSTEM__HOST_MACHINE_HOSTNAME", "host.docker"); - env.put("DPS__SOLVER__LOCAL__ACTIVE_ENV", ""); - env.put("DPS__SOLVER__LOCAL__ENVS_0__NAME", ""); - env.put("DPS__SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__TYPE", "A"); - env.put("DPS__SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__HOSTNAME", "github.com"); - env.put("DPS__SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__IP", "192.168.0.1"); - env.put("DPS__SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__TTL", "255"); - env.put("DPS__SOLVER__STUB__DOMAIN_NAME", "stub"); - env.put("DPS__DEFAULT_DNS__ACTIVE", "true"); - env.put("DPS__DEFAULT_DNS__RESOLV_CONF__PATHS", "/host/etc/systemd/resolved.conf,/host/etc/resolv.conf,/etc/systemd/resolved.conf,/etc/resolv.conf"); - env.put("DPS__DEFAULT_DNS__RESOLV_CONF__OVERRIDE_NAME_SERVERS", "true"); - env.put("DPS__LOG__LEVEL", "DEBUG"); - env.put("DPS__LOG__FILE", "console"); + env.put("DPS_VERSION", "3"); + env.put("DPS_SERVER__DNS__PORT", "53"); + env.put("DPS_SERVER__DNS__NO_ENTRIES_RESPONSE_CODE", "3"); + env.put("DPS_SERVER__WEB__PORT", "5380"); + env.put("DPS_SERVER__PROTOCOL", "UDP_TCP"); + env.put("DPS_SOLVER__REMOTE__ACTIVE", "true"); + env.put("DPS_SOLVER__REMOTE__DNS_SERVERS_0", "8.8.8.8"); + env.put("DPS_SOLVER__REMOTE__DNS_SERVERS_1", "4.4.4.4:53"); + env.put("DPS_SOLVER__REMOTE__CIRCUIT_BREAKER__NAME", "STATIC_THRESHOLD"); + env.put("DPS_SOLVER__DOCKER__REGISTER_CONTAINER_NAMES", "false"); + env.put("DPS_SOLVER__DOCKER__DOMAIN", "docker"); + env.put("DPS_SOLVER__DOCKER__HOST_MACHINE_FALLBACK", "true"); + env.put("DPS_SOLVER__DOCKER__DPS_NETWORK__NAME", "dps"); + env.put("DPS_SOLVER__DOCKER__DPS_NETWORK__AUTO_CREATE", "false"); + env.put("DPS_SOLVER__DOCKER__DPS_NETWORK__AUTO_CONNECT", "false"); + env.put("DPS_SOLVER__DOCKER__DOCKER_DAEMON_URI", "null"); + env.put("DPS_SOLVER__SYSTEM__HOST_MACHINE_HOSTNAME", "host.docker"); + env.put("DPS_SOLVER__LOCAL__ACTIVE_ENV", ""); + env.put("DPS_SOLVER__LOCAL__ENVS_0__NAME", ""); + env.put("DPS_SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__TYPE", "A"); + env.put("DPS_SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__HOSTNAME", "github.com"); + env.put("DPS_SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__IP", "192.168.0.1"); + env.put("DPS_SOLVER__LOCAL__ENVS_0__HOSTNAMES_0__TTL", "255"); + env.put("DPS_SOLVER__STUB__DOMAIN_NAME", "stub"); + env.put("DPS_DEFAULT_DNS__ACTIVE", "true"); + env.put("DPS_DEFAULT_DNS__RESOLV_CONF__PATHS", "/host/etc/systemd/resolved.conf,/host/etc/resolv.conf,/etc/systemd/resolved.conf,/etc/resolv.conf"); + env.put("DPS_DEFAULT_DNS__RESOLV_CONF__OVERRIDE_NAME_SERVERS", "true"); + env.put("DPS_LOG__LEVEL", "DEBUG"); + env.put("DPS_LOG__FILE", "console"); return env; } } diff --git a/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3Templates.java b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3Templates.java index 4472f2a76..080537ea8 100644 --- a/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3Templates.java +++ b/src/test/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/templates/ConfigV3Templates.java @@ -59,7 +59,7 @@ public static String buildYaml() { """); } - public ConfigV3 build() { + public static ConfigV3 build() { return new JsonConverter().parse(buildJson()); } From f600da9eb173b623e9fd568928c04d05ade00cc2 Mon Sep 17 00:00:00 2001 From: Elvis de Freitas Date: Mon, 22 Sep 2025 14:25:20 -0300 Subject: [PATCH 3/4] adjusts --- build.gradle | 1 + .../com/mageddo/dataformat/env/EnvMapper.java | 218 ++++++++++-------- .../mageddo/dnsproxyserver/utils/Numbers.java | 4 + 3 files changed, 125 insertions(+), 98 deletions(-) diff --git a/build.gradle b/build.gradle index 1c8ad9150..2f61695f5 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,7 @@ dependencies { implementation('jakarta.ws.rs:jakarta.ws.rs-api:2.1.6') implementation('com.mageddo.commons:commons-lang:0.1.21') implementation('org.apache.commons:commons-exec:1.3') + implementation("org.apache.commons:commons-text:1.14.0") implementation('ch.qos.logback:logback-classic:1.5.6') implementation('net.java.dev.jna:jna:5.13.0') diff --git a/src/main/java/com/mageddo/dataformat/env/EnvMapper.java b/src/main/java/com/mageddo/dataformat/env/EnvMapper.java index 9f8ddbe38..0c98daac0 100644 --- a/src/main/java/com/mageddo/dataformat/env/EnvMapper.java +++ b/src/main/java/com/mageddo/dataformat/env/EnvMapper.java @@ -1,113 +1,163 @@ package com.mageddo.dataformat.env; +import com.mageddo.dnsproxyserver.utils.Numbers; import com.mageddo.json.JsonUtils; import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.CaseUtils; import javax.inject.Inject; import javax.inject.Singleton; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; @Singleton @NoArgsConstructor(onConstructor_ = @Inject) public class EnvMapper { + private static final String SEGMENT_SEPARATOR = "__"; private static final Pattern ARRAY_INDEX_PATTERN = Pattern.compile("(.+)_([0-9]+)$"); private static final Pattern INTEGER_PATTERN = Pattern.compile("-?[0-9]+"); - public String toJson(Map env, String varsPrefix) { + private final SegmentMapper segmentMapper = new SegmentMapper(); + + public String toJson(final Map env, final String varsPrefix) { final var root = new LinkedHashMap(); - env.entrySet() - .stream() - .filter(entry -> entry.getKey() != null && entry.getKey().startsWith(varsPrefix)) - .sorted(Map.Entry.comparingByKey()) - .forEach(entry -> this.insert(root, entry.getKey().substring(varsPrefix.length()), entry.getValue())); + + this.findMatchingEnvs(env, varsPrefix) + .forEach(e -> insertPropertyAt(root, e, varsPrefix)); + return JsonUtils.writeValueAsString(root); } + private void insertPropertyAt( + Map root, + Map.Entry entry, + String varsPrefix + ) { + this.insert(root, buildEnvWithoutPrefix(entry.getKey(), varsPrefix), entry.getValue()); + } + + private static String buildEnvWithoutPrefix(final String key, String prefix) { + return key.substring(prefix.length()); + } + + private Stream> findMatchingEnvs(Map env, String varsPrefix) { + return env.entrySet() + .stream() + .filter(e -> e.getKey() != null && e.getKey() + .startsWith(varsPrefix)) + .sorted(Map.Entry.comparingByKey()); + } + @SuppressWarnings("unchecked") - private void insert(Map root, String rawKey, String rawValue) { - final var segments = this.parseSegments(rawKey); + private void insert(final Map root, final String rawKey, final String rawValue) { + + final var segments = this.segmentMapper.ofRawKey(rawKey); var current = root; - for (var index = 0; index < segments.size(); index++) { - final var segment = segments.get(index); - final var last = index == segments.size() - 1; - if (segment.hasIndex()) { - final var list = this.getOrCreateList(current, segment.name()); - this.ensureSize(list, segment.index()); - if (last) { - list.set(segment.index(), this.convertValue(rawValue)); - } else { - final var next = list.get(segment.index()); - if (next instanceof Map) { - current = (Map) next; - } else { - final var newMap = new LinkedHashMap(); - list.set(segment.index(), newMap); - current = newMap; - } + + for (var i = 0; i < segments.size(); i++) { + final var seg = segments.get(i); + final var isLast = (i == segments.size() - 1); + + if (seg.hasIndex()) { + final var list = getOrCreateList(current, seg.name()); + ensureSize(list, seg.index()); + + if (isLast) { + list.set(seg.index(), convertValue(rawValue)); + return; } - } else { - if (last) { - current.put(segment.name(), this.convertValue(rawValue)); + + final var next = list.get(seg.index()); + if (next instanceof Map) { + current = (Map) next; } else { - final var next = current.get(segment.name()); - if (next instanceof Map) { - current = (Map) next; - } else { - final var newMap = new LinkedHashMap(); - current.put(segment.name(), newMap); - current = newMap; - } + final var newMap = new LinkedHashMap(); + list.set(seg.index(), newMap); + current = newMap; } + continue; + } + + if (isLast) { + current.put(seg.name(), convertValue(rawValue)); + return; + } + + final var next = current.get(seg.name()); + if (next instanceof Map) { + current = (Map) next; + } else { + final var newMap = new LinkedHashMap(); + current.put(seg.name(), newMap); + current = newMap; } } } - private List parseSegments(String rawKey) { - final var segments = new ArrayList(); - final var keys = rawKey.split("__"); - for (final var key : keys) { - segments.add(this.parseSegment(key)); + private static class SegmentMapper { + + private List ofRawKey(final String rawKey) { + final var segments = new ArrayList(); + for (final var token : rawKey.split(SEGMENT_SEPARATOR)) { + segments.add(this.parseSegment(token)); + } + return segments; } - return segments; - } - private PathSegment parseSegment(String segment) { - final Matcher matcher = ARRAY_INDEX_PATTERN.matcher(segment); - if (matcher.matches()) { - final var property = this.toCamelCase(matcher.group(1)); - final var index = Integer.parseInt(matcher.group(2)); - return new PathSegment(property, index); + private PathSegment parseSegment(final String segment) { + final var m = ARRAY_INDEX_PATTERN.matcher(segment); + if (m.matches()) { + final var name = this.toCamelCase(m.group(1)); + final var index = Integer.parseInt(m.group(2)); + return new PathSegment(name, index); + } + return new PathSegment(this.toCamelCase(segment), null); + } + + private String toCamelCase(String value) { + return CaseUtils.toCamelCase(StringUtils.lowerCase(value), false, '_'); + } + + /** + * Ex.: "servers_0" => name=servers, index=0 + * "solver__remote__dnsServers_1" é segmentado por "__" + */ + record PathSegment(String name, Integer index) { + boolean hasIndex() { + return this.index != null; + } } - return new PathSegment(this.toCamelCase(segment), null); } - private List getOrCreateList(Map current, String key) { + + @SuppressWarnings("unchecked") + private List getOrCreateList(final Map current, final String key) { final var existing = current.get(key); if (existing instanceof List) { return (List) existing; } - final var list = new ArrayList(); + final var list = new ArrayList<>(); current.put(key, list); return list; } - private void ensureSize(List list, int index) { + private void ensureSize(final List list, final int index) { while (list.size() <= index) { list.add(null); } } - private Object convertValue(String rawValue) { + private Object convertValue(final String rawValue) { if (rawValue == null) { return null; } + final var value = rawValue.trim(); if (value.isEmpty()) { return ""; @@ -118,53 +168,25 @@ private Object convertValue(String rawValue) { if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { return Boolean.valueOf(value); } - if (INTEGER_PATTERN.matcher(value).matches()) { + + if (isInteger(value)) { try { - return Integer.valueOf(value); - } catch (NumberFormatException e) { - return Long.valueOf(value); + final var asLong = Long.parseLong(value); + if (Numbers.canBeInt(asLong)) { + return (int) asLong; + } + return asLong; + } catch (NumberFormatException ignore) { + // não deve ocorrer por causa do regex, mas mantemos seguro } } - return rawValue; + return value; } - private String toCamelCase(String value) { - final var lower = value.toLowerCase(Locale.ROOT); - final var tokens = lower.split("_"); - final var builder = new StringBuilder(); - for (var index = 0; index < tokens.length; index++) { - final var token = tokens[index]; - if (token.isEmpty()) { - continue; - } - if (index == 0) { - builder.append(token); - } else { - builder.append(Character.toUpperCase(token.charAt(0))).append(token.substring(1)); - } - } - return builder.toString(); + private static boolean isInteger(String value) { + return INTEGER_PATTERN + .matcher(value) + .matches(); } - private static final class PathSegment { - private final String name; - private final Integer index; - - PathSegment(String name, Integer index) { - this.name = name; - this.index = index; - } - - String name() { - return this.name; - } - - Integer index() { - return this.index; - } - - boolean hasIndex() { - return this.index != null; - } - } } diff --git a/src/main/java/com/mageddo/dnsproxyserver/utils/Numbers.java b/src/main/java/com/mageddo/dnsproxyserver/utils/Numbers.java index a8d874646..3ebfe04ed 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/utils/Numbers.java +++ b/src/main/java/com/mageddo/dnsproxyserver/utils/Numbers.java @@ -29,4 +29,8 @@ public static Integer firstPositive(Integer... arr) { } return null; } + + public static boolean canBeInt(long asLong) { + return asLong >= Integer.MIN_VALUE && asLong <= Integer.MAX_VALUE; + } } From c38fce704894d531844f1da2662c7cb61e377586 Mon Sep 17 00:00:00 2001 From: Elvis de Freitas Date: Mon, 22 Sep 2025 14:47:24 -0300 Subject: [PATCH 4/4] adjusts --- .../config/provider/dataformatv3/converter/EnvConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java b/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java index 9aa9c9500..4be948d98 100644 --- a/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java +++ b/src/main/java/com/mageddo/dnsproxyserver/config/provider/dataformatv3/converter/EnvConverter.java @@ -34,6 +34,6 @@ public String serialize(ConfigV3 config) { @Override public int priority() { - return 2; + return 0; } }