Self-describing values for Future-proofing
Kotlin DSL:
repositories {
mavenCentral()
}
dependencies {
implementation("org.erwinkok.multiformat:multiformat:$latest")
}
This project implements various protocols defined at: https://multiformats.io/
Notably, the following protocols are implemented:
- multiaddr: network addresses
- multibase: base encodings
- multicodec: serialization codes
- multihash: cryptographic hashes
- multistream-select: Friendly protocol multiplexing.
Next to this, it also implements Cid: https://github.com/multiformats/cid
This project is using the result-monad
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:
val connection = createConnection()
.getOrElse {
log.error { "Could not create connection: ${errorMessage(it)}" }
return Err(it)
}
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:
var connection: Connection? = null
try {
...
connection = createConnection()
...
methodThrowingException()
...
} catch (e: Exception) {
if (connection != null) {
connection.close()
}
}
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.
With the result-monad you can do something like:
val connection = createConnection()
.getOrElse {
log.error { "Could not create connection: ${errorMessage(it)}" }
return Err(it)
}
...
methodGeneratingError()
.onFailure {
connection.close()
return
}
...
A (very) brief description on how to use multiformats:
...but please also look at the various tests.
val addr1 = Multiaddress.fromString("/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234")
.getOrElse {
log.error { "Could not parse Multiaddress: ${errorMessage(it)}" }
return Err(it)
}
val ip6Addr = Multiaddress.fromString("/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095").getOrThrow()
val tcpAddr = Multiaddress.fromString("/tcp/8000").getOrThrow()
val webAddr = Multiaddress.fromString("/ws").getOrThrow()
val actual1 = Multiaddress.fromString("/").expectNoErrors()
.encapsulate(ip6Addr).expectNoErrors()
.encapsulate(tcpAddr).expectNoErrors()
.encapsulate(webAddr).expectNoErrors()
.toString()
val multibase = Multibase.encode("base16", "foobar".toByteArray()).getOrThrow()
val bytes = Multibase.decode("f666f6f626172").getOrThrow()
val codec = Multicodec.nameToType("cidv2")
val multihash = Multihash.fromBase58("QmPfjpVaf593UQJ9a5ECvdh2x17XuJYG5Yanv5UFnH3jPE")
val selected = MultistreamMuxer.selectOneOf(setOf("/a", "/b", "/c"), connection)
.getOrElse {
log.error { "Error selecting protocol: ${errorMessage(it)}" }
return Err(it)
}
This project has three submodules:
git submodule add https://github.com/multiformats/multicodec src/main/kotlin/org/erwinkok/multiformat/spec/multicodec
git submodule add https://github.com/multiformats/multibase src/main/kotlin/org/erwinkok/multiformat/spec/multibase
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.
Bug reports and pull requests are welcome on GitHub.
If you want to contact me, please write an e-mail to: erwin.kok(at)protonmail.com
This project is licensed under the BSD-3-Clause license, see LICENSE
file for more details.