All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.path.Field.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2024, OpenSavvy and contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package opensavvy.ktmongo.dsl.path

import opensavvy.ktmongo.dsl.DangerousMongoApi
import opensavvy.ktmongo.dsl.KtMongoDsl
import opensavvy.ktmongo.dsl.LowLevelApi
import kotlin.reflect.KProperty1

/**
 * High-level, typesafe pointer to a specific field in a document.
 *
 * This type may refer to fields nested in children documents or arrays.
 * Instances are generally used to refer to a MongoDB field we want to read or update, when
 * writing requests.
 *
 * ### Type safety
 *
 * This type is responsible for ensuring type safety:
 * ```kotlin
 * class User(
 *     val _id: ObjectId,
 *     val profile: Profile,
 *     val friends: List,
 * )
 *
 * class Profile(
 *     val name: String,
 * )
 *
 * class Friend(
 *     val name: String,
 *     val age: Int,
 * )
 *
 * // Refer to the user's id
 * println(User::_id.field)
 *
 * // Refer to the user's name
 * println(User::profile / Profile::name)
 *
 * // Refer to the name of the second friend
 * println(User::friends[1] / Friend::name)
 * ```
 *
 * Some of the functions of the DSL may be available only when [FieldDsl] is in scope.
 * All operator scopes provided by this library should bring it into scope automatically.
 *
 * For example, when writing a filter, methods from this interface are automatically available:
 * ```kotlin
 * collection.find {
 *     User::profile / Profile::name eq "Thibault Lognaise"
 * }
 * ```
 *
 * @param Root The type of the document in which this field is in.
 * @param Type The type of the value stored by this field.
 */
interface Field<@Suppress("unused") Root, @Suppress("unused") out Type> {

	/**
	 * Low-level representation of this field's path.
	 */
	@LowLevelApi
	val path: Path

	/**
	 * Refers to [child] as a nested field of the current field.
	 *
	 * ### Examples
	 *
	 * ```kotlin
	 * class User(
	 *     val id: Int,
	 *     val profile: Profile,
	 * )
	 *
	 * class Profile(
	 *     val name: String,
	 *     val age: Int,
	 * )
	 *
	 * // Refer to the name
	 * println(User::profile / Profile::name)
	 * // → 'profile.name'
	 *
	 * // Refer to the age
	 * println(User::profile / Profile::age)
	 * // → 'profile.age'
	 * ```
	 *
	 * @see get Access a specific element of an array
	 */
	@KtMongoDsl
	operator fun  div(child: Field): Field

	/**
	 * Refers to [child] as a nested field of the current field.
	 *
	 * ### Examples
	 *
	 * ```kotlin
	 * class User(
	 *     val id: Int,
	 *     val profile: Profile,
	 * )
	 *
	 * class Profile(
	 *     val name: String,
	 *     val age: Int,
	 * )
	 *
	 * // Refer to the name
	 * println(User::profile / Profile::name)
	 * // → 'profile.name'
	 *
	 * // Refer to the age
	 * println(User::profile / Profile::age)
	 * // → 'profile.age'
	 * ```
	 *
	 * @see get Access a specific element of an array
	 */
	@KtMongoDsl
	operator fun  div(child: KProperty1): Field
}

/**
 * Refers to a specific item in an array, by its index.
 *
 * ### Examples
 *
 * ```kotlin
 * class User(
 *     val name: String,
 *     val friends: List,
 * )
 *
 * class Friend(
 *     val name: String,
 * )
 *
 * // Refer to the first friend
 * println(User::friends[0])
 * // → 'friends.$0'
 *
 * // Refer to the third friend's name
 * println(User::friends[2] / Friend::name)
 * // → 'friends.$2.name'
 * ```
 */
@KtMongoDsl
@OptIn(LowLevelApi::class)
operator fun  Field>.get(index: Int): Field =
	FieldImpl(this.path / PathSegment.Indexed(index))

/**
 * DSL to refer to [fields][Field], usually automatically added into scope by operators.
 */
interface FieldDsl {

	/**
	 * Converts a Kotlin property into a [Field].
	 *
	 * The KtMongo DSL is built on top of the [Field] interface, which represents a specific
	 * field in a document (possibly nested).
	 *
	 * To help with writing requests, we provide utilities to use Kotlin property references as well:
	 * ```kotlin
	 * class User(
	 *     val name: String,
	 *     val age: Int,
	 * )
	 *
	 * println(User::name)       // KProperty1…
	 * println(User::name.field) // Field 'name'
	 * ```
	 *
	 * Most functions of the DSL have an overload that accepts a [KProperty1] and calls
	 * [field] before calling the real operator implementation.
	 */
	@KtMongoDsl
	@OptIn(LowLevelApi::class)
	val  KProperty1.field: Field
		get() = FieldImpl(Path(this.name))

	/**
	 * Refers to [child] as a nested field of the current field.
	 *
	 * ### Examples
	 *
	 * ```kotlin
	 * class User(
	 *     val id: Int,
	 *     val profile: Profile,
	 * )
	 *
	 * class Profile(
	 *     val name: String,
	 *     val age: Int,
	 * )
	 *
	 * // Refer to the id
	 * println(User::id)
	 * // → 'id'
	 *
	 * // Refer to the name
	 * println(User::profile / Profile::name)
	 * // → 'profile.name'
	 *
	 * // Refer to the age
	 * println(User::profile / Profile::age)
	 * // → 'profile.age'
	 * ```
	 *
	 * @see get Access a specific element of an array
	 */
	@KtMongoDsl
	operator fun  KProperty1.div(child: KProperty1): Field =
		this.field / child

	/**
	 * Refers to a specific item in an array, by its index.
	 *
	 * ### Examples
	 *
	 * ```kotlin
	 * class User(
	 *     val name: String,
	 *     val friends: List,
	 * )
	 *
	 * class Friend(
	 *     val name: String,
	 * )
	 *
	 * // Refer to the first friend
	 * println(User::friends[0])
	 * // → 'friends.$0'
	 *
	 * // Refer to the third friend's name
	 * println(User::friends[2] / Friend::name)
	 * // → 'friends.$2.name'
	 * ```
	 */
	@KtMongoDsl
	operator fun  KProperty1>.get(index: Int): Field =
		this.field[index]

}

@LowLevelApi
internal class FieldImpl(
	override val path: Path,
) : Field {

	@OptIn(DangerousMongoApi::class)
	override fun  div(child: Field): Field =
		FieldImpl(path / child.path)

	override fun  div(child: KProperty1): Field =
		FieldImpl(path / PathSegment.Field(child.name))

	override fun toString() = path.toString()

	// region Identity

	override fun equals(other: Any?): Boolean {
		if (this === other) return true
		if (other !is FieldImpl<*, *>) return false

		if (path != other.path) return false

		return true
	}

	override fun hashCode(): Int {
		return path.hashCode()
	}

	// endregion
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy