diff --git a/api/src/commonMain/kotlin/Parameters.kt b/api/src/commonMain/kotlin/Parameters.kt index 864d893..fa272d9 100644 --- a/api/src/commonMain/kotlin/Parameters.kt +++ b/api/src/commonMain/kotlin/Parameters.kt @@ -3,7 +3,19 @@ package opensavvy.spine.api import kotlin.jvm.JvmInline import kotlin.reflect.KProperty +/** + * Underlying storage for [Parameters]: a mutable map of parameter names to their raw string values. + * + * Library integrations may use this directly when constructing parameter bundles. + */ typealias ParameterStorage = MutableMap + +/** + * Factory function that builds a [Parameters] subtype backed by a [ParameterStorage]. + * + * This type appears when the client calls an endpoint with parameters. + * To learn more, see [Parameters]. + */ typealias ParameterConstructor

= (ParameterStorage) -> P /** diff --git a/api/src/commonMain/kotlin/Path.kt b/api/src/commonMain/kotlin/Path.kt index edae159..1c8ea17 100644 --- a/api/src/commonMain/kotlin/Path.kt +++ b/api/src/commonMain/kotlin/Path.kt @@ -9,9 +9,44 @@ class Path( val segments: List, ) : Iterable by segments, Addressed { + /** + * Convenience constructor that builds a [Path] from a list of string [segments]. + * + * Each element is validated and wrapped as a [Segment]. See [Segment] for constraints. + * + * ### Example + * + * ```kotlin + * // /api/v2/users/123/edit + * val p = Path("api", "v2", "users", "123", "edit") + * ``` + */ constructor(vararg segments: String) : this(segments.map(::Segment)) + /** + * Returns a new [Path] with [other] appended as the last segment. + * + * ### Example + * + * ```kotlin + * val p: Path = … + * + * val p2 = p + Segment("foo") + * ``` + */ operator fun plus(other: Segment): Path = Path(segments + other) + + /** + * Returns a new [Path] with [other] appended as the last segment. + * + * ### Example + * + * ```kotlin + * val p: Path = … + * + * val p2 = p + "foo" + * ``` + */ operator fun plus(other: String): Path = this + Segment(other) override val path: Path @@ -36,6 +71,12 @@ class Path( override fun toString() = segments.joinToString(separator = "/", prefix = "/") + /** + * A single segment of a URL [Path]. + * + * The [text] must be a non-empty string and must not contain the '/' character. + * Invalid values are rejected with an [IllegalArgumentException]. + */ @JvmInline value class Segment(val text: String) { init { diff --git a/api/src/commonMain/kotlin/ResolvedEndpoint.kt b/api/src/commonMain/kotlin/ResolvedEndpoint.kt index d98d49c..9f2e523 100644 --- a/api/src/commonMain/kotlin/ResolvedEndpoint.kt +++ b/api/src/commonMain/kotlin/ResolvedEndpoint.kt @@ -19,6 +19,15 @@ class ResolvedEndpoint internal constructor( override val path: Path, ) : Addressed +/** + * Resolves an [endpoint] declared in a resolved resource. + * + * ### Example + * + * ```kotlin + * println(Root / Users / User("999") / User.get) + * ``` + */ operator fun ResolvedResource.div(endpoint: Endpoint) = ResolvedEndpoint( resource, endpoint, diff --git a/api/src/commonMain/kotlin/ResolvedResource.kt b/api/src/commonMain/kotlin/ResolvedResource.kt index d325592..4877859 100644 --- a/api/src/commonMain/kotlin/ResolvedResource.kt +++ b/api/src/commonMain/kotlin/ResolvedResource.kt @@ -35,8 +35,45 @@ class ResolvedResource internal constructor( override fun toString() = "$path (represented by $resource)" } +/** + * Resolves the static [child] resource under this [ResolvedResource]. + * + * This operator participates in the resource resolution DSL. + * + * ### Example + * + * ```kotlin + * val users = Api / Users + * ``` + */ operator fun > ResolvedResource.div(child: Child): ResolvedResource = ResolvedResource(child, path + child.slug) + +/** + * Resolves the dynamic [child] resource under this [ResolvedResource] by binding its identifier. + * + * The identifier and the target resource are carried by [DynamicResource.Identified], + * typically created via the [DynamicResource.invoke] operator, e.g. `User("123")`. + * + * This operator participates in the resource resolution DSL. + * + * ### Example + * + * ```kotlin + * val user123 = Api / Users / User("123") + * ``` + */ operator fun > ResolvedResource.div(child: DynamicResource.Identified): ResolvedResource = ResolvedResource(child.resource, path + child.slug) +/** + * Starts resolution from the [RootResource] and resolves the static [child]. + * + * Syntactic sugar for `this.resolved / child`. + */ operator fun > Root.div(child: Child) = this.resolved / child + +/** + * Starts resolution from the [RootResource] and resolves the dynamic [child]. + * + * Syntactic sugar for `this.resolved / child`. + */ operator fun > Root.div(child: DynamicResource.Identified) = this.resolved / child