From a23a5d6dea2b8a7785008449a2320afd0e93b800 Mon Sep 17 00:00:00 2001 From: Geoff Bourne Date: Fri, 8 Nov 2024 18:58:32 -0600 Subject: [PATCH] modrinth: more info in error when dependency has no applicable files (#489) --- .../java/me/itzg/helpers/McImageHelper.java | 2 + .../helpers/modrinth/ModrinthCommand.java | 46 +++++++--- .../helpers/modrinth/model/ProjectType.java | 5 +- .../helpers/singles/TestLoggingCommand.java | 21 +++++ .../helpers/modrinth/ModrinthCommandTest.java | 83 +++++++++++++++++-- 5 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 src/main/java/me/itzg/helpers/singles/TestLoggingCommand.java diff --git a/src/main/java/me/itzg/helpers/McImageHelper.java b/src/main/java/me/itzg/helpers/McImageHelper.java index 9a0c67a4..59f12140 100644 --- a/src/main/java/me/itzg/helpers/McImageHelper.java +++ b/src/main/java/me/itzg/helpers/McImageHelper.java @@ -33,6 +33,7 @@ import me.itzg.helpers.singles.Asciify; import me.itzg.helpers.singles.HashCommand; import me.itzg.helpers.singles.NetworkInterfacesCommand; +import me.itzg.helpers.singles.TestLoggingCommand; import me.itzg.helpers.singles.YamlPathCmd; import me.itzg.helpers.sync.InterpolateCommand; import me.itzg.helpers.sync.MulitCopyCommand; @@ -82,6 +83,7 @@ SetPropertiesCommand.class, Sync.class, SyncAndInterpolate.class, + TestLoggingCommand.class, YamlPathCmd.class, VanillaTweaksCommand.class, } diff --git a/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java b/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java index 6d6c252b..87a646d1 100644 --- a/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java +++ b/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java @@ -63,6 +63,12 @@ public class ModrinthCommand implements Callable { description = "Default is ${DEFAULT-VALUE}\nValid values: ${COMPLETION-CANDIDATES}") DownloadDependencies downloadDependencies; + @Option(names = "--skip-existing", defaultValue = "${env:MODRINTH_SKIP_EXISTING}") + boolean skipExisting = true; + + @Option(names = "--skip-up-to-date", defaultValue = "${env:MODRINTH_SKIP_UP_TO_DATE}") + boolean skipUpToDate = true; + public enum DownloadDependencies { NONE, REQUIRED, @@ -151,30 +157,43 @@ private ModrinthManifest loadManifest() throws IOException { return Manifests.load(outputDirectory, ModrinthManifest.ID, ModrinthManifest.class); } - private Stream expandDependencies(ModrinthApiClient modrinthApiClient, Version version) { + private Stream expandDependencies(ModrinthApiClient modrinthApiClient, Project project, Version version) { log.debug("Expanding dependencies of version={}", version); return version.getDependencies().stream() .filter(this::filterDependency) .filter(dep -> projectsProcessed.add(dep.getProjectId())) .flatMap(dep -> { projectsProcessed.add(dep.getProjectId()); + final Version depVersion; - if (dep.getVersionId() == null) { - log.debug("Fetching versions of dep={} and picking", dep); - depVersion = pickVersion( - getVersionsForProject(modrinthApiClient, dep.getProjectId()) + try { + if (dep.getVersionId() == null) { + log.debug("Fetching versions of dep={} and picking", dep); + depVersion = pickVersion( + getVersionsForProject(modrinthApiClient, dep.getProjectId()) + ); + } + else { + log.debug("Fetching version for dep={}", dep); + depVersion = modrinthApiClient.getVersionFromId(dep.getVersionId()) + .block(); + } + } catch (GenericException e) { + throw new GenericException(String.format("Failed to expand %s of project '%s'", + dep, project.getTitle()), e); + } catch (NoFilesAvailableException e) { + throw new InvalidParameterException( + String.format("No matching files for %s of project '%s': %s", + dep, project.getTitle(), e.getMessage() + ), e ); } - else { - log.debug("Fetching version for dep={}", dep); - depVersion = modrinthApiClient.getVersionFromId(dep.getVersionId()) - .block(); - } + if (depVersion != null) { log.debug("Resolved version={} for dep={}", depVersion.getVersionNumber(), dep); return Stream.concat( Stream.of(depVersion), - expandDependencies(modrinthApiClient, depVersion) + expandDependencies(modrinthApiClient, project, depVersion) ) .peek(expandedVer -> log.debug("Expanded dependency={} into version={}", dep, expandedVer)); } @@ -243,7 +262,8 @@ private Path download(boolean isDatapack, VersionFile versionFile) { return fetch(URI.create(versionFile.getUrl())) .userAgentCommand("modrinth") .toFile(outPath) - .skipUpToDate(true) + .skipExisting(skipExisting) + .skipUpToDate(skipUpToDate) .handleStatus(Fetch.loggingDownloadStatusHandler(log)) .execute(); } catch (IOException e) { @@ -293,7 +313,7 @@ private Stream processProject(ModrinthApiClient modrinthApiClient, Project return Stream.concat( Stream.of(version), - expandDependencies(modrinthApiClient, version) + expandDependencies(modrinthApiClient, project, version) ) .map(ModrinthApiClient::pickVersionFile) .map(versionFile -> download(isDatapack, versionFile)) diff --git a/src/main/java/me/itzg/helpers/modrinth/model/ProjectType.java b/src/main/java/me/itzg/helpers/modrinth/model/ProjectType.java index b15d6454..dc35e39f 100644 --- a/src/main/java/me/itzg/helpers/modrinth/model/ProjectType.java +++ b/src/main/java/me/itzg/helpers/modrinth/model/ProjectType.java @@ -1,6 +1,7 @@ package me.itzg.helpers.modrinth.model; public enum ProjectType { - mod, - modpack + mod, + modpack, + resourcepack, } diff --git a/src/main/java/me/itzg/helpers/singles/TestLoggingCommand.java b/src/main/java/me/itzg/helpers/singles/TestLoggingCommand.java new file mode 100644 index 00000000..0175ca30 --- /dev/null +++ b/src/main/java/me/itzg/helpers/singles/TestLoggingCommand.java @@ -0,0 +1,21 @@ +package me.itzg.helpers.singles; + +import lombok.extern.slf4j.Slf4j; +import picocli.CommandLine.Command; +import picocli.CommandLine.ExitCode; + +import java.util.concurrent.Callable; + +@Command(name = "test-logging-levels") +@Slf4j +public class TestLoggingCommand implements Callable { + + @Override + public Integer call() throws Exception { + log.error("This is an error"); + log.warn("This is a warning"); + log.info("This is an info"); + log.debug("This is a debug"); + return ExitCode.OK; + } +} diff --git a/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java b/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java index f89d2e47..c53e457e 100644 --- a/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java +++ b/src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java @@ -6,6 +6,7 @@ import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -14,8 +15,12 @@ import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder; import java.nio.file.Path; import java.util.function.Consumer; +import me.itzg.helpers.LatchingExecutionExceptionHandler; +import me.itzg.helpers.errors.InvalidParameterException; import me.itzg.helpers.json.ObjectMappers; import me.itzg.helpers.modrinth.ModrinthCommand.DownloadDependencies; +import me.itzg.helpers.modrinth.model.Project; +import me.itzg.helpers.modrinth.model.ProjectType; import org.assertj.core.api.AbstractPathAssert; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; @@ -108,12 +113,7 @@ void downloadsOnlyRequestedDependencyTypes(ModrinthCommand.DownloadDependencies stubVersionRequest(requiredDepProjectId, requiredVersionId, deps -> {}); stubVersionRequest(optionalDepProjectId, optionalVersionId, deps -> {}); - stubFor(get(urlPathMatching("/cdn/(.+)")) - .willReturn(aResponse() - .withBody("{{request.pathSegments.[1]}}") - .withTransformers("response-template") - ) - ); + stubDownload(); final int exitCode = new CommandLine( new ModrinthCommand() @@ -150,6 +150,47 @@ else if (downloadDependencies == DownloadDependencies.OPTIONAL) { } } + @Test + void failsWhenNoDependenciesForModLoader(@TempDir Path tempDir) throws JsonProcessingException { + final String projectId = randomAlphanumeric(6); + final String projectSlug = randomAlphabetic(5); + final String versionId = randomAlphanumeric(6); + final String requiredDepProjectId = randomAlphanumeric(6); + + stubProjectBulkRequest(projectId, projectSlug); + + stubVersionRequest(projectId, versionId, deps -> { + deps.addObject() + .put("project_id", requiredDepProjectId) + .put("dependency_type", "required"); + }); + stubVersionRequestEmptyResponse(requiredDepProjectId); + stubGetProject(requiredDepProjectId, new Project().setProjectType(ProjectType.resourcepack)); + + stubDownload(); + + final LatchingExecutionExceptionHandler executionExceptionHandler = new LatchingExecutionExceptionHandler(); + + final int exitCode = new CommandLine( + new ModrinthCommand() + ) + .setExecutionExceptionHandler(executionExceptionHandler) + .execute( + "--api-base-url", wm.getRuntimeInfo().getHttpBaseUrl(), + "--output-directory", tempDir.toString(), + "--game-version", "1.21.1", + "--loader", "paper", + "--projects", projectId, + "--download-dependencies", DownloadDependencies.REQUIRED.name() + ); + + assertThat(exitCode).isNotEqualTo(ExitCode.OK); + + assertThat(executionExceptionHandler.getExecutionException()) + .isInstanceOf(InvalidParameterException.class) + .hasCauseInstanceOf(NoFilesAvailableException.class); + } + @Test void errorWhenNoApplicableVersion(@TempDir Path tempDir) { stubFor( @@ -325,6 +366,15 @@ private void stubProjectBulkRequest(String projectId, String projectSlug) { ); } + private void stubGetProject(String projectIdOrSlug, Project project) throws JsonProcessingException { + stubFor(get(urlPathEqualTo("/v2/project/"+projectIdOrSlug)) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody(objectMapper.writeValueAsBytes(project)) + ) + ); + } + private void stubVersionRequest(String projectId, String versionId, Consumer depsAdder) { final ArrayNode versionResp = objectMapper.createArrayNode(); final ObjectNode versionNode = versionResp @@ -347,7 +397,28 @@ private void stubVersionRequest(String projectId, String versionId, Consumer