Skip to content

Commit

Permalink
[3/4] Improve: Small Cleanup / Tech Debt Reduction Around NexusWebApi…
Browse files Browse the repository at this point in the history
… / V2 Types. (#2095)

* WIP: Documentation for Update Detection

* Added: Additional Edge Cases to Update Logic Docs

* Added: Extra case of `Archived in the Middle`

* Fixed: Indentation for `file_updates` field.

* Finalized 'Updating Mods' doc with simplified Implementation requested.

* Fixed: Minor Notes from older Research Doc

* Fixed: Added Missing 'Updating Mods' mkdocs sidebar item.

* [Working WIP] Added: Initial Implementation of Generic Page Caching System used by Mod Pages

* Tech Debt Reduction: Add additional V2 GraphQL Types and Correct Sizes of Existing Types

* Added: Missing 'UInt32' types in attribute definitions

* Improved: Accuracy of documentation for FileId struct.

* Added: Method for constructing UidForMod and UidForFile from GraphQL API Results

* Rename: ICanGetUid to ICanGetUidForMod

* Added: Tests for UidForModTests and UidForFileTests

* Removed: Unused Tests.cs file

---------

Co-authored-by: halgari <[email protected]>
  • Loading branch information
Sewer56 and halgari authored Oct 3, 2024
1 parent d0a202b commit d09d737
Show file tree
Hide file tree
Showing 36 changed files with 923 additions and 79 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using NexusMods.Abstractions.NexusWebApi.Types;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Paths;

namespace NexusMods.Abstractions.Collections.Json;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using JetBrains.Annotations;
using NexusMods.Abstractions.MnemonicDB.Attributes;
using NexusMods.Abstractions.NexusWebApi.Types;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using NexusMods.Abstractions.MnemonicDB.Attributes;
using NexusMods.Abstractions.NexusWebApi.Types;
using NexusMods.Abstractions.Resources.DB;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Abstractions.Telemetry;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using NexusMods.Abstractions.NexusWebApi.DTOs.Interfaces;
using GameId = NexusMods.Abstractions.NexusWebApi.Types.GameId;
using GameId = NexusMods.Abstractions.NexusWebApi.Types.V2.GameId;

// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable InconsistentNaming
Expand All @@ -27,7 +27,7 @@ public class GameInfo : IJsonArraySerializable<GameInfo>
/// This field is for deserialization only.
/// </remarks>
[JsonPropertyName("id")]
public int _Id { get; set; }
public uint _Id { get; set; }

/// <summary>
/// Returns the ID as typed ValueObject <see cref="GameId"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
using System.Text.Json.Serialization.Metadata;
using NexusMods.Abstractions.NexusWebApi.DTOs.Interfaces;
using NexusMods.Abstractions.NexusWebApi.Types;
using FileId = NexusMods.Abstractions.NexusWebApi.Types.FileId;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using FileId = NexusMods.Abstractions.NexusWebApi.Types.V2.FileId;

// 👇 Suppress uninitialised variables. Currently Nexus has mostly read-only API and we expect server to return the data.
#pragma warning disable CS8618
Expand Down Expand Up @@ -47,7 +48,7 @@ public class ModFile : IJsonSerializable<ModFile>
/// This ID is unique within the context of the game.
/// i.e. This ID might be used for another mod if you search for mods for another game.
/// </remarks>
public FileId FileId => FileId.From(_FileId);
public FileId FileId => FileId.From((uint)_FileId);

/// <summary>
/// Name (title) of the mod file as seen on the `Files` section of the mod page.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using NexusMods.Abstractions.NexusWebApi.DTOs.Interfaces;
using ModId = NexusMods.Abstractions.NexusWebApi.Types.ModId;
using ModId = NexusMods.Abstractions.NexusWebApi.Types.V2.ModId;

// 👇 Suppress uninitialised variables. Currently Nexus has mostly read-only API and we expect server to return the data.
#pragma warning disable CS8618
Expand All @@ -25,7 +25,7 @@ public class ModUpdate : IJsonArraySerializable<ModUpdate>
/// <summary>
/// An individual mod ID that is unique for this game.
/// </summary>
public ModId ModId => ModId.From(_ModId);
public ModId ModId => ModId.From((uint)_ModId);

/// <summary>
/// The last time a file on the mod page was updated.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using NexusMods.Abstractions.NexusWebApi.DTOs;
using NexusMods.Abstractions.NexusWebApi.DTOs.OAuth;
using NexusMods.Abstractions.NexusWebApi.Types;
using FileId = NexusMods.Abstractions.NexusWebApi.Types.V2.FileId;
using ModId = NexusMods.Abstractions.NexusWebApi.Types.V2.ModId;

namespace NexusMods.Abstractions.NexusWebApi;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<ProjectReference Include="..\NexusMods.Abstractions.Serialization\NexusMods.Abstractions.Serialization.csproj" />
<PackageReference Include="NexusMods.MnemonicDB.SourceGenerator" PrivateAssets="all" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
<Folder Include="Types\V2\" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NexusMods.Abstractions.NexusWebApi.Types;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ public class NXMModUrl : NXMUrl
/// <summary>
/// id of the mod page
/// </summary>
public ModId ModId { get; set; }
public V2.ModId ModId { get; set; }

/// <summary>
/// id of the file (within that game domain)
/// </summary>
public FileId FileId { get; set; }
public V2.FileId FileId { get; set; }

/// <summary>
/// game domain (name of the game within the Nexus Mods page)
Expand All @@ -36,8 +36,8 @@ public NXMModUrl(Uri uri)
Game = uri.Host;
try
{
ModId = ModId.From(ulong.Parse(uri.Segments[2].TrimEnd('/')));
FileId = FileId.From(ulong.Parse(uri.Segments[4].TrimEnd('/')));
ModId = V2.ModId.From(uint.Parse(uri.Segments[2].TrimEnd('/')));
FileId = V2.FileId.From(uint.Parse(uri.Segments[4].TrimEnd('/')));
}
catch (FormatException)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.ElementComparers;
using TransparentValueObjects;
namespace NexusMods.Abstractions.NexusWebApi.Types.V2;

/// <summary>
/// Unique ID for a mod file associated with a game (<see cref="GameId"/>).
/// Querying mod pages returns items of this type.
/// </summary>
[ValueObject<uint>] // Matches backend. Do not change.
public readonly partial struct FileId : IAugmentWith<DefaultValueAugment>, IAugmentWith<JsonAugment>
{
/// <inheritdoc/>
public static FileId DefaultValue => From(default(uint));
}

/// <summary>
/// File ID attribute, for NexusMods API file IDs.
/// </summary>
public class FileIdAttribute(string ns, string name) :
ScalarAttribute<FileId, uint>(ValueTags.UInt32, ns, name)
{
/// <inheritdoc />
protected override uint ToLowLevel(FileId value) => value.Value;

/// <inheritdoc />
protected override FileId FromLowLevel(uint value, ValueTags tags, AttributeResolver resolver) => FileId.From(value);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using TransparentValueObjects;

namespace NexusMods.Abstractions.NexusWebApi.Types;
namespace NexusMods.Abstractions.NexusWebApi.Types.V2;

/// <summary>
/// Unique identifier for an individual game hosted on Nexus.
/// </summary>
[ValueObject<int>]
[ValueObject<uint>] // Matches backend. Do not change.
public readonly partial struct GameId : IAugmentWith<DefaultValueAugment>
{
/// <inheritdoc/>
public static GameId DefaultValue => From(default);
public static GameId DefaultValue => From(default(uint));
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,29 @@
using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.ElementComparers;
using TransparentValueObjects;

namespace NexusMods.Abstractions.NexusWebApi.Types;
namespace NexusMods.Abstractions.NexusWebApi.Types.V2;

/// <summary>
/// An individual mod ID. Unique per game.
/// An individual mod ID. Unique per <see cref="GameId"/>.
/// i.e. Each game has its own set of IDs and starts with 0.
/// </summary>
[ValueObject<ulong>]
[ValueObject<uint>] // Matches backend. Do not change.
public readonly partial struct ModId : IAugmentWith<DefaultValueAugment>, IAugmentWith<JsonAugment>
{
/// <inheritdoc/>
public static ModId DefaultValue => From(default);
public static ModId DefaultValue => From(default(uint));
}


/// <summary>
/// Mod ID attribute, for NexusMods API mod IDs.
/// </summary>
public class ModIdAttribute(string ns, string name)
: ScalarAttribute<ModId, ulong>(ValueTags.UInt64, ns, name)
: ScalarAttribute<ModId, uint>(ValueTags.UInt32, ns, name)
{
/// <inheritdoc />
protected override ulong ToLowLevel(ModId value) => value.Value;
protected override uint ToLowLevel(ModId value) => value.Value;

/// <inheritdoc />
protected override ModId FromLowLevel(ulong value, ValueTags tags, AttributeResolver resolver) => ModId.From(value);
protected override ModId FromLowLevel(uint value, ValueTags tags, AttributeResolver resolver) => ModId.From(value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace NexusMods.Abstractions.NexusWebApi.Types.V2.Uid;

/// <summary>
/// This represents a unique ID of an individual file as stored on Nexus Mods.
///
/// This is a composite key of <see cref="FileId"/> and <see cref="GameId"/>, where
/// the upper 4 bytes represent the <see cref="FileId"/> and the lower 4 bytes represent
/// the <see cref="GameId"/>.
///
/// This is consistent with how the Nexus Mods backend produces the UID and is not
/// expected to change.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct UidForFile
{
/// <summary>
/// Unique identifier for the file, within the specific <see cref="GameId"/>.
/// </summary>
public FileId FileId;

/// <summary>
/// Unique identifier for the game.
/// </summary>
public GameId GameId;

/// <summary>
/// Decodes a Nexus Mods API result which contains an 'uid' field into a <see cref="UidForFile"/>.
/// </summary>
/// <param name="uid">The 'uid' field of a GraphQL API query. This should be an 8 byte number represented as a string.</param>
/// <remarks>
/// This throws if <param name="uid"/> is not a valid number.
/// </remarks>
public static UidForFile FromV2Api(string uid) => FromUlong(ulong.Parse(uid));

/// <summary>
/// Reinterprets the current <see cref="UidForFile"/> as a single <see cref="ulong"/>.
/// </summary>
public ulong AsUlong => Unsafe.As<UidForFile, ulong>(ref this);

/// <summary>
/// Reinterprets a given <see cref="ulong"/> into a <see cref="UidForFile"/>.
/// </summary>
public static UidForFile FromUlong(ulong value) => Unsafe.As<ulong, UidForFile>(ref value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace NexusMods.Abstractions.NexusWebApi.Types.V2.Uid;

/// <summary>
/// This represents a unique ID of an individual mod page as stored on Nexus Mods.
///
/// This is a composite key of <see cref="ModId"/> and <see cref="GameId"/>, where
/// the upper 4 bytes represent the <see cref="ModId"/> and the lower 4 bytes represent
/// the <see cref="GameId"/>. Values are stored in little endian byte order.
///
/// When transferred over the wire via the API, the resulting `ulong` is converted into
/// a string.
///
/// This is consistent with how the Nexus Mods backend produces the UID and is not
/// expected to change.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct UidForMod
{
/// <summary>
/// Unique identifier for the mod, within the specific <see cref="GameId"/>.
/// </summary>
public ModId ModId;

/// <summary>
/// Unique identifier for the game.
/// </summary>
public GameId GameId;

/// <summary>
/// Decodes a Nexus Mods API result which contains an 'uid' field into a <see cref="UidForFile"/>.
/// </summary>
/// <param name="uid">The 'uid' field of a GraphQL API query. This should be an 8 byte number represented as a string.</param>
/// <remarks>
/// This throws if <param name="uid"/> is not a valid number.
/// </remarks>
public static UidForMod FromV2Api(string uid) => FromUlong(ulong.Parse(uid));

/// <summary>
/// Reinterprets the current <see cref="UidForMod"/> as a single <see cref="ulong"/>.
/// </summary>
public ulong AsUlong => Unsafe.As<UidForMod, ulong>(ref this);

/// <summary>
/// Reinterprets a given <see cref="ulong"/> into a <see cref="UidForMod"/>.
/// </summary>
public static UidForMod FromUlong(ulong value) => Unsafe.As<ulong, UidForMod>(ref value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using DynamicData.Kernel;
using NexusMods.Abstractions.GameLocators;
using NexusMods.Abstractions.NexusWebApi.Types;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Paths;

namespace NexusMods.Games.RedEngine.Cyberpunk2077.Emitters;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text.RegularExpressions;
using NexusMods.Abstractions.GameLocators;
using NexusMods.Abstractions.NexusWebApi.Types;
using NexusMods.Abstractions.NexusWebApi.Types.V2;
using NexusMods.Paths;

namespace NexusMods.Games.RedEngine.Cyberpunk2077.Emitters;
Expand Down
4 changes: 2 additions & 2 deletions src/Games/NexusMods.Games.TestHarness/Verbs/StressTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
using NexusMods.ProxyConsole.Abstractions;
using NexusMods.ProxyConsole.Abstractions.VerbDefinitions;
using NexusMods.StandardGameLocators;
using ModId = NexusMods.Abstractions.NexusWebApi.Types.ModId;
using ModId = NexusMods.Abstractions.NexusWebApi.Types.V2.ModId;

namespace NexusMods.Games.TestHarness.Verbs;

Expand All @@ -38,7 +38,7 @@ internal static async Task<int> RunStressTest(
AdvancedManualInstallerUI.Headless = true;

var mods = await nexusApiClient.ModUpdatesAsync(game.Domain.Value, PastTime.Day, token);
var results = new List<(string FileName, ModId ModId, Abstractions.NexusWebApi.Types.FileId FileId, Hash Hash, bool Passed, Exception? exception)>();
var results = new List<(string FileName, ModId ModId, Abstractions.NexusWebApi.Types.V2.FileId FileId, Hash Hash, bool Passed, Exception? exception)>();

await using var gameFolder = temporaryFileManager.CreateFolder();
var (manualId, install) = await manualLocator.Add(game, new Version(1, 0), gameFolder);
Expand Down
Loading

0 comments on commit d09d737

Please sign in to comment.