mirror of
https://github.com/PaperMC/Velocity.git
synced 2026-06-21 09:47:44 +02:00
feat(config): map VelocityConfiguration to YAML via Configurate ObjectMapper
Wire up the Configurate ObjectMapper path for the new velocity.yml format: - Make VelocityConfiguration ObjectMapper-friendly: no-arg constructor, non-final nested fields, transient on non-config fields (forwardingSecret, motdAsComponent, favicon), and drop the dead gson @Expose annotations. - Annotate Advanced/Query/Metrics @ConfigSerializable and add @Setting for the keys the lower-case-dashed naming scheme can't derive (kick-existing-players, packet-limiter, haproxy-protocol, accepts-transfers, query enabled/port/map). - Add ConfigurationLoader with a LOWER_CASE_DASHED ObjectMapper factory, a YAML loader builder, load/save helpers, and custom TypeSerializers for the dynamic sections that don't fit object mapping: Servers (entries + try), ForcedHosts, and PacketLimiterConfig (renamed keys). - Add ConfigurationLoaderTest: loads the bundled default, and round-trips a config with non-default values for every renamed/custom-mapped key so a wrong mapping can't silently fall back to an identical default. Part of the velocity.toml -> velocity.yml Configurate migration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,30 +17,209 @@
|
|||||||
|
|
||||||
package com.velocitypowered.proxy.config;
|
package com.velocitypowered.proxy.config;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.spongepowered.configurate.CommentedConfigurationNode;
|
||||||
|
import org.spongepowered.configurate.ConfigurationNode;
|
||||||
|
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||||
|
import org.spongepowered.configurate.serialize.SerializationException;
|
||||||
|
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||||
|
import org.spongepowered.configurate.util.NamingSchemes;
|
||||||
|
import org.spongepowered.configurate.yaml.NodeStyle;
|
||||||
|
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Velocity Configurate Loader entry utils.
|
* Velocity Configurate (YAML) loader entry utils.
|
||||||
*/
|
*/
|
||||||
public class ConfigurationLoader {
|
public final class ConfigurationLoader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ObjectMapper factory configured to map {@code camelCase} fields onto {@code lower-case-dashed}
|
||||||
|
* configuration keys, matching the historical TOML key style.
|
||||||
|
*/
|
||||||
|
private static final ObjectMapper.Factory OBJECT_MAPPER_FACTORY = ObjectMapper.factoryBuilder()
|
||||||
|
.defaultNamingScheme(NamingSchemes.LOWER_CASE_DASHED)
|
||||||
|
.build();
|
||||||
|
|
||||||
private ConfigurationLoader() {
|
private ConfigurationLoader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* performs legacy configuration migration if needed.
|
* Builds a YAML loader for the given {@code path}, wired with the serializers needed to map a
|
||||||
|
* {@link VelocityConfiguration}.
|
||||||
*
|
*
|
||||||
* @return {@code true} if a migration was performed, {@code false} otherwise
|
* @param path the configuration file path
|
||||||
|
* @return the configured loader builder
|
||||||
*/
|
*/
|
||||||
public static boolean migrateIfNeeded() {
|
static YamlConfigurationLoader.Builder yamlLoader(final Path path) {
|
||||||
return false;
|
return YamlConfigurationLoader.builder()
|
||||||
|
.path(path)
|
||||||
|
.nodeStyle(NodeStyle.BLOCK)
|
||||||
|
.indent(2)
|
||||||
|
.defaultOptions(opts -> opts.serializers(builder -> builder
|
||||||
|
.register(VelocityConfiguration.Servers.class, new ServersSerializer())
|
||||||
|
.register(VelocityConfiguration.ForcedHosts.class, new ForcedHostsSerializer())
|
||||||
|
.register(VelocityConfiguration.PacketLimiterConfig.class,
|
||||||
|
new PacketLimiterConfigSerializer())
|
||||||
|
.registerAnnotatedObjects(OBJECT_MAPPER_FACTORY)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* loads the velocity configuration.
|
* Reads a {@link VelocityConfiguration} from the YAML file at {@code path}.
|
||||||
*
|
*
|
||||||
* @return the loaded configuration
|
* @param path the configuration file path
|
||||||
|
* @return the deserialized configuration
|
||||||
|
* @throws IOException if the file could not be read or deserialized
|
||||||
*/
|
*/
|
||||||
public static VelocityConfiguration loadConfiguration() {
|
static VelocityConfiguration load(final Path path) throws IOException {
|
||||||
return null;
|
final CommentedConfigurationNode node = yamlLoader(path).build().load();
|
||||||
|
final VelocityConfiguration config = node.get(VelocityConfiguration.class);
|
||||||
|
if (config == null) {
|
||||||
|
throw new IOException("Unable to deserialize configuration from " + path);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a {@link VelocityConfiguration} to the YAML file at {@code path}.
|
||||||
|
*
|
||||||
|
* @param config the configuration to write
|
||||||
|
* @param path the configuration file path
|
||||||
|
* @throws IOException if the file could not be written
|
||||||
|
*/
|
||||||
|
static void save(final VelocityConfiguration config, final Path path) throws IOException {
|
||||||
|
final YamlConfigurationLoader loader = yamlLoader(path).build();
|
||||||
|
final CommentedConfigurationNode node = loader.createNode();
|
||||||
|
node.set(VelocityConfiguration.class, config);
|
||||||
|
loader.save(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes {@code config} onto the provided {@code node}. Exposed for migration tooling.
|
||||||
|
*
|
||||||
|
* @param config the configuration to serialize
|
||||||
|
* @param node the node to write to
|
||||||
|
* @throws SerializationException if serialization fails
|
||||||
|
*/
|
||||||
|
static void write(final VelocityConfiguration config, final ConfigurationNode node)
|
||||||
|
throws SerializationException {
|
||||||
|
node.set(VelocityConfiguration.class, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the dynamic {@code [servers]} section, where named server entries live alongside the
|
||||||
|
* {@code try} fallback order in a single node.
|
||||||
|
*/
|
||||||
|
static final class ServersSerializer implements TypeSerializer<VelocityConfiguration.Servers> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VelocityConfiguration.Servers deserialize(final Type type, final ConfigurationNode node)
|
||||||
|
throws SerializationException {
|
||||||
|
final Map<String, String> servers = new LinkedHashMap<>();
|
||||||
|
List<String> attemptConnectionOrder = ImmutableList.of("lobby");
|
||||||
|
for (final Map.Entry<Object, ? extends ConfigurationNode> entry
|
||||||
|
: node.childrenMap().entrySet()) {
|
||||||
|
final String key = entry.getKey().toString();
|
||||||
|
final ConfigurationNode value = entry.getValue();
|
||||||
|
if (key.equalsIgnoreCase("try")) {
|
||||||
|
attemptConnectionOrder = value.getList(String.class, ImmutableList.of());
|
||||||
|
} else {
|
||||||
|
final String address = value.getString();
|
||||||
|
if (address == null) {
|
||||||
|
throw new SerializationException("Server entry " + key + " is not a string!");
|
||||||
|
}
|
||||||
|
servers.put(VelocityConfiguration.Servers.cleanServerName(key), address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new VelocityConfiguration.Servers(ImmutableMap.copyOf(servers),
|
||||||
|
ImmutableList.copyOf(attemptConnectionOrder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(final Type type, final VelocityConfiguration.@Nullable Servers obj,
|
||||||
|
final ConfigurationNode node) throws SerializationException {
|
||||||
|
if (obj == null) {
|
||||||
|
node.raw(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final Map.Entry<String, String> entry : obj.getServers().entrySet()) {
|
||||||
|
node.node(entry.getKey()).set(entry.getValue());
|
||||||
|
}
|
||||||
|
node.node("try").setList(String.class, obj.getAttemptConnectionOrder());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the dynamic {@code [forced-hosts]} section (host pattern to server list map).
|
||||||
|
*/
|
||||||
|
static final class ForcedHostsSerializer
|
||||||
|
implements TypeSerializer<VelocityConfiguration.ForcedHosts> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VelocityConfiguration.ForcedHosts deserialize(final Type type,
|
||||||
|
final ConfigurationNode node) throws SerializationException {
|
||||||
|
final Map<String, List<String>> forcedHosts = new LinkedHashMap<>();
|
||||||
|
for (final Map.Entry<Object, ? extends ConfigurationNode> entry
|
||||||
|
: node.childrenMap().entrySet()) {
|
||||||
|
final String key = entry.getKey().toString().toLowerCase(Locale.ROOT);
|
||||||
|
forcedHosts.put(key,
|
||||||
|
ImmutableList.copyOf(entry.getValue().getList(String.class, ImmutableList.of())));
|
||||||
|
}
|
||||||
|
return new VelocityConfiguration.ForcedHosts(ImmutableMap.copyOf(forcedHosts));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(final Type type, final VelocityConfiguration.@Nullable ForcedHosts obj,
|
||||||
|
final ConfigurationNode node) throws SerializationException {
|
||||||
|
if (obj == null) {
|
||||||
|
node.raw(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final Map.Entry<String, List<String>> entry : obj.getForcedHosts().entrySet()) {
|
||||||
|
node.node(entry.getKey()).setList(String.class, entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the {@code [packet-limiter]} section, whose keys do not follow the field naming
|
||||||
|
* scheme (for example {@code packets-per-second} maps to {@code pps}).
|
||||||
|
*/
|
||||||
|
static final class PacketLimiterConfigSerializer
|
||||||
|
implements TypeSerializer<VelocityConfiguration.PacketLimiterConfig> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VelocityConfiguration.PacketLimiterConfig deserialize(final Type type,
|
||||||
|
final ConfigurationNode node) {
|
||||||
|
final VelocityConfiguration.PacketLimiterConfig def =
|
||||||
|
VelocityConfiguration.PacketLimiterConfig.DEFAULT;
|
||||||
|
return new VelocityConfiguration.PacketLimiterConfig(
|
||||||
|
node.node("interval").getInt(def.interval()),
|
||||||
|
node.node("packets-per-second").getInt(def.pps()),
|
||||||
|
node.node("bytes-per-second").getInt(def.bytes()),
|
||||||
|
node.node("decompressed-bytes-per-second").getInt(def.bytesAfterDecompression()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(final Type type,
|
||||||
|
final VelocityConfiguration.@Nullable PacketLimiterConfig obj, final ConfigurationNode node)
|
||||||
|
throws SerializationException {
|
||||||
|
if (obj == null) {
|
||||||
|
node.raw(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.node("interval").set(obj.interval());
|
||||||
|
node.node("packets-per-second").set(obj.pps());
|
||||||
|
node.node("bytes-per-second").set(obj.bytes());
|
||||||
|
node.node("decompressed-bytes-per-second").set(obj.bytesAfterDecompression());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import com.electronwill.nightconfig.core.CommentedConfig;
|
|||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.gson.annotations.Expose;
|
|
||||||
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
import com.velocitypowered.api.proxy.config.ProxyConfig;
|
||||||
import com.velocitypowered.api.util.Favicon;
|
import com.velocitypowered.api.util.Favicon;
|
||||||
import com.velocitypowered.proxy.util.AddressUtil;
|
import com.velocitypowered.proxy.util.AddressUtil;
|
||||||
@@ -40,6 +39,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||||||
|
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Velocity's configuration.
|
* Velocity's configuration.
|
||||||
@@ -49,50 +49,32 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class);
|
private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class);
|
||||||
|
|
||||||
@Expose
|
|
||||||
private String bind = "0.0.0.0:25565";
|
private String bind = "0.0.0.0:25565";
|
||||||
@Expose
|
|
||||||
private String motd = "<aqua>A Velocity Server";
|
private String motd = "<aqua>A Velocity Server";
|
||||||
@Expose
|
|
||||||
private int showMaxPlayers = 500;
|
private int showMaxPlayers = 500;
|
||||||
@Expose
|
|
||||||
private boolean onlineMode = true;
|
private boolean onlineMode = true;
|
||||||
@Expose
|
|
||||||
private boolean preventClientProxyConnections = false;
|
private boolean preventClientProxyConnections = false;
|
||||||
@Expose
|
|
||||||
private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE;
|
private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE;
|
||||||
private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8);
|
private transient byte[] forwardingSecret =
|
||||||
@Expose
|
generateRandomString(12).getBytes(StandardCharsets.UTF_8);
|
||||||
private boolean announceForge = false;
|
private boolean announceForge = false;
|
||||||
@Expose
|
@Setting("kick-existing-players")
|
||||||
private boolean onlineModeKickExistingPlayers = false;
|
private boolean onlineModeKickExistingPlayers = false;
|
||||||
@Expose
|
|
||||||
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||||
@Expose
|
|
||||||
private boolean samplePlayersInPing = false;
|
private boolean samplePlayersInPing = false;
|
||||||
private final Servers servers;
|
private Servers servers = new Servers();
|
||||||
private final ForcedHosts forcedHosts;
|
private ForcedHosts forcedHosts = new ForcedHosts();
|
||||||
@Expose
|
private Advanced advanced = new Advanced();
|
||||||
private final Advanced advanced;
|
private Query query = new Query();
|
||||||
@Expose
|
private Metrics metrics = new Metrics();
|
||||||
private final Query query;
|
|
||||||
private final Metrics metrics;
|
|
||||||
@Expose
|
|
||||||
private boolean enablePlayerAddressLogging = true;
|
private boolean enablePlayerAddressLogging = true;
|
||||||
private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent;
|
private transient net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent;
|
||||||
private @Nullable Favicon favicon;
|
private transient @Nullable Favicon favicon;
|
||||||
@Expose
|
|
||||||
private boolean forceKeyAuthentication = true; // Added in 1.19
|
private boolean forceKeyAuthentication = true; // Added in 1.19
|
||||||
@Expose
|
@Setting("packet-limiter")
|
||||||
private PacketLimiterConfig packetLimiterConfig = PacketLimiterConfig.DEFAULT;
|
private PacketLimiterConfig packetLimiterConfig = PacketLimiterConfig.DEFAULT;
|
||||||
|
|
||||||
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
|
VelocityConfiguration() {
|
||||||
Query query, Metrics metrics) {
|
|
||||||
this.servers = servers;
|
|
||||||
this.forcedHosts = forcedHosts;
|
|
||||||
this.advanced = advanced;
|
|
||||||
this.query = query;
|
|
||||||
this.metrics = metrics;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
|
VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
|
||||||
@@ -501,7 +483,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
this.attemptConnectionOrder = attemptConnectionOrder;
|
this.attemptConnectionOrder = attemptConnectionOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> getServers() {
|
Map<String, String> getServers() {
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,7 +535,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
this.forcedHosts = forcedHosts;
|
this.forcedHosts = forcedHosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, List<String>> getForcedHosts() {
|
Map<String, List<String>> getForcedHosts() {
|
||||||
return forcedHosts;
|
return forcedHosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,47 +551,30 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
static class Advanced {
|
static class Advanced {
|
||||||
|
|
||||||
@Expose
|
|
||||||
private int compressionThreshold = 256;
|
private int compressionThreshold = 256;
|
||||||
@Expose
|
|
||||||
private int compressionLevel = -1;
|
private int compressionLevel = -1;
|
||||||
@Expose
|
|
||||||
private int loginRatelimit = 3000;
|
private int loginRatelimit = 3000;
|
||||||
@Expose
|
|
||||||
private int connectionTimeout = 5000;
|
private int connectionTimeout = 5000;
|
||||||
@Expose
|
|
||||||
private int readTimeout = 30000;
|
private int readTimeout = 30000;
|
||||||
@Expose
|
@Setting("haproxy-protocol")
|
||||||
private boolean proxyProtocol = false;
|
private boolean proxyProtocol = false;
|
||||||
@Expose
|
|
||||||
private boolean tcpFastOpen = false;
|
private boolean tcpFastOpen = false;
|
||||||
@Expose
|
|
||||||
private boolean bungeePluginMessageChannel = true;
|
private boolean bungeePluginMessageChannel = true;
|
||||||
@Expose
|
|
||||||
private boolean showPingRequests = false;
|
private boolean showPingRequests = false;
|
||||||
@Expose
|
|
||||||
private boolean failoverOnUnexpectedServerDisconnect = true;
|
private boolean failoverOnUnexpectedServerDisconnect = true;
|
||||||
@Expose
|
|
||||||
private boolean announceProxyCommands = true;
|
private boolean announceProxyCommands = true;
|
||||||
@Expose
|
|
||||||
private boolean logCommandExecutions = false;
|
private boolean logCommandExecutions = false;
|
||||||
@Expose
|
|
||||||
private boolean logPlayerConnections = true;
|
private boolean logPlayerConnections = true;
|
||||||
@Expose
|
@Setting("accepts-transfers")
|
||||||
private boolean acceptTransfers = false;
|
private boolean acceptTransfers = false;
|
||||||
@Expose
|
|
||||||
private boolean enableReusePort = false;
|
private boolean enableReusePort = false;
|
||||||
@Expose
|
|
||||||
private int commandRateLimit = 50;
|
private int commandRateLimit = 50;
|
||||||
@Expose
|
|
||||||
private boolean forwardCommandsIfRateLimited = true;
|
private boolean forwardCommandsIfRateLimited = true;
|
||||||
@Expose
|
|
||||||
private int kickAfterRateLimitedCommands = 5;
|
private int kickAfterRateLimitedCommands = 5;
|
||||||
@Expose
|
|
||||||
private int tabCompleteRateLimit = 50;
|
private int tabCompleteRateLimit = 50;
|
||||||
@Expose
|
|
||||||
private int kickAfterRateLimitedTabCompletes = 10;
|
private int kickAfterRateLimitedTabCompletes = 10;
|
||||||
|
|
||||||
Advanced() {
|
Advanced() {
|
||||||
@@ -752,15 +717,15 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConfigSerializable
|
||||||
static class Query {
|
static class Query {
|
||||||
|
|
||||||
@Expose
|
@Setting("enabled")
|
||||||
private boolean queryEnabled = false;
|
private boolean queryEnabled = false;
|
||||||
@Expose
|
@Setting("port")
|
||||||
private int queryPort = 25565;
|
private int queryPort = 25565;
|
||||||
@Expose
|
@Setting("map")
|
||||||
private String queryMap = "Velocity";
|
private String queryMap = "Velocity";
|
||||||
@Expose
|
|
||||||
private boolean showPlugins = false;
|
private boolean showPlugins = false;
|
||||||
|
|
||||||
Query() {
|
Query() {
|
||||||
@@ -803,6 +768,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
/**
|
/**
|
||||||
* Configuration for metrics.
|
* Configuration for metrics.
|
||||||
*/
|
*/
|
||||||
|
@ConfigSerializable
|
||||||
public static class Metrics {
|
public static class Metrics {
|
||||||
|
|
||||||
private boolean enabled = true;
|
private boolean enabled = true;
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Velocity Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.proxy.config;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.velocitypowered.proxy.config.VelocityConfiguration.PacketLimiterConfig;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
class ConfigurationLoaderTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bundled default config must deserialize cleanly via the ObjectMapper and custom
|
||||||
|
* serializers, preserving the dynamic {@code servers}/{@code try} and {@code forced-hosts}
|
||||||
|
* sections.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void bundledDefaultLoads(@TempDir final Path dir) throws IOException {
|
||||||
|
final Path path = dir.resolve("velocity.yml");
|
||||||
|
try (InputStream in = ConfigurationLoaderTest.class.getClassLoader()
|
||||||
|
.getResourceAsStream("default-velocity.yml")) {
|
||||||
|
assertNotNull(in, "default-velocity.yml is missing from resources");
|
||||||
|
Files.copy(in, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
final VelocityConfiguration config = ConfigurationLoader.load(path);
|
||||||
|
|
||||||
|
assertEquals(500, config.getShowMaxPlayers());
|
||||||
|
assertEquals(ImmutableMap.of(
|
||||||
|
"lobby", "127.0.0.1:30066",
|
||||||
|
"factions", "127.0.0.1:30067",
|
||||||
|
"minigames", "127.0.0.1:30068"), config.getServers());
|
||||||
|
assertEquals(ImmutableList.of("lobby"), config.getAttemptConnectionOrder());
|
||||||
|
assertEquals(ImmutableMap.of(
|
||||||
|
"lobby.example.com", ImmutableList.of("lobby"),
|
||||||
|
"factions.example.com", ImmutableList.of("factions"),
|
||||||
|
"minigames.example.com", ImmutableList.of("minigames")), config.getForcedHosts());
|
||||||
|
assertEquals(7, config.getPacketLimiterConfig().interval());
|
||||||
|
assertEquals(5242880, config.getPacketLimiterConfig().bytesAfterDecompression());
|
||||||
|
assertTrue(config.getMetrics().isEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the non-trivial key mappings (renamed via {@code @Setting} and the custom
|
||||||
|
* serializers) using values that differ from the Java field defaults, so a wrong mapping cannot
|
||||||
|
* silently fall back to an identical default. Also exercises a save/reload round trip.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void renamedKeysRoundTrip(@TempDir final Path dir) throws IOException {
|
||||||
|
final String yaml = """
|
||||||
|
config-version: 1
|
||||||
|
bind: "0.0.0.0:25577"
|
||||||
|
show-max-players: 123
|
||||||
|
online-mode: false
|
||||||
|
kick-existing-players: true
|
||||||
|
player-info-forwarding-mode: "MODERN"
|
||||||
|
ping-passthrough: "ALL"
|
||||||
|
sample-players-in-ping: true
|
||||||
|
enable-player-address-logging: false
|
||||||
|
force-key-authentication: false
|
||||||
|
announce-forge: true
|
||||||
|
packet-limiter:
|
||||||
|
interval: 9
|
||||||
|
packets-per-second: 100
|
||||||
|
bytes-per-second: 200
|
||||||
|
decompressed-bytes-per-second: 300
|
||||||
|
servers:
|
||||||
|
alpha: "1.2.3.4:25565"
|
||||||
|
beta: "5.6.7.8:25565"
|
||||||
|
try:
|
||||||
|
- beta
|
||||||
|
- alpha
|
||||||
|
forced-hosts:
|
||||||
|
"host.example.com":
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
advanced:
|
||||||
|
haproxy-protocol: true
|
||||||
|
accepts-transfers: true
|
||||||
|
compression-threshold: 128
|
||||||
|
command-rate-limit: 99
|
||||||
|
enable-reuse-port: true
|
||||||
|
query:
|
||||||
|
enabled: true
|
||||||
|
port: 12345
|
||||||
|
map: "CustomMap"
|
||||||
|
show-plugins: true
|
||||||
|
metrics:
|
||||||
|
enabled: false
|
||||||
|
""";
|
||||||
|
final Path path = dir.resolve("velocity.yml");
|
||||||
|
Files.writeString(path, yaml, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertConfig(ConfigurationLoader.load(path));
|
||||||
|
|
||||||
|
// Round trip: save the loaded config out and read it back; everything must still match.
|
||||||
|
final Path roundTripped = dir.resolve("velocity-roundtrip.yml");
|
||||||
|
ConfigurationLoader.save(ConfigurationLoader.load(path), roundTripped);
|
||||||
|
assertConfig(ConfigurationLoader.load(roundTripped));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertConfig(final VelocityConfiguration config) {
|
||||||
|
assertEquals(123, config.getShowMaxPlayers());
|
||||||
|
assertFalse(config.isOnlineMode());
|
||||||
|
assertTrue(config.isOnlineModeKickExistingPlayers());
|
||||||
|
assertEquals(PlayerInfoForwarding.MODERN, config.getPlayerInfoForwardingMode());
|
||||||
|
assertEquals(PingPassthroughMode.ALL, config.getPingPassthrough());
|
||||||
|
assertTrue(config.getSamplePlayersInPing());
|
||||||
|
assertFalse(config.isPlayerAddressLoggingEnabled());
|
||||||
|
assertFalse(config.isForceKeyAuthentication());
|
||||||
|
assertTrue(config.isAnnounceForge());
|
||||||
|
|
||||||
|
final PacketLimiterConfig limiter = config.getPacketLimiterConfig();
|
||||||
|
assertEquals(9, limiter.interval());
|
||||||
|
assertEquals(100, limiter.pps());
|
||||||
|
assertEquals(200, limiter.bytes());
|
||||||
|
assertEquals(300, limiter.bytesAfterDecompression());
|
||||||
|
|
||||||
|
assertEquals(ImmutableMap.of(
|
||||||
|
"alpha", "1.2.3.4:25565",
|
||||||
|
"beta", "5.6.7.8:25565"), config.getServers());
|
||||||
|
assertEquals(ImmutableList.of("beta", "alpha"), config.getAttemptConnectionOrder());
|
||||||
|
assertEquals(ImmutableMap.of(
|
||||||
|
"host.example.com", ImmutableList.of("alpha", "beta")), config.getForcedHosts());
|
||||||
|
|
||||||
|
assertTrue(config.isProxyProtocol());
|
||||||
|
assertTrue(config.isAcceptTransfers());
|
||||||
|
assertEquals(128, config.getCompressionThreshold());
|
||||||
|
assertEquals(99, config.getCommandRatelimit());
|
||||||
|
assertTrue(config.isEnableReusePort());
|
||||||
|
|
||||||
|
assertTrue(config.isQueryEnabled());
|
||||||
|
assertEquals(12345, config.getQueryPort());
|
||||||
|
assertEquals("CustomMap", config.getQueryMap());
|
||||||
|
assertTrue(config.shouldQueryShowPlugins());
|
||||||
|
|
||||||
|
assertFalse(config.getMetrics().isEnabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user