Skip to content
Draft
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
6 changes: 1 addition & 5 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ dependencies {
}

repositories {
mavenLocal {
content {
includeGroup("at.asitplus.gradle")
}
}
mavenLocal()
maven {
url = uri("https://raw.githubusercontent.com/a-sit-plus/gradle-conventions-plugin/mvn/repo")
name = "aspConventions"
Expand Down
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ val spdxHeaderSources = fileTree(rootDir) {
exclude("docs/**", "repo/**", "**/build/**", ".gradle/**")
}

allprojects {
repositories {
mavenLocal ()
}
}
spotless {
format("mainSourceHeaders") {
target(spdxHeaderSources)
Expand Down
29 changes: 29 additions & 0 deletions cbor/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import at.asitplus.gradle.*
plugins {
id("at.asitplus.propigator.buildlogic")
}

propigatorConventions {
android("at.asitplus.propigator.cbor")
mavenPublish(
name = "Propigator for CBOR",
description = "Typed properties over untamed CBOR data"
)
}

kotlin {
propigatorTargets()

sourceSets {
commonMain.dependencies {
api(project(":common"))
api(libs.kotlinx.serialization.cbor)
}
commonTest {
kotlin.srcDir(project(":common").layout.projectDirectory.dir("src/commonTest/kotlin"))
}
commonTest.dependencies {
implementation(libs.cosef)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: Copyright (c) A-SIT Plus GmbH
// SPDX-License-Identifier: Apache-2.0

package at.asitplus.propigator.cbor

import at.asitplus.propigator.common.ObjectBacked
import at.asitplus.propigator.common.backedProperty
import at.asitplus.propigator.common.slice
import kotlinx.serialization.KSerializer
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.cbor.CborMap
import kotlinx.serialization.cbor.CborNull
import kotlinx.serialization.serializer
import kotlin.properties.ReadOnlyProperty

typealias CborBackedProperty<V> =
ReadOnlyProperty<CborBacked, V>

open class CborBacked(
override val backingObject: CborMap,
override val serialFormat: Cbor = Cbor.Default,
) : ObjectBacked() {
override fun isFormatNull(element: Any?): Boolean = element is CborNull

override fun <V> getElement(key: String, serializer: KSerializer<V>): V? =
backingObject[key]?.let { serialFormat.decodeFromCborElement(serializer, it) }

override fun <S> getSlice(serializer: KSerializer<S>) =
serialFormat.decodeFromCborElement(serializer, backingObject)
}

/**
* Optional fields are backed as nullable type.
* Example
* ```val foo: String? by cborProperty("foo")```
*
* Required fields are backed as strict types
* ```val bar: Bar by cborProperty("bar_obj", CustomBarSerializer)```
*/
inline fun <reified V> cborProperty(
key: String? = null,
serializer: KSerializer<V> = serializer(),
): CborBackedProperty<V> =
backedProperty<CborBacked, V>(key, serializer)

/**
* Reads [defaultValue] when the backing object does not contain the property key.
*
* Serialization still emits the raw backing object unchanged. If defaults should be encoded,
* construct or receive the backing object with those default fields already present.
*/
inline fun <reified V> cborProperty(
key: String? = null,
defaultValue: V,
serializer: KSerializer<V> = serializer(),
): CborBackedProperty<V> = backedProperty(key, defaultValue, serializer)

inline fun <reified V> cborSlice(serializer: KSerializer<V> = serializer()): CborBackedProperty<V> =
slice(serializer)

/**
* Returns the combined content of two [CborMap]s.
* If both inputs are zero returns the empty [CborMap]
*/
@OptIn(ExperimentalUnsignedTypes::class)
@Throws(IllegalArgumentException::class)
fun CborMap?.strictUnion(other: CborMap?): CborMap {
if (this == null) return other ?: CborMap(emptyMap())
if (other == null) return this

val duplicates = this.keys intersect other.keys
require(duplicates.isEmpty()) {
"Duplicate keys: ${duplicates.joinToString()}"
}

return CborMap(this + other)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright (c) A-SIT Plus GmbH
// SPDX-License-Identifier: Apache-2.0

package at.asitplus.propigator.cbor

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.cbor.CborDecoder
import kotlinx.serialization.cbor.CborEncoder
import kotlinx.serialization.cbor.CborMap
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

class CborBackedSerializerTemplate<T : CborBacked>(
private val create: (CborMap, Cbor) -> T,
) : KSerializer<T> {
override val descriptor: SerialDescriptor = CborMap.serializer().descriptor

override fun deserialize(decoder: Decoder): T {
decoder as? CborDecoder
?: error("CborBackedSerializer only works with kotlinx.serialization CBOR")
val backingObject = decoder.decodeCborElement() as? CborMap
?: error("CborBackedSerializer only supports CBOR maps")
return create(backingObject, decoder.cbor).also { it.validate() }
}

override fun serialize(encoder: Encoder, value: T) {
encoder as? CborEncoder
?: error("CborBackedSerializer only works with kotlinx.serialization CBOR")
require(encoder.cbor.hasSameConfigurationAs(value.serialFormat)) {
"Mismatching CborConfiguration. By default, the object owns the serialization shape."
}
encoder.encodeCborElement(value.backingObject)
}
}

@OptIn(ExperimentalSerializationApi::class)
private fun Cbor.hasSameConfigurationAs(other: Cbor): Boolean {
val left = configuration
val right = other.configuration
return left.encodeDefaults == right.encodeDefaults &&
left.ignoreUnknownKeys == right.ignoreUnknownKeys &&
left.encodeKeyTags == right.encodeKeyTags &&
left.encodeValueTags == right.encodeValueTags &&
left.encodeObjectTags == right.encodeObjectTags &&
left.verifyKeyTags == right.verifyKeyTags &&
left.verifyValueTags == right.verifyValueTags &&
left.verifyObjectTags == right.verifyObjectTags &&
left.useDefiniteLengthEncoding == right.useDefiniteLengthEncoding &&
left.preferCborLabelsOverNames == right.preferCborLabelsOverNames &&
left.alwaysUseByteString == right.alwaysUseByteString
}
2 changes: 1 addition & 1 deletion common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ kotlin {

sourceSets {
commonMain.dependencies {
api(serialization("core"))
api(libs.kotlinx.serialization.core)
}
}
}
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[versions]
kotlin="2.3.20"
serialization = "1.11.1-SNAPSHOT"
testballoon = "1.0.0-K2.3.20"
yamlkt = "0.13.0"
asp="20260610"
Expand All @@ -11,11 +12,15 @@ indispensible = "3.21.0"

[libraries]
yamlkt = { module = "net.mamoe.yamlkt:yamlkt", version.ref = "yamlkt" }
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "serialization" }
#for build logic conventions plugin, as it needs the artifact, not the plugin ID
asp = { module = "at.asitplus.gradle:k2", version.ref = "asp" }
agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
sbombastic = { module = "at.asitplus.gradle.sbombastic:at.asitplus.gradle.sbombastic.gradle.plugin", version.ref = "sbombastic" }
josef = { module = "at.asitplus.signum:indispensable-josef", version.ref = "indispensible" }
cosef = { module = "at.asitplus.signum:indispensable-cosef", version.ref = "indispensible" }
nimbusJoseJwt = { module = "com.nimbusds:nimbus-jose-jwt", version.ref = "nimbus"}

[plugins]
Expand Down
2 changes: 1 addition & 1 deletion json/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ kotlin {
sourceSets {
commonMain.dependencies {
api(project(":common"))
api(serialization("json"))
api(libs.kotlinx.serialization.json)
}
commonTest {
kotlin.srcDir(project(":common").layout.projectDirectory.dir("src/commonTest/kotlin"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ inline fun <reified V> jsonSlice(serializer: KSerializer<V> = serializer()): Jso
slice(serializer)

/**
* Returns the combined content of two JsonObjects.
* If both inputs are zero returns the empty JsonObject
* Returns the combined content of two [JsonObject]s.
* If both inputs are zero returns the empty [JsonObject]
*/
@Throws(IllegalArgumentException::class)
fun JsonObject?.strictUnion(other: JsonObject?): JsonObject {
Expand Down
4 changes: 3 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pluginManagement {
repositories {
google()
mavenLocal()
mavenCentral()
gradlePluginPortal()
maven("https://central.sonatype.com/repository/maven-snapshots/")
Expand Down Expand Up @@ -33,4 +34,5 @@ rootProject.name = "propigator"

include(":common")
include(":json")
include(":yaml")
include(":yaml")
include(":cbor")
Loading