Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
root = true

[{*.kt,*.kts}]
[*.{kt,kts}]
end_of_line = lf
ij_kotlin_allow_trailing_comma = true
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
ij_kotlin_indent_before_arrow_on_new_line = false
ij_kotlin_line_break_after_multiline_when_entry = true
ij_kotlin_allow_trailing_comma_on_call_site = true
indent_size = 4
indent_style = space
insert_final_newline = true
ktlint_argument_list_wrapping_ignore_when_parameter_count_greater_or_equal_than = 8
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = 4
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset
ktlint_code_style = intellij_idea
ktlint_enum_entry_name_casing = upper_or_camel_cases
ktlint_function_signature_body_expression_wrapping = default
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset
ktlint_ignore_back_ticked_identifier = false
ktlint_property_naming_constant_naming = screaming_snake_case
max_line_length = off
ktlint_standard_property-naming = disabled
ktlint_standard_discouraged-comment-location = disabled
ktlint_standard_function-expression-body = disabled
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ jobs:
with:
submodules: 'true'

- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
java-version: 21
cache: 'gradle'

- name: Gradle build
Expand Down
76 changes: 35 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ Self-describing values for Future-proofing

[![ci](https://github.com/erwin-kok/multiformat/actions/workflows/ci.yaml/badge.svg)](https://github.com/erwin-kok/multiformat/actions/workflows/ci.yaml)
[![Maven Central](https://img.shields.io/maven-central/v/org.erwinkok.multiformat/multiformat)](https://central.sonatype.com/artifact/org.erwinkok.result/result-monad)
[![Kotlin](https://img.shields.io/badge/kotlin-1.9.0-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Kotlin](https://img.shields.io/badge/kotlin-2.3.0-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![License](https://img.shields.io/github/license/erwin-kok/multiformat.svg)](https://github.com/erwin-kok/multiformat/blob/master/LICENSE)

## Usage
## Introduction

This project provides **Kotlin implementations of the Multiformats protocols**.

Multiformats define self-describing data formats designed to remain interoperable across systems, languages, and decades. They are a foundational building block in systems such as IPFS and libp2p.

Kotlin DSL:
The goal of this project is to offer **clear, explicit, and specification-faithful implementations** of the core Multiformats standards, optimized for correctness and readability rather than convenience abstractions.

Specifications are defined at https://multiformats.io.

## Installation

```kotlin
repositories {
Expand All @@ -20,28 +28,30 @@ dependencies {
}
```

## Introduction
## Implemented Protocols

This project implements various protocols defined at: https://multiformats.io/
- **[multiaddr](https://github.com/multiformats/multiaddr)** — Self-describing network addresses
- **[multibase](https://github.com/multiformats/multibase)** — Base encoding descriptors
- **[multicodec](https://github.com/multiformats/multicodec)** — Self-describing serialization codes
- **[multihash](https://github.com/multiformats/multihash)** — Cryptographic hash identifiers
- **[multistream-select](https://github.com/multiformats/multistream-select)** — Protocol negotiation

Notably, the following protocols are implemented:
Additionally, this project implements:
- **[CID](https://github.com/multiformats/cid)** - Content Identifier

- [multiaddr](https://github.com/multiformats/multiaddr): network addresses
- [multibase](https://github.com/multiformats/multibase): base encodings
- [multicodec](https://github.com/multiformats/multicodec): serialization codes
- [multihash](https://github.com/multiformats/multihash): cryptographic hashes
- [multistream-select](https://github.com/multiformats/multistream-select): Friendly protocol multiplexing.
## Error Handling Model

Next to this, it also implements Cid: https://github.com/multiformats/cid
This project uses an explicit Result monad for error handling.

With few exceptions, public APIs return Result<T> instead of throwing exceptions. This makes error propagation explicit, composable, and visible in the type system.

## Using the Result Monad
This is a deliberate design choice.

This project is using the [result-monad](https://github.com/erwin-kok/result-monad)
Exceptions are implicit and non-local: once execution enters a try block, it is no longer clear which operation caused control flow to jump to a catch clause. This complicates reasoning about resource ownership and cleanup.

This means that (almost) all methods of this project return a `Result<...>`. The caller can check whether an error was generated,
or it can use the value. For example:
By contrast, Result values make success and failure part of normal control flow.

Example:
```kotlin
val connection = createConnection()
.getOrElse {
Expand All @@ -50,16 +60,8 @@ val connection = createConnection()
}

connection.write(...)


fun createConnection(): Result<Connection> {
...
}
```

The advantage is that it is easier (at least for me) to track the flow of the code and to handle correct/error cases.
The disadvantage of exceptions is, is that you do not know which statement in a try-block generated the exception. For
example:
Compared to exception-based control flow:

```kotlin
var connection: Connection? = null
Expand All @@ -76,10 +78,9 @@ try {
}
```

In the catch-block you do not know where the exception was thrown: before or after creating the connection. This means
that you do not know if the connection should be closed, or not. Of course there are many ways to solve this.
In the catch block, it is unclear whether the connection was successfully created or not.

With the result-monad you can do something like:
Using Result makes this explicit:
```kotlin
val connection = createConnection()
.getOrElse {
Expand All @@ -94,12 +95,11 @@ methodGeneratingError()
}
...
```
The control flow and resource lifetime are explicit and locally reasoned about.

## Usage

A (very) brief description on how to use multiformats:

...but please also look at the various tests.
This section provides a brief overview. For more comprehensive examples, see the test suite.

### multiaddr

Expand Down Expand Up @@ -146,7 +146,6 @@ val selected = MultistreamMuxer.selectOneOf(setOf("/a", "/b", "/c"), connection)
}
```


## Sub-modules

This project has three submodules:
Expand All @@ -157,16 +156,11 @@ git submodule add https://github.com/multiformats/multibase src/main/kotlin/org/
git submodule add https://github.com/multiformats/multihash src/main/kotlin/org/erwinkok/multiformat/spec/multihash
```

These are the official specifications repositories, which are used here for auto-generation code and or verifying the
test results are according to spec.

## Contributing

Bug reports and pull requests are welcome on [GitHub](https://github.com/erwin-kok/multiformat).

## Contact
They are used for:

If you want to contact me, please write an e-mail to: [erwin.kok(at)protonmail.com](mailto:erwin.kok@protonmail.com)
- Code generation
- Conformance testing
- Verifying behavior against the specifications

## License

Expand Down
74 changes: 30 additions & 44 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
// Copyright (c) 2022 Erwin Kok. BSD-3-Clause license. See LICENSE file for more details.
@file:Suppress("UnstableApiUsage")

import com.adarshr.gradle.testlogger.theme.ThemeType
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask

@Suppress("DSL_SCOPE_VIOLATION")
plugins {
kotlin("jvm") version "2.0.20"
kotlin("jvm") version "2.3.0"

`java-library`
`java-test-fixtures`
Expand All @@ -30,7 +27,7 @@ repositories {
}

group = "org.erwinkok.multiformat"
version = "1.1.0"
version = "1.2.0"

java {
withSourcesJar()
Expand Down Expand Up @@ -58,33 +55,28 @@ dependencies {
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.coroutines.debug)

testRuntimeOnly(libs.logback.classic)
testRuntimeOnly(libs.junit.jupiter.engine)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

testlogger {
theme = ThemeType.MOCHA
}

tasks {
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}

withType<JavaCompile>().configureEach {
sourceCompatibility = JavaVersion.VERSION_17.toString()
targetCompatibility = JavaVersion.VERSION_17.toString()
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

withType<com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask> {
rejectVersionIf {
isNonStable(candidate.version)
}
}
kotlin {
jvmToolchain(21)
}

test {
useJUnitPlatform()
tasks.withType<DependencyUpdatesTask> {
rejectVersionIf {
isNonStable(candidate.version)
}
}

Expand All @@ -95,6 +87,10 @@ fun isNonStable(version: String): Boolean {
return isStable.not()
}

tasks.test {
useJUnitPlatform()
}

sourceSets {
main {
java {
Expand All @@ -103,31 +99,21 @@ sourceSets {
}
}

koverReport {
filters {
excludes {
classes(
"org.erwinkok.multiformat.multicodec.Codec",
"org.erwinkok.multiformat.multicodec.GenerateKt*",
)
}
}

defaults {
html {
onCheck = true
kover {
reports {
filters {
excludes {
classes(
"org.erwinkok.multiformat.multicodec.Codec",
"org.erwinkok.multiformat.multicodec.GenerateKt*",
)
}
}

verify {
onCheck = true
rule {
isEnabled = true
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.APPLICATION
bound {
minValue = 0
maxValue = 99
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.LINE
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
minValue.set(0)
}
}
}
Expand All @@ -153,7 +139,7 @@ publishing {
developer {
id.set("erwin-kok")
name.set("Erwin Kok")
email.set("erwin-kok@gmx.com")
email.set("erwin.kok@protonmail.com")
url.set("https://github.com/erwin-kok/")
roles.set(listOf("owner", "developer"))
}
Expand Down
33 changes: 18 additions & 15 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
[versions]
kotlinx-coroutines = "1.9.0"
kotlinx-atomicfu = "0.25.0"
kotlin-logging = "3.0.5"
kotlinx-serialization = "1.7.3"
junit-jupiter = "5.11.1"
slf4j-api = "2.0.16"

kotlin = "2.0.20"
kover-plugin = "0.7.2"
ktlint-plugin = "11.5.0"
nexus-plugin = "1.3.0"
versions-plugin = "0.47.0"
kotlinx-coroutines = "1.10.2"
kotlinx-atomicfu = "0.29.0"
kotlin-logging = "7.0.14"
kotlinx-serialization = "1.9.0"
junit-jupiter = "6.0.2"
slf4j-api = "2.0.17"

kotlin = "2.3.0"
kover-plugin = "0.9.4"
ktlint-plugin = "14.0.1"
nexus-plugin = "2.0.0"
versions-plugin = "0.53.0"
testlogger-plugin = "4.0.0"
protobuf-plugin = "0.9.3"
protobuf-plugin = "0.9.6"

ipaddress = "5.5.1"
ktor = "2.3.12"
ktor = "3.3.3"
logback-classic = "1.5.18"

result-monad = "1.4.0"

Expand All @@ -24,7 +25,7 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-debug = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-debug", version.ref = "kotlinx-coroutines" }
kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "kotlinx-atomicfu" }
kotlin-logging = { module = "io.github.microutils:kotlin-logging-jvm", version.ref = "kotlin-logging" }
kotlin-logging = { module = "io.github.oshai:kotlin-logging-jvm", version.ref = "kotlin-logging" }
kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }

ipaddress = { module = "com.github.seancfoley:ipaddress", version.ref = "ipaddress" }
Expand All @@ -36,6 +37,8 @@ junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.re
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit-jupiter" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" }

logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-classic" }

[plugins]
build-kotlin = { id = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
build-kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover-plugin" }
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
3 changes: 2 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading