From 29263ff59b48ffa89261ac9bf1bb6d0980a03fea Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 1 Mar 2025 16:27:56 +0200 Subject: [PATCH] Add missing changes from #1496 --- .../search/MangaSearchQueryCapabilities.kt | 4 +- .../parsers/model/search/QueryCriteria.kt | 46 +++++++++++++++++++ .../parsers/model/search/SearchCapability.kt | 29 ++++++++++-- .../parsers/site/all/MangaDexParser.kt | 30 ++++-------- .../parsers/site/mangabox/MangaboxParser.kt | 13 ++---- .../parsers/site/mangabox/en/Mangairo.kt | 10 ++-- .../parsers/site/mangabox/en/Mangakakalot.kt | 10 ++-- .../site/mangabox/en/MangakakalotTv.kt | 10 ++-- .../parsers/util/SearchQueryConverter.kt | 42 ++++++++--------- .../model/search/MangaSearchQueryTest.kt | 8 ++-- 10 files changed, 124 insertions(+), 78 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt index 818cc16c..6551defe 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryCapabilities.kt @@ -14,7 +14,7 @@ public data class MangaSearchQueryCapabilities internal constructor( @InternalParsersApi public fun validate(query: MangaSearchQuery) { - val strictFields = capabilities.filter { !it.otherCriteria }.mapToSet { it.field } + val strictFields = capabilities.filter { it.isExclusive }.mapToSet { it.field } val usedStrictFields = query.criteria.mapToSet { it.field }.intersect(strictFields) if (usedStrictFields.isNotEmpty() && query.criteria.size > 1) { @@ -34,7 +34,7 @@ public data class MangaSearchQueryCapabilities internal constructor( } // Ensure single value per criterion if supportMultiValue is false - if (!capability.multiValue) { + if (!capability.isMultiple) { when (criterion) { is Include<*> -> if (criterion.values.size > 1) throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt index ffaf5759..6760efdc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/QueryCriteria.kt @@ -15,6 +15,17 @@ public sealed interface QueryCriteria { override fun hashCode(): Int + /** + * Represents an inclusion criterion that allows search results based on a set of allowed values. + * + * @param T The type of value being included in the search. + * @property values The set of values that should be included in the search results. + * + * ### Example Usage: + * ```kotlin + * val genreFilter = QueryCriteria.Include(SearchableField.STATE, setOf(MangaState.ONGOING, MangaState.FINISHED)) + * ``` + */ public data class Include( public override val field: SearchableField, @JvmField public val values: Set, @@ -25,6 +36,17 @@ public sealed interface QueryCriteria { } } + /** + * Represents an exclusion criterion that exclude results containing certain values. + * + * @param T The type of value being excluded from the search. + * @property values The set of values that should be excluded from the search results. + * + * ### Example Usage: + * ```kotlin + * val excludeTag = QueryCriteria.Exclude(SearchableField.TAG, setOf(MangaTag(key, title, source))) + * ``` + */ public data class Exclude( public override val field: SearchableField, @JvmField public val values: Set, @@ -35,6 +57,18 @@ public sealed interface QueryCriteria { } } + /** + * Represents a range criterion that allows search based on a range of values. + * + * @param T The type of value used in the range (must be comparable). + * @property from The starting value of the range (inclusive). + * @property to The ending value of the range (inclusive). + * + * ### Example Usage: + * ```kotlin + * val yearRange = QueryCriteria.Range(SearchableField.PUBLICATION_YEAR, 2000, 2020) + * ``` + */ public data class Range>( public override val field: SearchableField, @JvmField public val from: T, @@ -47,6 +81,18 @@ public sealed interface QueryCriteria { } } + + /** + * Represents a match criterion that search results based on an exact match of a value. + * + * @param T The type of value being matched. + * @property value The exact value that must be matched. + * + * ### Example Usage: + * ```kotlin + * val titleMatch = QueryCriteria.Match(SearchableField.TITLE, "manga title") + * ``` + */ public data class Match( public override val field: SearchableField, @JvmField public val value: T, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt index 097986fa..175528c0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/model/search/SearchCapability.kt @@ -2,9 +2,32 @@ package org.koitharu.kotatsu.parsers.model.search import kotlin.reflect.KClass -public data class SearchCapability ( +/** + * Defines the search capabilities of a given field in the manga search query. + * + * @property field The searchable field that this capability applies to. + * Example values: + * - `SearchableField.TITLE_NAME` for searching by title. + * - `SearchableField.AUTHOR` for searching by author names. + * - `SearchableField.TAG` for filtering by tags. + * @property criteriaTypes The set of supported criteria types for the field. + * Example values: + * - `setOf(Include::class, Exclude::class)` selected field supports inclusion/exclusion criteria. + * - `setOf(Range::class)` selected field support numerical range criteria. + * @property isMultiValue Indicates whether the field supports multiple values. + * - `true` if multiple values can be provided (e.g., multiple tags or authors). + * - `false` if only a single value is allowed (e.g., only one tag or author). + * @property isExclusive Specifies whether the field can be used alongside other criteria. + * - `true` if this field can be used with other search criteria. + * - `false` if using this field requires it to be the only criterion in query. + */ +public data class SearchCapability( + /** The searchable field that this capability applies to. */ @JvmField public val field: SearchableField, + /** The set of supported criteria types for this field. */ @JvmField public val criteriaTypes: Set>>, - @JvmField public val multiValue: Boolean, - @JvmField public val otherCriteria: Boolean, + /** Indicates whether the field supports multiple values. */ + @JvmField public val isMultiple: Boolean, + /** Specifies whether the field can be used alongside other criteria. */ + @JvmField public val isExclusive: Boolean = false, ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt index d92d157b..f607ca79 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt @@ -73,62 +73,52 @@ internal class MangaDexParser(context: MangaLoaderContext) : AbstractMangaParser SearchCapability( field = TAG, criteriaTypes = setOf(Include::class, Exclude::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = TITLE_NAME, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), SearchCapability( field = STATE, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = AUTHOR, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = CONTENT_TYPE, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = CONTENT_RATING, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = DEMOGRAPHIC, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = ORIGINAL_LANGUAGE, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = LANGUAGE, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = PUBLICATION_YEAR, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt index ac8dc3d2..644258f1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt @@ -41,26 +41,23 @@ internal abstract class MangaboxParser( SearchCapability( field = TAG, criteriaTypes = setOf(Include::class, Exclude::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = TITLE_NAME, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), SearchCapability( field = STATE, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = AUTHOR, criteriaTypes = setOf(Include::class), - multiValue = false, - otherCriteria = false, + isMultiple = false, + isExclusive = true, ), ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt index 7301f2f5..3e93a09e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt @@ -43,20 +43,18 @@ internal class Mangairo(context: MangaLoaderContext) : SearchCapability( field = TAG, criteriaTypes = setOf(Include::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), SearchCapability( field = TITLE_NAME, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = false, + isMultiple = false, + isExclusive = true, ), SearchCapability( field = STATE, criteriaTypes = setOf(Include::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt index 6b18ea57..e3c61fc5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt @@ -32,20 +32,18 @@ internal class Mangakakalot(context: MangaLoaderContext) : SearchCapability( field = TAG, criteriaTypes = setOf(Include::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), SearchCapability( field = TITLE_NAME, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = false, + isMultiple = false, + isExclusive = true, ), SearchCapability( field = STATE, criteriaTypes = setOf(Include::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt index b4fc155a..a4d2eb1f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt @@ -35,20 +35,18 @@ internal class MangakakalotTv(context: MangaLoaderContext) : SearchCapability( field = TAG, criteriaTypes = setOf(Include::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), SearchCapability( field = TITLE_NAME, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = false, + isMultiple = false, + isExclusive = true, ), SearchCapability( field = STATE, criteriaTypes = setOf(Include::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ), ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt index af7e7970..c7c4881f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/SearchQueryConverter.kt @@ -87,10 +87,10 @@ internal fun convertToMangaListFilter(searchQuery: MangaSearchQuery): MangaListF } internal fun MangaSearchQueryCapabilities.toMangaListFilterCapabilities() = MangaListFilterCapabilities( - isMultipleTagsSupported = capabilities.any { x -> x.field == TAG && x.multiValue }, + isMultipleTagsSupported = capabilities.any { x -> x.field == TAG && x.isMultiple }, isTagsExclusionSupported = capabilities.any { x -> x.field == TAG && x.criteriaTypes.contains(Exclude::class) }, isSearchSupported = capabilities.any { x -> x.field == TITLE_NAME }, - isSearchWithFiltersSupported = capabilities.any { x -> x.field == TITLE_NAME && x.otherCriteria }, + isSearchWithFiltersSupported = capabilities.any { x -> x.field == TITLE_NAME && !x.isExclusive }, isYearSupported = capabilities.any { x -> x.field == PUBLICATION_YEAR && x.criteriaTypes.contains(Match::class) }, isYearRangeSupported = capabilities.any { x -> x.field == PUBLICATION_YEAR && x.criteriaTypes.contains(Range::class) }, isOriginalLocaleSupported = capabilities.any { x -> x.field == ORIGINAL_LANGUAGE }, @@ -102,80 +102,76 @@ internal fun MangaListFilterCapabilities.toMangaSearchQueryCapabilities(): Manga capabilities = setOfNotNull( isMultipleTagsSupported.takeIf { it }?.let { SearchCapability( - field = TAG, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true, + field = TAG, + criteriaTypes = setOf(Include::class), + isMultiple = true, ) }, isTagsExclusionSupported.takeIf { it }?.let { SearchCapability( - field = TAG, criteriaTypes = setOf(Exclude::class), multiValue = true, otherCriteria = true, + field = TAG, + criteriaTypes = setOf(Exclude::class), + isMultiple = true, ) }, isSearchSupported.takeIf { it }?.let { SearchCapability( field = TITLE_NAME, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = false, + isMultiple = false, + isExclusive = true, ) }, isSearchWithFiltersSupported.takeIf { it }?.let { SearchCapability( field = TITLE_NAME, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ) }, isYearSupported.takeIf { it }?.let { SearchCapability( field = PUBLICATION_YEAR, criteriaTypes = setOf(Match::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ) }, isYearRangeSupported.takeIf { it }?.let { SearchCapability( field = PUBLICATION_YEAR, criteriaTypes = setOf(Range::class), - multiValue = false, - otherCriteria = true, + isMultiple = false, ) }, isOriginalLocaleSupported.takeIf { it }?.let { SearchCapability( field = ORIGINAL_LANGUAGE, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ) }, SearchCapability( field = LANGUAGE, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( - field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true, + field = STATE, criteriaTypes = setOf(Include::class), isMultiple = true, ), SearchCapability( field = CONTENT_TYPE, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = CONTENT_RATING, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), SearchCapability( field = DEMOGRAPHIC, criteriaTypes = setOf(Include::class), - multiValue = true, - otherCriteria = true, + isMultiple = true, ), ), ) diff --git a/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt b/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt index b7be3832..6ec3409f 100644 --- a/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt +++ b/src/test/kotlin/org/koitharu/kotatsu/parsers/model/search/MangaSearchQueryTest.kt @@ -14,10 +14,10 @@ class MangaSearchQueryCapabilitiesTest { private val capabilities = MangaSearchQueryCapabilities( capabilities = setOf( - SearchCapability(TITLE_NAME, setOf(Match::class), multiValue = false, otherCriteria = false), - SearchCapability(TAG, setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true), - SearchCapability(PUBLICATION_YEAR, setOf(Range::class), multiValue = false, otherCriteria = true), - SearchCapability(STATE, setOf(Include::class), multiValue = false, otherCriteria = true), + SearchCapability(TITLE_NAME, setOf(Match::class), isMultiple = false, isExclusive = true), + SearchCapability(TAG, setOf(Include::class, Exclude::class), isMultiple = true, isExclusive = false), + SearchCapability(PUBLICATION_YEAR, setOf(Range::class), isMultiple = false, isExclusive = false), + SearchCapability(STATE, setOf(Include::class), isMultiple = false, isExclusive = false), ), )