Skip to content

Commit 71fac39

Browse files
authored
Merge pull request #35 from Zechiax/feature/search-facets
Add search facets to allow better filtering of search results
2 parents 33046a2 + a55c504 commit 71fac39

File tree

8 files changed

+187
-27
lines changed

8 files changed

+187
-27
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Modrinth.Models.Facets;
2+
3+
namespace Modrinth.Net.Test;
4+
5+
[TestFixture]
6+
public class FacetCollectionTests
7+
{
8+
[Test]
9+
public void CollectionWithOnlyOneFacet()
10+
{
11+
var collection = new FacetCollection();
12+
collection.Add(Facet.Category("test"));
13+
Assert.That(collection.ToString(), Is.EqualTo("[[\"categories:test\"]]"));
14+
}
15+
16+
[Test]
17+
public void CollectionWithMultipleFacets_OR()
18+
{
19+
var collection = new FacetCollection();
20+
collection.Add(Facet.Category("test"), Facet.Category("test2"));
21+
Assert.That(collection.ToString(), Is.EqualTo("[[\"categories:test\",\"categories:test2\"]]"));
22+
}
23+
24+
[Test]
25+
public void CollectionWithMultipleFacets_AND()
26+
{
27+
var collection = new FacetCollection();
28+
collection.Add(Facet.Category("test"));
29+
collection.Add(Facet.Category("test2"));
30+
Assert.That(collection.ToString(), Is.EqualTo("[[\"categories:test\"],[\"categories:test2\"]]"));
31+
}
32+
33+
[Test]
34+
public void CollectionWithMultipleFacets_AND_OR()
35+
{
36+
var collection = new FacetCollection();
37+
collection.Add(Facet.Category("test"));
38+
collection.Add(Facet.Category("test2"), Facet.Category("test3"));
39+
Assert.That(collection.ToString(),
40+
Is.EqualTo("[[\"categories:test\"],[\"categories:test2\",\"categories:test3\"]]"));
41+
}
42+
}

Modrinth.Net.Test/ModrinthApiTests/SearchTests.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Index = Modrinth.Models.Enums.Index;
1+
using Modrinth.Models.Facets;
2+
using Index = Modrinth.Models.Enums.Index;
23

34
namespace Modrinth.Net.Test.ModrinthApiTests;
45

@@ -125,4 +126,18 @@ public async Task Search_WithUpdatedSort_ShouldReturnSortedByUpdatedList()
125126
search.Hits.Select(p => p.DateModified),
126127
Is.EqualTo(search.Hits.Select(p => p.DateModified).OrderByDescending(d => d)));
127128
}
129+
130+
// Search with facets
131+
[Test]
132+
public async Task Search_WithFacets_ShouldReturnFilteredResults()
133+
{
134+
var facets = new FacetCollection();
135+
136+
facets.Add(Facet.Category("adventure"));
137+
138+
var search = await _client.Project.SearchAsync("", facets: facets);
139+
140+
// Check that every search result has the adventure category
141+
Assert.That(search.Hits.Select(p => p.Categories).All(c => c.Contains("adventure")));
142+
}
128143
}

Modrinth.Net/Endpoints/Project/IProjectApi.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Modrinth.Models;
2+
using Modrinth.Models.Facets;
23
using Index = Modrinth.Models.Enums.Index;
34

45
namespace Modrinth.Endpoints.Project;
@@ -9,6 +10,7 @@ public interface IProjectApi
910
/// Search Modrinth for project by it's name
1011
/// </summary>
1112
/// <param name="query">The query to search for</param>
13+
/// <param name="facets">Facets to filter the search by</param>
1214
/// <param name="index">The sorting method used for sorting search results</param>
1315
/// <param name="offset">The offset into the search. Skips this number of results</param>
1416
/// <param name="limit">The number of results returned by the search</param>
@@ -17,7 +19,8 @@ Task<SearchResponse> SearchAsync(
1719
string query,
1820
Index index = Index.Downloads,
1921
ulong offset = 0,
20-
ulong limit = 10);
22+
ulong limit = 10,
23+
FacetCollection? facets = null);
2124

2225
/// <summary>
2326
/// Gets project by slug or ID

Modrinth.Net/Endpoints/Project/ProjectApi.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Flurl.Http;
22
using Modrinth.Extensions;
33
using Modrinth.Models;
4+
using Modrinth.Models.Facets;
45
using Index = Modrinth.Models.Enums.Index;
56

67
namespace Modrinth.Endpoints.Project;
@@ -64,13 +65,16 @@ public async Task UnfollowAsync(string slugOrId)
6465

