Merge pull request 'Validate mod files' (#9) from TheKodeToad/better-mod-file-validation into main
All checks were successful
Publish to snapshot maven / build (push) Successful in 20s

Reviewed-on: #9
Reviewed-by: Ecorous <ecorous@outlook.com>
Reviewed-by: owlsys <owlsys@noreply.localhost>
This commit is contained in:
owlsys 2024-06-12 13:31:13 -04:00
commit b957330656

View file

@ -7,6 +7,7 @@ import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.electronwill.nightconfig.core.CommentedConfig;
@ -16,7 +17,10 @@ import com.electronwill.nightconfig.toml.TomlParser;
import dev.frogmc.frogloader.api.mod.ModDependencies;
import dev.frogmc.frogloader.api.mod.ModExtensions;
import dev.frogmc.frogloader.api.mod.ModProperties;
import dev.frogmc.frogloader.api.mod.SemVer;
import dev.frogmc.frogloader.impl.SemVerParseException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -32,7 +36,7 @@ public class ModPropertiesReader {
CommentedConfig props = PARSER.parse(fs.getPath(PROPERTIES_FILE_NAME), FileNotFoundAction.THROW_ERROR);
return Optional.of(readProperties(props, Collections.singleton(mod)));
} catch (IOException e) {
} catch (IOException | InvalidModPropertiesException e) {
LOGGER.warn("Failed to read mod properties: ", e);
}
@ -60,7 +64,7 @@ public class ModPropertiesReader {
return Optional.empty();
}
private static ModProperties readProperties(UnmodifiableConfig config, Collection<Path> sources) {
private static ModProperties readProperties(UnmodifiableConfig config, Collection<Path> sources) throws InvalidModPropertiesException {
String manifestVer = config.get("frog.format_version");
try {
@ -73,53 +77,91 @@ public class ModPropertiesReader {
@AllArgsConstructor
private enum FormatVersion {
V1_0_0("1.0.0", (config, sources) -> {
List<String> badProperties = new ArrayList<>();
String id = config.get("frog.mod.id");
String name = config.get("frog.mod.name");
String icon = config.get("frog.mod.icon");
String version = config.get("frog.mod.version");
String license = config.get("frog.mod.license");
String license = Objects.requireNonNullElse(config.get("frog.mod.license"), "");
if (license == null) {
license = "";
if (id == null || id.isEmpty())
badProperties.add("frog.mod.id");
if (name == null || name.isEmpty())
badProperties.add("frog.mod.name");
SemVer semVer = null;
if (version == null || version.isEmpty())
badProperties.add("frog.mod.version");
else {
try {
semVer = SemVerImpl.parse(version);
} catch (SemVerParseException e) {
badProperties.add("frog.mod.version");
}
}
List<UnmodifiableConfig> creditsList = config.get("frog.mod.credits");
Map<String, Collection<String>> credits = new HashMap<>();
if (creditsList != null) {
creditsList.forEach(c -> credits.put(c.get("name"), c.get("roles")));
int i = 0;
for (UnmodifiableConfig entry : creditsList) {
String entryName = entry.get("name");
Collection<String> entryRoles = entry.get("roles");
if (entryName == null || entryName.isEmpty())
badProperties.add("frog.mod.credits." + i + ".name");
if (entryRoles == null || entryRoles.isEmpty())
badProperties.add("frog.mod.credits." + i + ".name");
credits.put(entryName, entryRoles);
++i;
}
}
Collection<ModDependencies.Entry> depends = new HashSet<>();
List<UnmodifiableConfig> dependsConfig = config.get("frog.dependencies.depends");
if (dependsConfig != null) {
dependsConfig.forEach(entry -> depends.add(new ModDependencies.Entry(entry.get("id"), entry.get("versions"), entry.get("link"), entry.get("name"))));
}
Function<String, Collection<ModDependencies.Entry>> parseDependencies = key -> {
Collection<ModDependencies.Entry> result = new HashSet<>();
Collection<ModDependencies.Entry> breaks = new HashSet<>();
List<UnmodifiableConfig> breaksConfig = config.get("frog.dependencies.breaks");
if (breaksConfig != null) {
breaksConfig.forEach(entry -> breaks.add(new ModDependencies.Entry(entry.get("id"), entry.get("versions"), entry.get("link"), entry.get("name"))));
}
List<UnmodifiableConfig> dependenciesConfig = config.get("frog.dependencies." + key);
if (dependenciesConfig != null) {
int i = 0;
Collection<ModDependencies.Entry> suggests = new HashSet<>();
List<UnmodifiableConfig> suggestsConfig = config.get("frog.dependencies.suggests");
if (suggestsConfig != null) {
suggestsConfig.forEach(entry -> suggests.add(new ModDependencies.Entry(entry.get("id"), entry.get("versions"), entry.get("link"), entry.get("name"))));
}
for (UnmodifiableConfig entry : dependenciesConfig) {
String entryId = entry.get("id");
String entryVersions = entry.get("versions");
Collection<ModDependencies.Entry> provides = new HashSet<>();
List<UnmodifiableConfig> providesConfig = config.get("frog.dependencies.provides");
if (providesConfig != null) {
providesConfig.forEach(entry -> provides.add(new ModDependencies.Entry(entry.get("id"), entry.get("version"), entry.get("link"), entry.get("name"))));
}
if (entryId == null || entryId.isEmpty())
badProperties.add("frog.dependencies." + key + '.' + i + ".id");
result.add(new ModDependencies.Entry(entryId, entryVersions, entry.get("link"), entry.get("name")));
++i;
}
}
return result;
};
var depends = parseDependencies.apply("depends");
var breaks = parseDependencies.apply("breaks");
var suggests = parseDependencies.apply("suggests");
var provides = parseDependencies.apply("provides");
if (!badProperties.isEmpty())
throw new InvalidModPropertiesException(id, sources, badProperties);
UnmodifiableConfig extensionsConfig = config.get("frog.extensions");
Map<String, Object> extensions = new HashMap<>();
if (extensionsConfig != null) {
if (extensionsConfig != null)
extensionsConfig.entrySet().forEach(entry -> extensions.put(entry.getKey(), entry.getValue()));
}
return new ModPropertiesImpl(id, name, icon, SemVerImpl.parse(version), license, Collections.unmodifiableMap(credits),
return new ModPropertiesImpl(id, name, icon, semVer, license, Collections.unmodifiableMap(credits),
new ModDependencies(depends, breaks, suggests, provides), ModExtensions.of(extensions), sources);
});
@ -133,8 +175,26 @@ public class ModPropertiesReader {
@FunctionalInterface
public interface Parser {
ModProperties parse(UnmodifiableConfig config, Collection<Path> sources) throws IOException;
ModProperties parse(UnmodifiableConfig config, Collection<Path> sources) throws IOException, InvalidModPropertiesException;
}
}
@Getter
public static class InvalidModPropertiesException extends Exception {
private final Collection<String> invalid;
public InvalidModPropertiesException(String id, Collection<Path> sources, Collection<String> invalid) {
super(
"Invalid properties for %s (%s) - invalid or missing values for: %s".formatted(
Objects.requireNonNullElse(id, "<unknown>"),
sources.stream().map(Path::toString).collect(Collectors.joining(", ")),
String.join(", ", invalid)
)
);
this.invalid = invalid;
}
}
}