Skip to content

Commit

Permalink
Feature/manifest v2 (#3)
Browse files Browse the repository at this point in the history
* Add `sha1` and `complianceLevel` fields to VersionEntry

* Add tests for parsing `version_manifest_v2.json`

* Mark the Manifest V1 constructor as deprecated and reorganize tests for VersionManifest

* Parameterize Tests

* Bump Version

* Add some missing fields

---------

Co-authored-by: xtrm <[email protected]>
  • Loading branch information
OroArmor and xtrm-en authored Apr 26, 2024
1 parent 052ba63 commit 909d543
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 21 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/update-changelog.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Update HTML Changelog

on:
push:
branches: [master]
paths:
- CHANGELOG.md

jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Dispatch website rebuild
uses: benc-uk/workflow-dispatch@v1
with:
workflow: manual-publish.yaml
repo: quiltmc/quiltmc.org
token: "${{ secrets.COZY_PAT }}"
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 1.0.0

Creation of Launcher Meta Parser - A shared library for Quilt tools to parse the launchermeta information from Mojang

# 1.1.0

Changes:
- Update gradle and other dependencies
- Add support for v2 Manifest files
- Improve tests
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group 'org.quiltmc'
version '1.0.0'
version '1.1.0'

repositories {
mavenCentral()
Expand Down
Binary file removed launchermeta-parser.zip
Binary file not shown.
31 changes: 31 additions & 0 deletions src/main/java/org/quiltmc/launchermeta/version/v1/Arguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@
import java.util.List;
import java.util.Objects;

import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;

public class Arguments {
Expand Down Expand Up @@ -95,5 +99,32 @@ public Argument deserialize(JsonElement json, Type typeOfT, JsonDeserializationC
return new Argument(value, rules);
}
}

public static class Serializer implements JsonSerializer<Argument> {
@Override
public JsonElement serialize(Argument argument, Type type, JsonSerializationContext context) {
if (argument.value.size() == 1 && argument.rules.isEmpty()) {
return new JsonPrimitive(argument.value.get(0));
}

JsonObject json = new JsonObject();

if (argument.value.size() == 1) {
json.add("value", new JsonPrimitive(argument.value.get(0)));
} else {
JsonArray array = new JsonArray(argument.value.size());
argument.value.forEach(array::add);
json.add("value", array);
}

if (!argument.rules.isEmpty()) {
JsonArray array = new JsonArray(argument.rules.size());
argument.rules.stream().map(rule -> context.serialize(rule, new TypeToken<Rule>(){}.getType())).forEach(array::add);
json.add("rules", array);
}

return json;
}
}
}
}
38 changes: 37 additions & 1 deletion src/main/java/org/quiltmc/launchermeta/version/v1/Rule.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,29 @@ public static class Features {
@Nullable
private Boolean hasCustomResolution;

public Features(@Nullable Boolean isDemoUser, @Nullable Boolean hasCustomResolution) {
@SerializedName("has_quick_plays_support")
@Nullable
private Boolean hasQuickPlaysSupport;

@SerializedName("is_quick_play_singleplayer")
@Nullable
private Boolean isQuickPlaySinglePlayer;

@SerializedName("is_quick_play_multiplayer")
@Nullable
private Boolean isQuickPlayMultiPlayer;

@SerializedName("is_quick_play_realms")
@Nullable
private Boolean isQuickPlayRealms;

public Features(@Nullable Boolean isDemoUser, @Nullable Boolean hasCustomResolution, @Nullable Boolean hasQuickPlaysSupport, @Nullable Boolean isQuickPlaySinglePlayer, @Nullable Boolean isQuickPlayMultiPlayer, @Nullable Boolean isQuickPlayRealms) {
this.isDemoUser = isDemoUser;
this.hasCustomResolution = hasCustomResolution;
this.hasQuickPlaysSupport = hasQuickPlaysSupport;
this.isQuickPlaySinglePlayer = isQuickPlaySinglePlayer;
this.isQuickPlayMultiPlayer = isQuickPlayMultiPlayer;
this.isQuickPlayRealms = isQuickPlayRealms;
}

public Optional<Boolean> getDemoUser() {
Expand All @@ -111,6 +131,22 @@ public Optional<Boolean> getHasCustomResolution() {
return Optional.ofNullable(hasCustomResolution);
}

public Optional<Boolean> getHasQuickPlaysSupport() {
return Optional.ofNullable(hasQuickPlaysSupport);
}

public Optional<Boolean> getIsQuickPlaySinglePlayer() {
return Optional.ofNullable(isQuickPlaySinglePlayer);
}

public Optional<Boolean> getIsQuickPlayMultiPlayer() {
return Optional.ofNullable(isQuickPlayMultiPlayer);
}

public Optional<Boolean> getIsQuickPlayRealms() {
return Optional.ofNullable(isQuickPlayRealms);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
public class Version {
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(Arguments.Argument.class, new Arguments.Argument.Parser())
.registerTypeAdapter(Arguments.Argument.class, new Arguments.Argument.Serializer())
.create();

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,31 @@
*/
package org.quiltmc.launchermeta.version_manifest;

import java.util.Objects;
import java.util.Optional;

public class VersionEntry {
private final String id;
private final String type;
private final String url;
private final String time;
private final String releaseTime;
private final String sha1;
private final int complianceLevel;

@Deprecated
public VersionEntry(String id, String type, String url, String time, String releaseTime) {
this(id, type, url, time, releaseTime, null, 0);
}

public VersionEntry(String id, String type, String url, String time, String releaseTime, String sha1, int complianceLevel) {
this.id = id;
this.type = type;
this.url = url;
this.time = time;
this.releaseTime = releaseTime;
this.sha1 = sha1;
this.complianceLevel = complianceLevel;
}

public String getId() {
Expand All @@ -50,11 +62,19 @@ public String getReleaseTime() {
return releaseTime;
}

public Optional<String> getSha1() {
return Optional.ofNullable(sha1);
}

public int getComplianceLevel() {
return complianceLevel;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VersionEntry versionEntry = (VersionEntry) o;
return id.equals(versionEntry.id) && type.equals(versionEntry.type) && url.equals(versionEntry.url) && time.equals(versionEntry.time) && releaseTime.equals(versionEntry.releaseTime);
return id.equals(versionEntry.id) && type.equals(versionEntry.type) && url.equals(versionEntry.url) && time.equals(versionEntry.time) && releaseTime.equals(versionEntry.releaseTime) && Objects.equals(sha1, versionEntry.sha1) && complianceLevel == versionEntry.complianceLevel;
}
}
34 changes: 30 additions & 4 deletions src/test/java/org/quiltmc/launchermeta/version/v1/VersionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,36 @@
import java.io.IOException;

import com.google.gson.JsonElement;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.quiltmc.launchermeta.TestUtil;
import org.quiltmc.launchermeta.version_manifest.VersionManifest;
import org.quiltmc.launchermeta.version_manifest.VersionManifestTest;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;

public class VersionTest {
private static final String VERSION_URL = "https://launchermeta.mojang.com/v1/packages/f2affa3247f2471d3334b199d1915ce582914464/21w42a.json";
private static final String VERSION_URL = "https://piston-meta.mojang.com/v1/packages/3ecc58bbbc2b680be6742747089cbbf3272526f9/1.20.6-rc1.json";
private static final String MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json";

@Test
public void testParseFullJson() throws IOException {
public void testParseDoesNotThrow() throws IOException {
JsonElement json = TestUtil.getJsonFromURL(VERSION_URL);
assertDoesNotThrow(() -> {
Version version = Version.fromJson(json);
});
}

@Disabled("Manual verification passes with 1.20.6-rc1, but the order of elements is different")
@Test
public void testParseFullJson() throws IOException {
JsonElement json = TestUtil.getJsonFromURL(VERSION_URL);
Assertions.assertEquals(json, Version.GSON.toJsonTree(Version.fromJson(json)));
}

@Test
public void assertNoMethodReturnsAreNull() throws IOException {
VersionManifest.fromJson(TestUtil.getJsonFromURL(VersionManifestTest.MANIFEST_URL))
VersionManifest.fromJson(TestUtil.getJsonFromURL(MANIFEST_URL))
.getVersions()
.parallelStream()
.map(version -> {
Expand All @@ -52,4 +61,21 @@ public void assertNoMethodReturnsAreNull() throws IOException {
})
.forEach(TestUtil.checkNoMethodsReturnNull(Version.class));
}

@Disabled("Manual verification passes with 1.20.6-rc1, but the order of elements is different")
@Test
public void assertCreatesSameJson() throws IOException {
VersionManifest.fromJson(TestUtil.getJsonFromURL(MANIFEST_URL))
.getVersions()
.parallelStream()
.forEach(version -> {
JsonElement json = null;
try {
json = TestUtil.getJsonFromURL(version.getUrl());
} catch (IOException e) {
e.printStackTrace();
}
Assertions.assertEquals(Version.GSON.toJsonTree(Version.fromJson(json)), json);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,22 @@

import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;

import com.google.gson.JsonElement;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import org.quiltmc.launchermeta.TestUtil;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;

public class VersionManifestTest {
public static final String MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String MANIFEST_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final String MANIFEST_URL_V2 = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";

private static final String TEST_JSON = """
{
Expand All @@ -42,28 +49,66 @@ public class VersionManifestTest {
}]
}
""";
private static final String TEST_JSON_V2 = """
{
"latest": {
"release": "1.17.1",
"snapshot": "21w42a"
},
"versions": [{
"id": "21w42a",
"type": "snapshot",
"url": "https://piston-meta.mojang.com/v1/packages/3ce8fdf60e69bfb0944e479ada4cf6b60dcc3995/21w42a.json",
"time": "2021-10-20T12:46:24+00:00",
"releaseTime": "2021-10-20T12:41:25+00:00",
"sha1": "3ce8fdf60e69bfb0944e479ada4cf6b60dcc3995",
"complianceLevel": 1
}]
}
""";
private static final VersionManifest VERSION_MANIFEST = new VersionManifest(new LatestVersions("1.17.1", "21w42a"), List.of(new VersionEntry("21w42a", "snapshot", "https://launchermeta.mojang.com/v1/packages/f2affa3247f2471d3334b199d1915ce582914464/21w42a.json", "2021-10-20T12:46:24+00:00", "2021-10-20T12:41:25+00:00")));
public static final VersionManifest VERSION_MANIFEST_V2 = new VersionManifest(new LatestVersions("1.17.1", "21w42a"), List.of(new VersionEntry("21w42a", "snapshot", "https://piston-meta.mojang.com/v1/packages/3ce8fdf60e69bfb0944e479ada4cf6b60dcc3995/21w42a.json", "2021-10-20T12:46:24+00:00", "2021-10-20T12:41:25+00:00", "3ce8fdf60e69bfb0944e479ada4cf6b60dcc3995", 1)));

@Test
public void testParseFullJson() throws IOException {
JsonElement json = TestUtil.getJsonFromURL(VersionManifestTest.MANIFEST_URL);
@ParameterizedTest
@ValueSource(strings = {MANIFEST_URL, MANIFEST_URL_V2})
void testParseFullJson(String url) throws IOException {
JsonElement json = TestUtil.getJsonFromURL(url);
VersionManifest manifest = VersionManifest.fromJson(json);

assertEquals(manifest.getVersions().size(), json.getAsJsonObject().get("versions").getAsJsonArray().size(), "Size of version array matches json");
assertEquals(manifest.getLatestVersions().getRelease(), json.getAsJsonObject().get("latest").getAsJsonObject().get("release").getAsString(), "Size of version array matches json");
}

@Test
public void testParseTestJson() {
VersionManifest actual = VersionManifest.fromString(TEST_JSON);
VersionManifest expected = new VersionManifest(new LatestVersions("1.17.1", "21w42a"),
List.of(new VersionEntry("21w42a", "snapshot", "https://launchermeta.mojang.com/v1/packages/f2affa3247f2471d3334b199d1915ce582914464/21w42a.json", "2021-10-20T12:46:24+00:00", "2021-10-20T12:41:25+00:00")));
void testParseFullJsonV2HasShaAnCompliance() throws IOException {
JsonElement json = TestUtil.getJsonFromURL(MANIFEST_URL_V2);
VersionManifest manifest = VersionManifest.fromJson(json);
String latestVersion = manifest.getLatestVersions().getRelease();
VersionEntry latestEntry = manifest.getVersions().stream()
.filter(entry -> entry.getId().equals(latestVersion))
.findFirst()
.orElseThrow();
assertNotNull(latestEntry.getSha1(), "Latest version has a sha1");
assertNotEquals(latestEntry.getComplianceLevel(), 0, "Latest version has a compliance level");
}

assertEquals(actual, expected, "Actual parse matches expected result");
@ParameterizedTest
@MethodSource("provideManifest")
void testParseTestJson(String json, VersionManifest manifest) {
VersionManifest actual = VersionManifest.fromString(json);
assertEquals(actual, manifest, "Actual parse matches expected result");
}

@Test
public void assertNoMethodReturnsAreNull() throws IOException {
TestUtil.checkNoMethodsReturnNull(VersionManifest.class)
.accept(VersionManifest.fromJson(TestUtil.getJsonFromURL(VersionManifestTest.MANIFEST_URL)));
@ParameterizedTest
@ValueSource(strings = {MANIFEST_URL, MANIFEST_URL_V2})
void checkRemoteNoNulls(String url) throws IOException {
TestUtil.checkNoMethodsReturnNull(VersionManifest.class).accept(VersionManifest.fromJson(TestUtil.getJsonFromURL(url)));
}

private static Stream<Arguments> provideManifest() {
return Stream.of(
Arguments.of(TEST_JSON, VERSION_MANIFEST),
Arguments.of(TEST_JSON_V2, VERSION_MANIFEST_V2)
);
}
}

0 comments on commit 909d543

Please sign in to comment.