Skip to content

Commit

Permalink
modrinth: more info in error when dependency has no applicable files (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
itzg authored Nov 9, 2024
1 parent 57e9cb5 commit a23a5d6
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src/main/java/me/itzg/helpers/McImageHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -82,6 +83,7 @@
SetPropertiesCommand.class,
Sync.class,
SyncAndInterpolate.class,
TestLoggingCommand.class,
YamlPathCmd.class,
VanillaTweaksCommand.class,
}
Expand Down
46 changes: 33 additions & 13 deletions src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public class ModrinthCommand implements Callable<Integer> {
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,
Expand Down Expand Up @@ -151,30 +157,43 @@ private ModrinthManifest loadManifest() throws IOException {
return Manifests.load(outputDirectory, ModrinthManifest.ID, ModrinthManifest.class);
}

private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient, Version version) {
private Stream<Version> 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));
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -293,7 +313,7 @@ private Stream<Path> 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))
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/me/itzg/helpers/modrinth/model/ProjectType.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.itzg.helpers.modrinth.model;

public enum ProjectType {
mod,
modpack
mod,
modpack,
resourcepack,
}
21 changes: 21 additions & 0 deletions src/main/java/me/itzg/helpers/singles/TestLoggingCommand.java
Original file line number Diff line number Diff line change
@@ -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<Integer> {

@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;
}
}
83 changes: 77 additions & 6 deletions src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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<ArrayNode> depsAdder) {
final ArrayNode versionResp = objectMapper.createArrayNode();
final ObjectNode versionNode = versionResp
Expand All @@ -347,7 +397,28 @@ private void stubVersionRequest(String projectId, String versionId, Consumer<Arr
.withJsonBody(versionResp)
)
);
}

private void stubVersionRequestEmptyResponse(String projectId) {
final ArrayNode versionResp = objectMapper.createArrayNode();

stubFor(get(urlPathEqualTo("/v2/project/" + projectId + "/version"))
.withQueryParam("loaders", equalTo("[\"paper\",\"spigot\"]"))
.withQueryParam("game_versions", equalTo("[\"1.21.1\"]"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withJsonBody(versionResp)
)
);
}

private static void stubDownload() {
stubFor(get(urlPathMatching("/cdn/(.+)"))
.willReturn(aResponse()
.withBody("{{request.pathSegments.[1]}}")
.withTransformers("response-template")
)
);
}

private static void setupStubs() {
Expand Down

0 comments on commit a23a5d6

Please sign in to comment.