From d0d44ff8a930fde5081d4a40501fa7fd556c1432 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 20 Jul 2026 21:59:20 +0300 Subject: [PATCH 1/3] docs: add a Modules section to the README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README only documented the core kuri artifact; the optional kuri-bind and kuri-serde-kotlinx modules were discoverable only by scrolling into the guide. Add a short Modules section after Installation that lists all three artifacts — what each does, its platform coverage, and how to depend on it — so the optional add-ons are visible up front. --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 20667d2..b60f21e 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,31 @@ Runs on **Java 8+** and **Kotlin 2.0+**, with no runtime dependencies beyond the > kuri is in the `0.x` series — the public API is not yet frozen and may change between minor releases, so > pin to an exact version. +## Modules + +kuri ships as three artifacts under `org.dexpace`, so you pull in only what you use: + +- **`kuri`** — the core engine: the `Url` and `Uri` models, parsing, building, the query API, and the + standalone `Percent` / `Idn` / `Schemes` utilities. Kotlin Multiplatform (JVM, Android, JS, Wasm, native). + This is the only module the [quick start](#quick-start) needs. +- **`kuri-bind`** — maps an annotated request object onto a `Url`/`Uri` builder (`@Url`, `@Path`, `@Query`, + …). JVM-only, since it uses Kotlin reflection; the core stays dependency-free. See + [Annotation binding](docs/GUIDE.md#annotation-binding-kuri-bind). +- **`kuri-serde-kotlinx`** — a [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) + bridge: `Url`/`Uri` serializers plus a query-parameters format. Multiplatform across the same targets as + `kuri`, minus Android. See + [kotlinx.serialization](docs/GUIDE.md#kotlinxserialization-kuri-serde-kotlinx). + +The optional modules depend on the core, and you add them the same way: + +```kotlin +dependencies { + implementation("org.dexpace:kuri:0.1.0") + implementation("org.dexpace:kuri-bind:0.1.0") // optional — annotation binding + implementation("org.dexpace:kuri-serde-kotlinx:0.1.0") // optional — kotlinx.serialization +} +``` + ## Quick start ```kotlin From a157f4a033053b7a54cc776e48bc7102c61df06e Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 20 Jul 2026 22:02:31 +0300 Subject: [PATCH 2/3] docs: add a Highlights section with per-module examples to the README Building on the new Modules section, add a Highlights section that shows kuri's capabilities with short, concrete examples: the parse/edit/rebuild builder round-trip, the optional Kotlin operator DSL, RFC 6570 URI templates, annotation binding with kuri-bind, and type-safe query decoding with kuri-serde-kotlinx. This gives each optional module a snippet on the landing page and surfaces features that were previously only in the guide. --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/README.md b/README.md index b60f21e..8b34957 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,63 @@ Url.parseOrThrow("HTTP://Example.com/a/../b").toString() // "http://example.co The guide covers the full comparison, the accessor differences to watch for, IPv6 zone identifiers, and IRI conversion: [Two models, one engine](docs/GUIDE.md#two-models-one-engine). +## Highlights + +**Parse, edit, rebuild.** Values are immutable; a builder pre-filled by `newBuilder()` round-trips cleanly. + +```kotlin +Url.parseOrThrow("https://example.com/v1/users?page=1") + .newBuilder() + .addPathSegment("42") + .setQueryParameter("page", "2") + .build() // https://example.com/v1/users/42?page=2 +``` + +**A Kotlin DSL when you want it.** The optional `ktx` package adds operators and builder lambdas — `/` +appends a path segment, `+` adds a query parameter. + +```kotlin +val api = Url.parseOrThrow("https://api.example.com/v1") +api / "users" / "42" + ("page" to "2") // https://api.example.com/v1/users/42?page=2 +``` + +**RFC 6570 URI templates.** Parse a template once, expand it against variables — with the full operator and +modifier set. + +```kotlin +UriTemplate.parse("https://api.example.com/users/{id}{?fields*}") + .expand(mapOf("id" to 42, "fields" to listOf("name", "email"))) +// https://api.example.com/users/42?fields=name&fields=email +``` + +**Annotation binding** — with `kuri-bind`, turn a request object straight into a URL. + +```kotlin +@Url @PathTemplate("/repos/{owner}/{repo}/issues") +data class Issues( + @Path("owner") val owner: String, + @Path("repo") val repo: String, + @Query("state") val state: String, +) + +val apiBase = Url.parseOrThrow("https://api.example.com") +KuriBind.bindInto(apiBase.newBuilder(), Issues("dexpace", "kuri", "open")).build() +// https://api.example.com/repos/dexpace/kuri/issues?state=open +``` + +**Type-safe query strings** — with `kuri-serde-kotlinx`, decode a query into a `@Serializable` class and back. + +```kotlin +@Serializable +data class Search(val q: String, val page: Int = 1, val tags: List = emptyList()) + +QueryParametersFormat.decodeFromQueryString("q=kotlin&page=2&tags=a&tags=b") +// Search(q = "kotlin", page = 2, tags = ["a", "b"]) +``` + +There's more — non-throwing `ParseResult`, `URLSearchParams`-style query editing, IDNA hosts, `redact()` for +safe logging, and configurable resource limits. The [user guide](docs/GUIDE.md) has it all. + ## Documentation - **[User guide](docs/GUIDE.md)** — the complete usage reference: From 7ae1112688ce29c90b42dbafcaff63f6222ec150 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 20 Jul 2026 22:06:20 +0300 Subject: [PATCH 3/3] docs: make the README feature section technical and precise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the section from Highlights to Features and rewrite each lead-in to be precise and technical — naming the actual packages (org.dexpace.kuri.ktx), types (ParseResult, UriTemplate, QueryParametersFormat), RFC levels, and mechanisms rather than casual phrasing. Add non-throwing parse and structured query editing to the set, and drop the now-duplicated parse note from the quick start. --- README.md | 51 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8b34957..6d796a9 100644 --- a/README.md +++ b/README.md @@ -104,9 +104,6 @@ url.queryParameters().get("q"); // "1" url.toString(); // "https://example.com/b?q=1#frag" ``` -Parsing never throws: `parse` returns a `ParseResult`, with `parseOrNull` / `parseOrThrow` / `canParse` when -you want a `null`, an exception, or a boolean instead. - ## Two models kuri gives you two profiles over one engine. Reach for **`Url`** for WHATWG web URLs — `http`, `https`, `ws`, @@ -122,9 +119,11 @@ Url.parseOrThrow("HTTP://Example.com/a/../b").toString() // "http://example.co The guide covers the full comparison, the accessor differences to watch for, IPv6 zone identifiers, and IRI conversion: [Two models, one engine](docs/GUIDE.md#two-models-one-engine). -## Highlights +## Features -**Parse, edit, rebuild.** Values are immutable; a builder pre-filled by `newBuilder()` round-trips cleanly. +**Immutable builders.** Values are immutable. `newBuilder()` returns a builder pre-populated from an existing +value, so a parse → modify → build round-trip mutates nothing. `build()` validates and throws on an +invalid combination; `buildOrNull()` returns `null` instead. ```kotlin Url.parseOrThrow("https://example.com/v1/users?page=1") @@ -134,16 +133,38 @@ Url.parseOrThrow("https://example.com/v1/users?page=1") .build() // https://example.com/v1/users/42?page=2 ``` -**A Kotlin DSL when you want it.** The optional `ktx` package adds operators and builder lambdas — `/` -appends a path segment, `+` adds a query parameter. +**Non-throwing parse.** `parse` returns a sealed `ParseResult` (`Ok`/`Err`) — errors are values, and the +`Err` branch carries a structured `UriParseError`. `parseOrNull`, `parseOrThrow`, and `canParse` cover the +`null`, exception, and boolean cases. + +```kotlin +when (val r = Url.parse(input)) { + is ParseResult.Ok -> r.value.hostName + is ParseResult.Err -> r.error.message // structured reason, not a bare exception +} +``` + +**Structured query editing.** `queryParameters` is a duplicate-preserving, iterable view; the builder edits +follow `URLSearchParams` semantics, and `split(name, delimiter)` flattens repeated pairs and delimited lists +into one list. + +```kotlin +val q = Url.parseOrThrow("https://h/?id=1,2&id=3").queryParameters +q.split("id", ',').map(String::toInt) // [1, 2, 3] +``` + +**Kotlin operator DSL.** The optional `org.dexpace.kuri.ktx` package overloads `/` to append a +percent-encoded path segment and `+` to add a query parameter, and adds `buildUrl { }` / `edit { }` builder +lambdas. Nothing here is visible to Java. ```kotlin val api = Url.parseOrThrow("https://api.example.com/v1") api / "users" / "42" + ("page" to "2") // https://api.example.com/v1/users/42?page=2 ``` -**RFC 6570 URI templates.** Parse a template once, expand it against variables — with the full operator and -modifier set. +**RFC 6570 URI templates.** `UriTemplate` compiles a template once and expands it against a variable map. +All four RFC levels are supported, including the `?` `&` `#` `.` `/` `;` `+` operators and the prefix (`:n`) +and explode (`*`) modifiers. ```kotlin UriTemplate.parse("https://api.example.com/users/{id}{?fields*}") @@ -151,7 +172,9 @@ UriTemplate.parse("https://api.example.com/users/{id}{?fields*}") // https://api.example.com/users/42?fields=name&fields=email ``` -**Annotation binding** — with `kuri-bind`, turn a request object straight into a URL. +**Annotation binding (`kuri-bind`).** A JVM module that maps an annotated request object onto a `Url`/`Uri` +builder by reflection: declare the mapping with `@Url`/`@Path`/`@Query`/`@PathTemplate`, then bind any +instance onto a base URL. ```kotlin @Url @PathTemplate("/repos/{owner}/{repo}/issues") @@ -166,7 +189,9 @@ KuriBind.bindInto(apiBase.newBuilder(), Issues("dexpace", "kuri", "open")).build // https://api.example.com/repos/dexpace/kuri/issues?state=open ``` -**Type-safe query strings** — with `kuri-serde-kotlinx`, decode a query into a `@Serializable` class and back. +**kotlinx.serialization bridge (`kuri-serde-kotlinx`).** `QueryParametersFormat` decodes a query string into +a flat `@Serializable` class and encodes it back; `UrlSerializer` / `UriSerializer` serialize values as their +string form in any kotlinx format. ```kotlin @Serializable @@ -176,8 +201,8 @@ QueryParametersFormat.decodeFromQueryString("q=kotlin&page=2&tags=a&tags // Search(q = "kotlin", page = 2, tags = ["a", "b"]) ``` -There's more — non-throwing `ParseResult`, `URLSearchParams`-style query editing, IDNA hosts, `redact()` for -safe logging, and configurable resource limits. The [user guide](docs/GUIDE.md) has it all. +Beyond these: UTS-46 IDNA host processing, `redact()` for credential-safe logging, `resolve` / `relativize` +against a base, and configurable parse resource limits. The [user guide](docs/GUIDE.md) documents each in full. ## Documentation