6566
/// <inheritdoc />
6667
public async Task<SearchResponse> SearchAsync(string query, Index index = Index.Downloads, ulong offset = 0,
67-
ulong limit = 10)
68+
ulong limit = 10, FacetCollection? facets = null)
6869
{
69-
return await _client.Request("search")
70+
var request = _client.Request("search")
7071
.SetQueryParam("query", query)
7172
.SetQueryParam("index", index.ToString().ToLower())
7273
.SetQueryParam("offset", offset)
73-
.SetQueryParam("limit", limit)
74-
.GetJsonAsync<SearchResponse>();
74+
.SetQueryParam("limit", limit);
75+
76+
if (facets is {Count: > 0}) request = request.SetQueryParam("facets", facets.ToString());
77+
78+
return await request.GetJsonAsync<SearchResponse>();
7579
}
7680
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Modrinth.Models.Enums;
2+
3+
namespace Modrinth.Extensions;
4+
5+
public static class ProjectTypeExtensions
6+
{
7+
/// <summary>
8+
/// Convert ProjectType to string for Modrinth API
9+
/// </summary>
10+
/// <param name="projectType"></param>
11+
/// <returns></returns>
12+
public static string ToModrinthString(this ProjectType projectType)
13+
{
14+
return projectType switch
15+
{
16+
ProjectType.Mod => "mod",
17+
ProjectType.Modpack => "modpack",
18+
ProjectType.Resourcepack => "resourcepack",
19+
ProjectType.Shader => "shader",
20+
// Return lower string, this should work for all, but it is not guaranteed
21+
_ => projectType.ToString().ToLower()
22+
};
23+
}
24+
}
Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using Modrinth.Models;
2-
using Modrinth.Models.Enums;
1+
using Modrinth.Extensions;
2+
using Modrinth.Models;
33

44
namespace Modrinth.Helpers;
55

@@ -13,23 +13,6 @@ public static class UrlCreatorHelper
1313
/// </summary>
1414
public const string ModrinthUrl = "https://modrinth.com";
1515

16-
/// <summary>
17-
/// Returns formatted type used in Modrinth links to specific project
18-
/// </summary>
19-
/// <param name="projectType"></param>
20-
/// <returns></returns>
21-
private static string GetProjectUrlType(ProjectType projectType)
22-
{
23-
return projectType switch
24-
{
25-
ProjectType.Mod => "mod",
26-
ProjectType.Modpack => "modpack",
27-
ProjectType.Resourcepack => "resourcepack",
28-
ProjectType.Shader => "shader",
29-
// Return lower string, this should work for all, but it is not guaranteed
30-
_ => projectType.ToString().ToLower()
31-
};
32-
}
3316

3417
/// <summary>
3518
/// Return direct link to the user on Modrinth
@@ -38,7 +21,7 @@ private static string GetProjectUrlType(ProjectType projectType)
3821
/// <returns></returns>
3922
public static string GetDirectUrl(this Project project)
4023
{
41-
return $"{ModrinthUrl}/{GetProjectUrlType(project.ProjectType)}/{project.Id}";
24+
return $"{ModrinthUrl}/{project.ProjectType.ToModrinthString()}/{project.Id}";
4225
}
4326

4427
/// <summary>
@@ -58,6 +41,6 @@ public static string GetDirectUrl(this User user)
5841
/// <returns></returns>
5942
public static string GetDirectUrl(this SearchResult searchResult)
6043
{
61-
return $"{ModrinthUrl}/{GetProjectUrlType(searchResult.ProjectType)}/{searchResult.ProjectId}";
44+
return $"{ModrinthUrl}/{searchResult.ProjectType.ToModrinthString()}/{searchResult.ProjectId}";
6245
}
6346
}

Modrinth.Net/Models/Facets/Facet.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using Modrinth.Extensions;
2+
using Modrinth.Models.Enums;
3+
4+
namespace Modrinth.Models.Facets;
5+
6+
public abstract class Facet
7+
{
8+
public static Facet<string> Category(string value)
9+
{
10+
return new(FacetType.Categories, value);
11+
}
12+
13+
public static Facet<string> Version(string value)
14+
{
15+
return new(FacetType.Versions, value);
16+
}
17+
18+
public static Facet<string> License(string value)
19+
{
20+
return new(FacetType.License, value);
21+
}
22+
23+
public static Facet<string> ProjectType(ProjectType projectType)
24+
{
25+
return new(FacetType.ProjectType, projectType.ToModrinthString());
26+
}
27+
}
28+
29+
public class Facet<T> : Facet
30+
{
31+
public Facet(FacetType type, T value)
32+
{
33+
Type = type;
34+
Value = value;
35+
}
36+
37+
public FacetType Type { get; }
38+
public T Value { get; }
39+
40+
public override string ToString()
41+
{
42+
return Type switch
43+
{
44+
FacetType.Categories => $"categories:{Value}",
45+
FacetType.Versions => $"versions:{Value}",
46+
FacetType.License => $"license:{Value}",
47+
FacetType.ProjectType => $"project_type:{Value}",
48+
_ => string.Empty
49+
};
50+
}
51+
}
52+
53+
public enum FacetType
54+
{
55+
Categories,
56+
Versions,
57+
License,
58+
ProjectType
59+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace Modrinth.Models.Facets;
2+
3+
public class FacetCollection
4+
{
5+
private readonly List<Facet[]> _facets = new();
6+
public int Count => _facets.Count;
7+
8+
/// <summary>
9+
/// Adds a facets to the collection, it will be added as a new group
10+
/// Facets in a group are OR'd together
11+
/// Facets in different groups are AND'd together
12+
/// </summary>
13+
/// <param name="facets"></param>
14+
public void Add(params Facet[] facets)
15+
{
16+
if (facets.Length == 0)
17+
return;
18+
_facets.Add(facets);
19+
}
20+
21+
public override string ToString()
22+
{
23+
// Serialize the facets into a Javascript array
24+
var serializedFacets = _facets.Select(
25+
facets =>
26+
$"[{string.Join(',', facets.Select(facet => $"\"{facet}\""))}]");
27+
28+
return $"[{string.Join(',', serializedFacets)}]";
29+
}
30+
}

0 commit comments

Comments
 (0)