Add missing changes from #1496

Koitharu 1 year ago
parent 3b91a3883e
commit 29263ff59b
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -14,7 +14,7 @@ public data class MangaSearchQueryCapabilities internal constructor(
@InternalParsersApi @InternalParsersApi
public fun validate(query: MangaSearchQuery) { 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) val usedStrictFields = query.criteria.mapToSet { it.field }.intersect(strictFields)
if (usedStrictFields.isNotEmpty() && query.criteria.size > 1) { 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 // Ensure single value per criterion if supportMultiValue is false
if (!capability.multiValue) { if (!capability.isMultiple) {
when (criterion) { when (criterion) {
is Include<*> -> if (criterion.values.size > 1) is Include<*> -> if (criterion.values.size > 1)
throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}") throw IllegalArgumentException("Multiple values are not allowed for field ${criterion.field}")

@ -15,6 +15,17 @@ public sealed interface QueryCriteria<T> {
override fun hashCode(): Int 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<T : Any>( public data class Include<T : Any>(
public override val field: SearchableField, public override val field: SearchableField,
@JvmField public val values: Set<T>, @JvmField public val values: Set<T>,
@ -25,6 +36,17 @@ public sealed interface QueryCriteria<T> {
} }
} }
/**
* 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<T : Any>( public data class Exclude<T : Any>(
public override val field: SearchableField, public override val field: SearchableField,
@JvmField public val values: Set<T>, @JvmField public val values: Set<T>,
@ -35,6 +57,18 @@ public sealed interface QueryCriteria<T> {
} }
} }
/**
* 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<T : Comparable<T>>( public data class Range<T : Comparable<T>>(
public override val field: SearchableField, public override val field: SearchableField,
@JvmField public val from: T, @JvmField public val from: T,
@ -47,6 +81,18 @@ public sealed interface QueryCriteria<T> {
} }
} }
/**
* 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<T : Any>( public data class Match<T : Any>(
public override val field: SearchableField, public override val field: SearchableField,
@JvmField public val value: T, @JvmField public val value: T,

@ -2,9 +2,32 @@ package org.koitharu.kotatsu.parsers.model.search
import kotlin.reflect.KClass 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, @JvmField public val field: SearchableField,
/** The set of supported criteria types for this field. */
@JvmField public val criteriaTypes: Set<KClass<out QueryCriteria<*>>>, @JvmField public val criteriaTypes: Set<KClass<out QueryCriteria<*>>>,
@JvmField public val multiValue: Boolean, /** Indicates whether the field supports multiple values. */
@JvmField public val otherCriteria: Boolean, @JvmField public val isMultiple: Boolean,
/** Specifies whether the field can be used alongside other criteria. */
@JvmField public val isExclusive: Boolean = false,
) )

@ -73,62 +73,52 @@ internal class MangaDexParser(context: MangaLoaderContext) : AbstractMangaParser
SearchCapability( SearchCapability(
field = TAG, field = TAG,
criteriaTypes = setOf(Include::class, Exclude::class), criteriaTypes = setOf(Include::class, Exclude::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = TITLE_NAME, field = TITLE_NAME,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = STATE, field = STATE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = AUTHOR, field = AUTHOR,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = CONTENT_TYPE, field = CONTENT_TYPE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = CONTENT_RATING, field = CONTENT_RATING,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = DEMOGRAPHIC, field = DEMOGRAPHIC,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = ORIGINAL_LANGUAGE, field = ORIGINAL_LANGUAGE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = LANGUAGE, field = LANGUAGE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = PUBLICATION_YEAR, field = PUBLICATION_YEAR,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
) )

@ -41,26 +41,23 @@ internal abstract class MangaboxParser(
SearchCapability( SearchCapability(
field = TAG, field = TAG,
criteriaTypes = setOf(Include::class, Exclude::class), criteriaTypes = setOf(Include::class, Exclude::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = TITLE_NAME, field = TITLE_NAME,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = STATE, field = STATE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = AUTHOR, field = AUTHOR,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = false, isMultiple = false,
otherCriteria = false, isExclusive = true,
), ),
) )

@ -43,20 +43,18 @@ internal class Mangairo(context: MangaLoaderContext) :
SearchCapability( SearchCapability(
field = TAG, field = TAG,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = TITLE_NAME, field = TITLE_NAME,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = false, isExclusive = true,
), ),
SearchCapability( SearchCapability(
field = STATE, field = STATE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
) )

@ -32,20 +32,18 @@ internal class Mangakakalot(context: MangaLoaderContext) :
SearchCapability( SearchCapability(
field = TAG, field = TAG,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = TITLE_NAME, field = TITLE_NAME,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = false, isExclusive = true,
), ),
SearchCapability( SearchCapability(
field = STATE, field = STATE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
) )

@ -35,20 +35,18 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
SearchCapability( SearchCapability(
field = TAG, field = TAG,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = TITLE_NAME, field = TITLE_NAME,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = false, isExclusive = true,
), ),
SearchCapability( SearchCapability(
field = STATE, field = STATE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
), ),
) )

@ -87,10 +87,10 @@ internal fun convertToMangaListFilter(searchQuery: MangaSearchQuery): MangaListF
} }
internal fun MangaSearchQueryCapabilities.toMangaListFilterCapabilities() = MangaListFilterCapabilities( 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) }, isTagsExclusionSupported = capabilities.any { x -> x.field == TAG && x.criteriaTypes.contains(Exclude::class) },
isSearchSupported = capabilities.any { x -> x.field == TITLE_NAME }, 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) }, 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) }, isYearRangeSupported = capabilities.any { x -> x.field == PUBLICATION_YEAR && x.criteriaTypes.contains(Range::class) },
isOriginalLocaleSupported = capabilities.any { x -> x.field == ORIGINAL_LANGUAGE }, isOriginalLocaleSupported = capabilities.any { x -> x.field == ORIGINAL_LANGUAGE },
@ -102,80 +102,76 @@ internal fun MangaListFilterCapabilities.toMangaSearchQueryCapabilities(): Manga
capabilities = setOfNotNull( capabilities = setOfNotNull(
isMultipleTagsSupported.takeIf { it }?.let { isMultipleTagsSupported.takeIf { it }?.let {
SearchCapability( SearchCapability(
field = TAG, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true, field = TAG,
criteriaTypes = setOf(Include::class),
isMultiple = true,
) )
}, },
isTagsExclusionSupported.takeIf { it }?.let { isTagsExclusionSupported.takeIf { it }?.let {
SearchCapability( SearchCapability(
field = TAG, criteriaTypes = setOf(Exclude::class), multiValue = true, otherCriteria = true, field = TAG,
criteriaTypes = setOf(Exclude::class),
isMultiple = true,
) )
}, },
isSearchSupported.takeIf { it }?.let { isSearchSupported.takeIf { it }?.let {
SearchCapability( SearchCapability(
field = TITLE_NAME, field = TITLE_NAME,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = false, isExclusive = true,
) )
}, },
isSearchWithFiltersSupported.takeIf { it }?.let { isSearchWithFiltersSupported.takeIf { it }?.let {
SearchCapability( SearchCapability(
field = TITLE_NAME, field = TITLE_NAME,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
) )
}, },
isYearSupported.takeIf { it }?.let { isYearSupported.takeIf { it }?.let {
SearchCapability( SearchCapability(
field = PUBLICATION_YEAR, field = PUBLICATION_YEAR,
criteriaTypes = setOf(Match::class), criteriaTypes = setOf(Match::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
) )
}, },
isYearRangeSupported.takeIf { it }?.let { isYearRangeSupported.takeIf { it }?.let {
SearchCapability( SearchCapability(
field = PUBLICATION_YEAR, field = PUBLICATION_YEAR,
criteriaTypes = setOf(Range::class), criteriaTypes = setOf(Range::class),
multiValue = false, isMultiple = false,
otherCriteria = true,
) )
}, },
isOriginalLocaleSupported.takeIf { it }?.let { isOriginalLocaleSupported.takeIf { it }?.let {
SearchCapability( SearchCapability(
field = ORIGINAL_LANGUAGE, field = ORIGINAL_LANGUAGE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
) )
}, },
SearchCapability( SearchCapability(
field = LANGUAGE, field = LANGUAGE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = STATE, criteriaTypes = setOf(Include::class), multiValue = true, otherCriteria = true, field = STATE, criteriaTypes = setOf(Include::class), isMultiple = true,
), ),
SearchCapability( SearchCapability(
field = CONTENT_TYPE, field = CONTENT_TYPE,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = CONTENT_RATING, field = CONTENT_RATING,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
SearchCapability( SearchCapability(
field = DEMOGRAPHIC, field = DEMOGRAPHIC,
criteriaTypes = setOf(Include::class), criteriaTypes = setOf(Include::class),
multiValue = true, isMultiple = true,
otherCriteria = true,
), ),
), ),
) )

@ -14,10 +14,10 @@ class MangaSearchQueryCapabilitiesTest {
private val capabilities = MangaSearchQueryCapabilities( private val capabilities = MangaSearchQueryCapabilities(
capabilities = setOf( capabilities = setOf(
SearchCapability(TITLE_NAME, setOf(Match::class), multiValue = false, otherCriteria = false), SearchCapability(TITLE_NAME, setOf(Match::class), isMultiple = false, isExclusive = true),
SearchCapability(TAG, setOf(Include::class, Exclude::class), multiValue = true, otherCriteria = true), SearchCapability(TAG, setOf(Include::class, Exclude::class), isMultiple = true, isExclusive = false),
SearchCapability(PUBLICATION_YEAR, setOf(Range::class), multiValue = false, otherCriteria = true), SearchCapability(PUBLICATION_YEAR, setOf(Range::class), isMultiple = false, isExclusive = false),
SearchCapability(STATE, setOf(Include::class), multiValue = false, otherCriteria = true), SearchCapability(STATE, setOf(Include::class), isMultiple = false, isExclusive = false),
), ),
) )

Loading…
Cancel
Save