diff --git a/.github/scripts/update-readme-version.sh b/.github/scripts/update-readme-version.sh
index fcfdc8b..425afe3 100755
--- a/.github/scripts/update-readme-version.sh
+++ b/.github/scripts/update-readme-version.sh
@@ -1,13 +1,13 @@
#!/bin/bash
#
-# Copyright 2024 Kazimierz Pogoda / Xemantic
+# Copyright 2024-2025 Kazimierz Pogoda / Xemantic
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
@@ -47,19 +47,19 @@ fi
ESCAPED_GROUP_ID=$(echo "$GROUP_ID" | sed 's/\./\\./g')
# Create the pattern to match
-PATTERN="\"$ESCAPED_GROUP_ID:$ARTIFACT_ID:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\""
+PATTERN="\"$ESCAPED_GROUP_ID:$ARTIFACT_ID:[0-9]+(\.[0-9]+){0,2}\""
# Create the replacement string
REPLACEMENT="\"$GROUP_ID:$ARTIFACT_ID:$VERSION\""
# Check if the pattern exists in the file
-if ! grep -q "$GROUP_ID:$ARTIFACT_ID:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*" README.md; then
+if ! grep -Eq "$GROUP_ID:$ARTIFACT_ID:[0-9]+(\.[0-9]+){0,2}" README.md; then
echo "Error: Dependency pattern not found in README.md"
exit 1
fi
# Perform the replacement and save to a temporary file
-sed "s|$PATTERN|$REPLACEMENT|g" README.md > README.md.tmp
+sed -E "s|$PATTERN|$REPLACEMENT|g" README.md > README.md.tmp
# Check if sed made any changes
if cmp -s README.md README.md.tmp; then
diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml
index ddf4930..bd6d430 100644
--- a/.github/workflows/build-branch.yml
+++ b/.github/workflows/build-branch.yml
@@ -11,17 +11,17 @@ jobs:
build_branch:
runs-on: ubuntu-latest
steps:
- - name: Checkout sources
- uses: actions/checkout@v4.2.2
+ - name: Checkout sources
+ uses: actions/checkout@v4.2.2
- - name: Setup Java
- uses: actions/setup-java@v4.6.0
- with:
- distribution: 'temurin'
- java-version: 23
+ - name: Setup Java
+ uses: actions/setup-java@v4.6.0
+ with:
+ distribution: 'temurin'
+ java-version: 23
- - name: Setup Gradle
- uses: gradle/actions/setup-gradle@v4.2.2
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4.2.2
- - name: Build
- run: ./gradlew build
+ - name: Build
+ run: ./gradlew build
diff --git a/.github/workflows/build-main.yml b/.github/workflows/build-main.yml
index f790bec..c9d6605 100644
--- a/.github/workflows/build-main.yml
+++ b/.github/workflows/build-main.yml
@@ -7,20 +7,20 @@ jobs:
build_main:
runs-on: ubuntu-latest
steps:
- - name: Checkout sources
- uses: actions/checkout@v4.2.2
+ - name: Checkout sources
+ uses: actions/checkout@v4.2.2
- - name: Setup Java
- uses: actions/setup-java@v4.6.0
- with:
- distribution: 'temurin'
- java-version: 23
+ - name: Setup Java
+ uses: actions/setup-java@v4.6.0
+ with:
+ distribution: 'temurin'
+ java-version: 23
- - name: Setup Gradle
- uses: gradle/actions/setup-gradle@v4.2.2
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4.2.2
- - name: Build
- run: ./gradlew build sourcesJar dokkaGeneratePublicationHtml publish
- env:
- ORG_GRADLE_PROJECT_githubActor: ${{ secrets.GITHUBACTOR }}
- ORG_GRADLE_PROJECT_githubToken: ${{ secrets.GITHUBTOKEN }}
+ - name: Build
+ run: ./gradlew build sourcesJar dokkaGeneratePublicationHtml publish
+ env:
+ ORG_GRADLE_PROJECT_githubActor: ${{ secrets.GITHUBACTOR }}
+ ORG_GRADLE_PROJECT_githubToken: ${{ secrets.GITHUBTOKEN }}
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index c397fe4..c755dfd 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -1,7 +1,7 @@
name: Build release
on:
release:
- types: [published]
+ types: [ published ]
jobs:
build_release:
runs-on: ubuntu-latest
@@ -10,48 +10,48 @@ jobs:
# added or changed files to the repository.
contents: write
steps:
- - name: Write release version
- run: |
- VERSION=${GITHUB_REF_NAME#v}
- echo Version: $VERSION
- echo "VERSION=$VERSION" >> $GITHUB_ENV
+ - name: Write release version
+ run: |
+ VERSION=${GITHUB_REF_NAME#v}
+ echo Version: $VERSION
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
- - name: Checkout sources
- uses: actions/checkout@v4.2.2
- with:
- ref: ${{ github.head_ref }}
- fetch-depth: 0
+ - name: Checkout sources
+ uses: actions/checkout@v4.2.2
+ with:
+ ref: ${{ github.head_ref }}
+ fetch-depth: 0
- - name: Setup Java
- uses: actions/setup-java@v4.6.0
- with:
- distribution: 'temurin'
- java-version: 23
+ - name: Setup Java
+ uses: actions/setup-java@v4.6.0
+ with:
+ distribution: 'temurin'
+ java-version: 23
- - name: Setup Gradle
- uses: gradle/actions/setup-gradle@v4.2.2
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v4.2.2
- - name: Build
- env:
- ORG_GRADLE_PROJECT_githubActor: ${{ secrets.GITHUBACTOR }}
- ORG_GRADLE_PROJECT_githubToken: ${{ secrets.GITHUBTOKEN }}
- ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
- ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
- ORG_GRADLE_PROJECT_sonatypeUser: ${{ secrets.SONATYPE_USER }}
- ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
- run: ./gradlew -Pversion=$VERSION build sourcesJar dokkaGeneratePublicationHtml publishToSonatype closeAndReleaseSonatypeStagingRepository
+ - name: Build
+ env:
+ ORG_GRADLE_PROJECT_githubActor: ${{ secrets.GITHUBACTOR }}
+ ORG_GRADLE_PROJECT_githubToken: ${{ secrets.GITHUBTOKEN }}
+ ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
+ ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
+ ORG_GRADLE_PROJECT_sonatypeUser: ${{ secrets.SONATYPE_USER }}
+ ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
+ run: ./gradlew -Pversion=$VERSION build sourcesJar dokkaGeneratePublicationHtml publishToSonatype closeAndReleaseSonatypeStagingRepository
- - name: Checkout main branch
- uses: actions/checkout@v4.2.2
- with:
- ref: main
- fetch-depth: 0
+ - name: Checkout main branch
+ uses: actions/checkout@v4.2.2
+ with:
+ ref: main
+ fetch-depth: 0
- - name: Update README
- run: sh .github/scripts/update-readme-version.sh
+ - name: Update README
+ run: sh .github/scripts/update-readme-version.sh
- - name: Commit README
- uses: stefanzweifel/git-auto-commit-action@v5.0.1
- with:
- commit_message: Dependency version in README.md updated to ${{ env.VERSION }}
- file_pattern: 'README.md'
+ - name: Commit README
+ uses: stefanzweifel/git-auto-commit-action@v5.0.1
+ with:
+ commit_message: Dependency version in README.md updated to ${{ env.VERSION }}
+ file_pattern: 'README.md'
diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml
index 2e19eca..a1e6a9f 100644
--- a/.github/workflows/updater.yml
+++ b/.github/workflows/updater.yml
@@ -4,7 +4,7 @@ name: GitHub Actions Version Updater
on:
schedule:
# Automatically run on every Sunday
- - cron: '0 0 * * 0'
+ - cron: '0 0 * * 0'
jobs:
build:
diff --git a/.idea/copyright/apache2_0.xml b/.idea/copyright/apache2_0.xml
index 82555f1..990430d 100644
--- a/.idea/copyright/apache2_0.xml
+++ b/.idea/copyright/apache2_0.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c8b5f48..6e247af 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# xemantic-ai-money
-Kotlin multiplatform library for real-time calculation of LLM usage costs
+Real-time calculation of LLM usage costs - a Kotlin multiplatform library
[
](https://central.sonatype.com/namespace/com.xemantic.ai)
[
](https://github.com/xemantic/xemantic-ai-money/releases)
@@ -22,22 +22,16 @@ Kotlin multiplatform library for real-time calculation of LLM usage costs
## Why?
-The APIs of AI companies, like [OpenAI API](https://platform.openai.com/docs/api-reference/introduction)
-and [Anthropic API](https://docs.anthropic.com/en/api/getting-started), are providing the usage information regarding input and output tokens
-associated with each API call. Depending on many factors, like the model being used, batch
-processing, involved cache, etc., these tokens can be billed according to particular rules.
+The APIs of AI companies, like [OpenAI API](https://platform.openai.com/docs/api-reference/introduction) and [Anthropic API](https://docs.anthropic.com/en/api/getting-started), are providing the usage information regarding input and output tokens associated with each API call.
+Depending on many factors, like the model being used, batch processing, involved cache, etc., these tokens can be billed according to particular rules.
This library is fulfilling the need of proper accounting of API usage by:
-* representing monetary amounts as [Money](src/commonMain/kotlin/Money.kt) interface,
- supporting big decimal arithmetics and operator overloading,
-* representing ratios of monetary amounts (e.g. input token cost for given model)
- as [Money.Ratio](src/commonMain/kotlin/Money.kt) interface.
-
+* representing monetary amounts as [Money](src/commonMain/kotlin/Money.kt) interface, supporting big decimal arithmetics and operator overloading,
+* representing ratios of monetary amounts (e.g. input token cost for given model) as [Money.Ratio](src/commonMain/kotlin/Money.kt) interface.
> [!NOTE]
-> The `xemantic-ai-money` was initially a part of the
-> [anthropic-sdk-kotlin](https://github.com/xemantic/anthropic-sdk-kotlin), but was eventually externalized,
-> as a common functionality applicable across various API-related use cases.
+> The `xemantic-ai-money` was initially a part of the [anthropic-sdk-kotlin](https://github.com/xemantic/anthropic-sdk-kotlin), but was eventually externalized, as a
+> common functionality applicable across various API-related use cases.
## Usage
@@ -45,7 +39,7 @@ In `build.gradle.kts` add:
```kotlin
dependencies {
- implementation("com.xemantic.ai:xemantic-ai-money:0.2")
+ implementation("com.xemantic.ai:xemantic-ai-money:0.2")
}
```
@@ -53,8 +47,7 @@ See [test cases](src/commonTest/kotlin) for further information.
## Big decimal arithmetics
-The implementation of big decimal arithmetic in use will depend on the multiplatform
-targets:
+The implementation of big decimal arithmetic in use will depend on the multiplatform targets:
* `java.math.BigDecimal` for JVM target, for maximal performance and stability.
* [kotlin-multiplatform-bignum](https://github.com/ionspin/kotlin-multiplatform-bignum) for non-JVM targets.
diff --git a/api/xemantic-ai-money.api b/api/xemantic-ai-money.api
index 93f844e..ef66cf9 100644
--- a/api/xemantic-ai-money.api
+++ b/api/xemantic-ai-money.api
@@ -11,15 +11,6 @@ public final class com/xemantic/ai/money/JvmMoney : com/xemantic/ai/money/Money
public fun toString ()Ljava/lang/String;
}
-public final class com/xemantic/ai/money/JvmMoneyKt {
- public static final fun Money (Ljava/lang/String;)Lcom/xemantic/ai/money/Money;
- public static final fun Ratio (Lcom/xemantic/ai/money/Money$Companion;Ljava/lang/String;)Lcom/xemantic/ai/money/Money$Ratio;
- public static final fun getONE (Lcom/xemantic/ai/money/Money$Companion;)Lcom/xemantic/ai/money/Money;
- public static final fun getONE (Lcom/xemantic/ai/money/Money$Ratio$Companion;)Lcom/xemantic/ai/money/Money$Ratio;
- public static final fun getZERO (Lcom/xemantic/ai/money/Money$Companion;)Lcom/xemantic/ai/money/Money;
- public static final fun toMoneyRatio (I)Lcom/xemantic/ai/money/Money$Ratio;
-}
-
public abstract interface class com/xemantic/ai/money/Money {
public static final field Companion Lcom/xemantic/ai/money/Money$Companion;
public abstract fun compareTo (Lcom/xemantic/ai/money/Money;)I
@@ -52,6 +43,15 @@ public final class com/xemantic/ai/money/MoneyKt {
public static final fun times (ILcom/xemantic/ai/money/Money;)Lcom/xemantic/ai/money/Money;
}
+public final class com/xemantic/ai/money/Money_jvmKt {
+ public static final fun Money (Ljava/lang/String;)Lcom/xemantic/ai/money/Money;
+ public static final fun Ratio (Lcom/xemantic/ai/money/Money$Companion;Ljava/lang/String;)Lcom/xemantic/ai/money/Money$Ratio;
+ public static final fun getONE (Lcom/xemantic/ai/money/Money$Companion;)Lcom/xemantic/ai/money/Money;
+ public static final fun getONE (Lcom/xemantic/ai/money/Money$Ratio$Companion;)Lcom/xemantic/ai/money/Money$Ratio;
+ public static final fun getZERO (Lcom/xemantic/ai/money/Money$Companion;)Lcom/xemantic/ai/money/Money;
+ public static final fun toMoneyRatio (I)Lcom/xemantic/ai/money/Money$Ratio;
+}
+
public final class com/xemantic/ai/money/serialization/MoneyRatioSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lcom/xemantic/ai/money/serialization/MoneyRatioSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/xemantic/ai/money/Money$Ratio;
diff --git a/build.gradle.kts b/build.gradle.kts
index e8795ac..1b6c28f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,15 +9,15 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.swiftexport.ExperimentalSwiftExportDsl
plugins {
- alias(libs.plugins.kotlin.multiplatform)
- alias(libs.plugins.kotlin.plugin.serialization)
- alias(libs.plugins.kotlin.plugin.power.assert)
- alias(libs.plugins.kotlinx.binary.compatibility.validator)
- alias(libs.plugins.dokka)
- alias(libs.plugins.versions)
- `maven-publish`
- signing
- alias(libs.plugins.publish)
+ alias(libs.plugins.kotlin.multiplatform)
+ alias(libs.plugins.kotlin.plugin.serialization)
+ alias(libs.plugins.kotlin.plugin.power.assert)
+ alias(libs.plugins.kotlinx.binary.compatibility.validator)
+ alias(libs.plugins.dokka)
+ alias(libs.plugins.versions)
+ `maven-publish`
+ signing
+ alias(libs.plugins.publish)
}
val githubAccount = "xemantic"
@@ -33,7 +33,8 @@ val signingPassword: String? by project
val sonatypeUser: String? by project
val sonatypePassword: String? by project
-println("""
+println(
+"""
+--------------------------------------------
| Project: ${project.name}
| Version: ${project.version}
@@ -43,124 +44,124 @@ println("""
)
repositories {
- mavenCentral()
+ mavenCentral()
}
kotlin {
- applyDefaultHierarchyTemplate()
-
- explicitApi()
+ applyDefaultHierarchyTemplate()
- compilerOptions {
- apiVersion = kotlinTarget
- languageVersion = kotlinTarget
- freeCompilerArgs.add("-Xmulti-dollar-interpolation")
- extraWarnings.set(true)
- progressiveMode = true
- }
+ explicitApi()
- jvm {
- // set up according to https://jakewharton.com/gradle-toolchains-are-rarely-a-good-idea/
compilerOptions {
- apiVersion = kotlinTarget
- languageVersion = kotlinTarget
- jvmTarget = JvmTarget.fromTarget(javaTarget)
- freeCompilerArgs.add("-Xjdk-release=$javaTarget")
- progressiveMode = true
- }
- }
-
- js {
- browser()
- nodejs()
- binaries.library()
- }
-
- wasmJs {
- browser()
- nodejs()
- //d8()
- binaries.library()
- }
-
- wasmWasi {
- nodejs()
- binaries.library()
- }
-
- // native, see https://kotlinlang.org/docs/native-target-support.html
- // tier 1
- macosX64()
- macosArm64()
- iosSimulatorArm64()
- iosX64()
- iosArm64()
-
- // tier 2
- linuxX64()
- linuxArm64()
- watchosSimulatorArm64()
- watchosX64()
- watchosArm32()
- watchosArm64()
- tvosSimulatorArm64()
- tvosX64()
- tvosArm64()
-
- // tier 3
- androidNativeArm32()
- androidNativeArm64()
- androidNativeX86()
- androidNativeX64()
- mingwX64()
- // can be enabled once it is released in BigNum
- //watchosDeviceArm64()
-
- @OptIn(ExperimentalSwiftExportDsl::class)
- swiftExport {}
-
- sourceSets {
-
- commonMain {
- dependencies {
- implementation(libs.xemantic.ai.tool.schema)
- implementation(libs.kotlinx.serialization.core)
- }
+ apiVersion = kotlinTarget
+ languageVersion = kotlinTarget
+ freeCompilerArgs.add("-Xmulti-dollar-interpolation")
+ extraWarnings.set(true)
+ progressiveMode = true
}
- commonTest {
- dependencies {
- implementation(libs.kotlin.test)
- implementation(libs.kotlinx.serialization.json)
- implementation(libs.xemantic.kotlin.test)
- }
+ jvm {
+ // set up according to https://jakewharton.com/gradle-toolchains-are-rarely-a-good-idea/
+ compilerOptions {
+ apiVersion = kotlinTarget
+ languageVersion = kotlinTarget
+ jvmTarget = JvmTarget.fromTarget(javaTarget)
+ freeCompilerArgs.add("-Xjdk-release=$javaTarget")
+ progressiveMode = true
+ }
}
- val nonJvmMain by creating {
- dependsOn(commonMain.get())
- dependencies {
- implementation(libs.bignum)
- }
+ js {
+ browser()
+ nodejs()
+ binaries.library()
}
- nativeMain {
- dependsOn(nonJvmMain)
+ wasmJs {
+ browser()
+ nodejs()
+ //d8()
+ binaries.library()
}
- jsMain {
- dependsOn(nonJvmMain)
+ wasmWasi {
+ nodejs()
+ binaries.library()
}
- wasmJsMain {
- dependsOn(nonJvmMain)
- }
+ // native, see https://kotlinlang.org/docs/native-target-support.html
+ // tier 1
+ macosX64()
+ macosArm64()
+ iosSimulatorArm64()
+ iosX64()
+ iosArm64()
+
+ // tier 2
+ linuxX64()
+ linuxArm64()
+ watchosSimulatorArm64()
+ watchosX64()
+ watchosArm32()
+ watchosArm64()
+ tvosSimulatorArm64()
+ tvosX64()
+ tvosArm64()
+
+ // tier 3
+ androidNativeArm32()
+ androidNativeArm64()
+ androidNativeX86()
+ androidNativeX64()
+ mingwX64()
+ // can be enabled once it is released in BigNum
+ //watchosDeviceArm64()
+
+ @OptIn(ExperimentalSwiftExportDsl::class)
+ swiftExport {}
+
+ sourceSets {
+
+ commonMain {
+ dependencies {
+ implementation(libs.xemantic.ai.tool.schema)
+ implementation(libs.kotlinx.serialization.core)
+ }
+ }
- wasmWasiMain {
- dependsOn(nonJvmMain)
- }
+ commonTest {
+ dependencies {
+ implementation(libs.kotlin.test)
+ implementation(libs.kotlinx.serialization.json)
+ implementation(libs.xemantic.kotlin.test)
+ }
+ }
+
+ val nonJvmMain by creating {
+ dependsOn(commonMain.get())
+ dependencies {
+ implementation(libs.bignum)
+ }
+ }
- }
+ nativeMain {
+ dependsOn(nonJvmMain)
+ }
+
+ jsMain {
+ dependsOn(nonJvmMain)
+ }
+
+ wasmJsMain {
+ dependsOn(nonJvmMain)
+ }
+
+ wasmWasiMain {
+ dependsOn(nonJvmMain)
+ }
+
+ }
}
@@ -169,137 +170,137 @@ tasks.named("tvosSimulatorArm64Test") { enabled = false }
tasks.named("watchosSimulatorArm64Test") { enabled = false }
tasks.withType {
- testLogging {
- events(
- TestLogEvent.SKIPPED,
- TestLogEvent.FAILED
- )
- showStackTraces = true
- exceptionFormat = TestExceptionFormat.FULL
- }
+ testLogging {
+ events(
+ TestLogEvent.SKIPPED,
+ TestLogEvent.FAILED
+ )
+ showStackTraces = true
+ exceptionFormat = TestExceptionFormat.FULL
+ }
}
powerAssert {
- functions = listOf(
- "com.xemantic.kotlin.test.assert",
- "com.xemantic.kotlin.test.have"
- )
+ functions = listOf(
+ "com.xemantic.kotlin.test.assert",
+ "com.xemantic.kotlin.test.have"
+ )
}
// https://kotlinlang.org/docs/dokka-migration.html#adjust-configuration-options
dokka {
- pluginsConfiguration.html {
- footerMessage.set("(c) 2024 Xemantic")
- }
+ pluginsConfiguration.html {
+ footerMessage.set("(c) 2024 Xemantic")
+ }
}
val javadocJar by tasks.registering(Jar::class) {
- archiveClassifier.set("javadoc")
- from(tasks.dokkaGeneratePublicationHtml)
+ archiveClassifier.set("javadoc")
+ from(tasks.dokkaGeneratePublicationHtml)
}
publishing {
- repositories {
- if (!isReleaseBuild) {
- maven {
- name = "GitHubPackages"
- setUrl("https://maven.pkg.github.com/$githubAccount/${rootProject.name}")
- credentials {
- username = githubActor
- password = githubToken
+ repositories {
+ if (!isReleaseBuild) {
+ maven {
+ name = "GitHubPackages"
+ setUrl("https://maven.pkg.github.com/$githubAccount/${rootProject.name}")
+ credentials {
+ username = githubActor
+ password = githubToken
+ }
+ }
}
- }
}
- }
- publications {
- withType {
- artifact(javadocJar)
- pom {
- name = "xemantic-ai-money"
- description = "Kotlin multiplatform library for real-time calculation of LLM usage costs"
- url = "https://github.com/$githubAccount/${rootProject.name}"
- inceptionYear = "2024"
- organization {
- name = "Xemantic"
- url = "https://xemantic.com"
- }
- licenses {
- license {
- name = "The Apache Software License, Version 2.0"
- url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
- distribution = "repo"
- }
- }
- scm {
- url = "https://github.com/$githubAccount/${rootProject.name}"
- connection = "scm:git:git:github.com/$githubAccount/${rootProject.name}.git"
- developerConnection = "scm:git:https://github.com/$githubAccount/${rootProject.name}.git"
- }
- ciManagement {
- system = "GitHub"
- url = "https://github.com/$githubAccount/${rootProject.name}/actions"
- }
- issueManagement {
- system = "GitHub"
- url = "https://github.com/$githubAccount/${rootProject.name}/issues"
+ publications {
+ withType {
+ artifact(javadocJar)
+ pom {
+ name = "xemantic-ai-money"
+ description = "Real-time calculation of LLM usage costs - a Kotlin multiplatform library"
+ url = "https://github.com/$githubAccount/${rootProject.name}"
+ inceptionYear = "2024"
+ organization {
+ name = "Xemantic"
+ url = "https://xemantic.com"
+ }
+ licenses {
+ license {
+ name = "The Apache Software License, Version 2.0"
+ url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
+ distribution = "repo"
+ }
+ }
+ scm {
+ url = "https://github.com/$githubAccount/${rootProject.name}"
+ connection = "scm:git:git:github.com/$githubAccount/${rootProject.name}.git"
+ developerConnection = "scm:git:https://github.com/$githubAccount/${rootProject.name}.git"
+ }
+ ciManagement {
+ system = "GitHub"
+ url = "https://github.com/$githubAccount/${rootProject.name}/actions"
+ }
+ issueManagement {
+ system = "GitHub"
+ url = "https://github.com/$githubAccount/${rootProject.name}/issues"
+ }
+ developers {
+ developer {
+ id = "morisil"
+ name = "Kazik Pogoda"
+ email = "morisil@xemantic.com"
+ }
+ }
+ }
}
- developers {
- developer {
- id = "morisil"
- name = "Kazik Pogoda"
- email = "morisil@xemantic.com"
- }
- }
- }
}
- }
}
if (isReleaseBuild) {
- // workaround for KMP/gradle signing issue
- // https://github.com/gradle/gradle/issues/26091
- tasks {
- withType {
- dependsOn(withType())
+ // workaround for KMP/gradle signing issue
+ // https://github.com/gradle/gradle/issues/26091
+ tasks {
+ withType {
+ dependsOn(withType())
+ }
}
- }
- // Resolves issues with .asc task output of the sign task of native targets.
- // See: https://github.com/gradle/gradle/issues/26132
- // And: https://youtrack.jetbrains.com/issue/KT-46466
- tasks.withType().configureEach {
- val pubName = name.removePrefix("sign").removeSuffix("Publication")
+ // Resolves issues with .asc task output of the sign task of native targets.
+ // See: https://github.com/gradle/gradle/issues/26132
+ // And: https://youtrack.jetbrains.com/issue/KT-46466
+ tasks.withType().configureEach {
+ val pubName = name.removePrefix("sign").removeSuffix("Publication")
- // These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
+ // These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
- // Task ':linkDebugTest' uses this output of task ':signPublication' without declaring an explicit or implicit dependency
- tasks.findByName("linkDebugTest$pubName")?.let {
- mustRunAfter(it)
- }
- // Task ':compileTestKotlin' uses this output of task ':signPublication' without declaring an explicit or implicit dependency
- tasks.findByName("compileTestKotlin$pubName")?.let {
- mustRunAfter(it)
+ // Task ':linkDebugTest' uses this output of task ':signPublication' without declaring an explicit or implicit dependency
+ tasks.findByName("linkDebugTest$pubName")?.let {
+ mustRunAfter(it)
+ }
+ // Task ':compileTestKotlin' uses this output of task ':signPublication' without declaring an explicit or implicit dependency
+ tasks.findByName("compileTestKotlin$pubName")?.let {
+ mustRunAfter(it)
+ }
}
- }
- signing {
- useInMemoryPgpKeys(
- signingKey,
- signingPassword
- )
- sign(publishing.publications)
- }
+ signing {
+ useInMemoryPgpKeys(
+ signingKey,
+ signingPassword
+ )
+ sign(publishing.publications)
+ }
- nexusPublishing {
- repositories {
- sonatype { //only for users registered in Sonatype after 24 Feb 2021
- nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
- snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
- username.set(sonatypeUser)
- password.set(sonatypePassword)
- }
+ nexusPublishing {
+ repositories {
+ sonatype { //only for users registered in Sonatype after 24 Feb 2021
+ nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
+ snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
+ username.set(sonatypeUser)
+ password.set(sonatypePassword)
+ }
+ }
}
- }
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 884b7a0..72e4986 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,18 +3,18 @@ kotlinTarget = "2.1"
javaTarget = "17"
kotlin = "2.1.0"
-kotlinxSerialization = "1.7.3"
+kotlinxSerialization = "1.8.0"
xemanticAiToolSchema = "0.1.4"
-xemanticKotlinTest = "1.0"
+xemanticKotlinTest = "1.2"
bignum = "0.3.10"
versionsPlugin = "0.51.0"
-dokkaPlugin = "2.0.0-Beta"
+dokkaPlugin = "2.0.0"
publishPlugin = "2.0.0"
-binaryCompatibilityValidatorPlugin = "0.16.3"
+binaryCompatibilityValidatorPlugin = "0.17.0"
[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
@@ -22,7 +22,7 @@ kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serializa
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
xemantic-ai-tool-schema = { module = "com.xemantic.ai:xemantic-ai-tool-schema", version.ref = "xemanticAiToolSchema" }
-xemantic-kotlin-test = { module="com.xemantic.kotlin:xemantic-kotlin-test", version.ref="xemanticKotlinTest" }
+xemantic-kotlin-test = { module = "com.xemantic.kotlin:xemantic-kotlin-test", version.ref = "xemanticKotlinTest" }
bignum = { module = "com.ionspin.kotlin:bignum", version.ref = "bignum" }
@@ -33,4 +33,4 @@ kotlin-plugin-power-assert = { id = "org.jetbrains.kotlin.plugin.power-assert",
dokka = { id = "org.jetbrains.dokka", version.ref = "dokkaPlugin" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versionsPlugin" }
publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "publishPlugin" }
-kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref ="binaryCompatibilityValidatorPlugin" }
+kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index e2847c8..cea7a79 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index 21cee6a..f3b75f3 100755
--- a/gradlew
+++ b/gradlew
@@ -1,13 +1,13 @@
#!/bin/sh
#
-# Copyright 2024 Kazimierz Pogoda / Xemantic
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+# SPDX-License-Identifier: Apache-2.0
+#
##############################################################################
#
@@ -84,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
-' "$PWD" ) || exit
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
diff --git a/settings.gradle.kts b/settings.gradle.kts
index c104d4b..2d3fc98 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -3,5 +3,5 @@ val name = "xemantic-ai-money"
rootProject.name = name
gradle.beforeProject {
- group = groupId
+ group = groupId
}
diff --git a/src/commonMain/kotlin/Money.kt b/src/commonMain/kotlin/Money.kt
index 96c5302..a8f9bbf 100644
--- a/src/commonMain/kotlin/Money.kt
+++ b/src/commonMain/kotlin/Money.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -43,34 +43,34 @@ import kotlinx.serialization.Serializable
@Serializable(MoneySerializer::class)
public interface Money {
- public operator fun plus(money: Money): Money
+ public operator fun plus(money: Money): Money
- public operator fun minus(money: Money): Money
+ public operator fun minus(money: Money): Money
- public operator fun times(money: Money): Money
+ public operator fun times(money: Money): Money
- public operator fun times(ratio: Ratio): Money
+ public operator fun times(ratio: Ratio): Money
- public operator fun times(
- scalar: Int
- ): Money = this * scalar.toMoneyRatio()
+ public operator fun times(
+ scalar: Int
+ ): Money = this * scalar.toMoneyRatio()
- public operator fun compareTo(money: Money): Int
+ public operator fun compareTo(money: Money): Int
- /**
- * A ratio to multiply Money. It can be used for representing
- * small ratios, like price per LLM token.
- */
- @Description("Represents a ratio used to multiply Money")
- @Pattern($$"""^-?\d*\.?\d+$""")
- @Serializable(MoneyRatioSerializer::class)
- public interface Ratio {
+ /**
+ * A ratio to multiply Money. It can be used for representing
+ * small ratios, like price per LLM token.
+ */
+ @Description("Represents a ratio used to multiply Money")
+ @Pattern($$"""^-?\d*\.?\d+$""")
+ @Serializable(MoneyRatioSerializer::class)
+ public interface Ratio {
- public operator fun times(money: Money): Money
+ public operator fun times(money: Money): Money
- public companion object
+ public companion object
- }
+ }
}
diff --git a/src/commonMain/kotlin/serialization/MoneySerialization.kt b/src/commonMain/kotlin/serialization/MoneySerialization.kt
index f66664c..d3d2b50 100644
--- a/src/commonMain/kotlin/serialization/MoneySerialization.kt
+++ b/src/commonMain/kotlin/serialization/MoneySerialization.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -30,50 +30,50 @@ import kotlinx.serialization.encoding.Encoder
public object MoneySerializer : KSerializer {
- // we can autogenerate a serializer which will retain annotations of serialized class
- @OptIn(ExperimentalSerializationApi::class)
- @Serializer(forClass = Money::class)
- private object AutoSerializer
+ // we can autogenerate a serializer which will retain annotations of serialized class
+ @OptIn(ExperimentalSerializationApi::class)
+ @Serializer(forClass = Money::class)
+ private object AutoSerializer
- @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
- public override val descriptor: SerialDescriptor = buildSerialDescriptor(
- serialName = AutoSerializer.descriptor.serialName,
- kind = PrimitiveKind.STRING
- ) {
- annotations = AutoSerializer.descriptor.annotations
- }
+ @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
+ public override val descriptor: SerialDescriptor = buildSerialDescriptor(
+ serialName = AutoSerializer.descriptor.serialName,
+ kind = PrimitiveKind.STRING
+ ) {
+ annotations = AutoSerializer.descriptor.annotations
+ }
- override fun serialize(encoder: Encoder, value: Money) {
- encoder.encodeString(value.toString())
- }
+ override fun serialize(encoder: Encoder, value: Money) {
+ encoder.encodeString(value.toString())
+ }
- override fun deserialize(
- decoder: Decoder
- ): Money = Money(decoder.decodeString())
+ override fun deserialize(
+ decoder: Decoder
+ ): Money = Money(decoder.decodeString())
}
public object MoneyRatioSerializer : KSerializer {
- // we can autogenerate a serializer which will retain annotations of serialized class
- @OptIn(ExperimentalSerializationApi::class)
- @Serializer(forClass = Money.Ratio::class)
- private object AutoSerializer
+ // we can autogenerate a serializer which will retain annotations of serialized class
+ @OptIn(ExperimentalSerializationApi::class)
+ @Serializer(forClass = Money.Ratio::class)
+ private object AutoSerializer
- @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
- public override val descriptor: SerialDescriptor = buildSerialDescriptor(
- serialName = AutoSerializer.descriptor.serialName,
- kind = PrimitiveKind.STRING
- ) {
- annotations = AutoSerializer.descriptor.annotations
- }
+ @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
+ public override val descriptor: SerialDescriptor = buildSerialDescriptor(
+ serialName = AutoSerializer.descriptor.serialName,
+ kind = PrimitiveKind.STRING
+ ) {
+ annotations = AutoSerializer.descriptor.annotations
+ }
- override fun serialize(encoder: Encoder, value: Money.Ratio) {
- encoder.encodeString(value.toString())
- }
+ override fun serialize(encoder: Encoder, value: Money.Ratio) {
+ encoder.encodeString(value.toString())
+ }
- override fun deserialize(
- decoder: Decoder
- ): Money.Ratio = Money.Ratio(decoder.decodeString())
+ override fun deserialize(
+ decoder: Decoder
+ ): Money.Ratio = Money.Ratio(decoder.decodeString())
}
diff --git a/src/commonTest/kotlin/MoneyRatioTest.kt b/src/commonTest/kotlin/MoneyRatioTest.kt
index 5c0270c..b23f2f7 100644
--- a/src/commonTest/kotlin/MoneyRatioTest.kt
+++ b/src/commonTest/kotlin/MoneyRatioTest.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,51 +22,51 @@ import kotlin.test.assertFailsWith
class MoneyRatioTest {
- @Test
- fun `Should create Money Ratio instance from String`() {
- assert(Money.Ratio("1.25").toString() == "1.25")
- }
+ @Test
+ fun `Should create Money Ratio instance from String`() {
+ assert(Money.Ratio("1.25").toString() == "1.25")
+ }
- @Test
- fun `Should not create Money Ratio instance from invalid String`() {
- assertFailsWith {
- Money.Ratio("foo")
+ @Test
+ fun `Should not create Money Ratio instance from invalid String`() {
+ assertFailsWith {
+ Money.Ratio("foo")
+ }
}
- }
- @Test
- fun `Should create Money Ratio instance with long fractional part`() {
- assert(Money.Ratio("0.0000001").toString() == "0.0000001")
- }
+ @Test
+ fun `Should create Money Ratio instance with long fractional part`() {
+ assert(Money.Ratio("0.0000001").toString() == "0.0000001")
+ }
- @Test
- fun `Should compare Money Ratio instances`() {
- assert(Money.Ratio.ONE == Money.Ratio("1"))
- assert(Money.Ratio("0") == Money.Ratio("0.0"))
- assert(Money.Ratio("0.1") == Money.Ratio("0.100000"))
- }
+ @Test
+ fun `Should compare Money Ratio instances`() {
+ assert(Money.Ratio.ONE == Money.Ratio("1"))
+ assert(Money.Ratio("0") == Money.Ratio("0.0"))
+ assert(Money.Ratio("0.1") == Money.Ratio("0.100000"))
+ }
- @Test
- fun `Should multiple Money Ratio and Money`() {
- assert(Money.Ratio("0.000001") * Money("3.0") == Money("0.000003"))
- }
+ @Test
+ fun `Should multiple Money Ratio and Money`() {
+ assert(Money.Ratio("0.000001") * Money("3.0") == Money("0.000003"))
+ }
- @Test
- fun `Should convert valid string to Money Ratio`() {
- assert("0.000001".moneyRatio == Money.Ratio("0.000001"))
- }
+ @Test
+ fun `Should convert valid string to Money Ratio`() {
+ assert("0.000001".moneyRatio == Money.Ratio("0.000001"))
+ }
- @Test
- fun `Should not convert invalid string to Money Ratio`() {
- assertFailsWith {
- "foo".moneyRatio
+ @Test
+ fun `Should not convert invalid string to Money Ratio`() {
+ assertFailsWith {
+ "foo".moneyRatio
+ }
}
- }
- @Test
- fun `Should convert Int to Money Ratio`() {
- assert(1.toMoneyRatio() == Money.Ratio("1"))
- assert(42.toMoneyRatio() == Money.Ratio("42"))
- }
+ @Test
+ fun `Should convert Int to Money Ratio`() {
+ assert(1.toMoneyRatio() == Money.Ratio("1"))
+ assert(42.toMoneyRatio() == Money.Ratio("42"))
+ }
}
diff --git a/src/commonTest/kotlin/MoneyTest.kt b/src/commonTest/kotlin/MoneyTest.kt
index 9f6ee15..b466417 100644
--- a/src/commonTest/kotlin/MoneyTest.kt
+++ b/src/commonTest/kotlin/MoneyTest.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,84 +22,84 @@ import kotlin.test.assertFailsWith
class MoneyTest {
- @Test
- fun `Should create Money instance from String`() {
- assert(Money("42.50").toString() == "42.5")
- }
+ @Test
+ fun `Should create Money instance from String`() {
+ assert(Money("42.50").toString() == "42.5")
+ }
+
+ @Test
+ fun `Should not create Money instance from invalid String`() {
+ assertFailsWith {
+ Money.Ratio("foo")
+ }
+ }
+
+ @Test
+ fun `Should create Money instance with long fractional part`() {
+ assert(Money("42.123456789").toString() == "42.123456789")
+ }
+
+ @Test
+ fun `Should compare Money instances`() {
+ assert(Money.ZERO == Money("0"))
+ assert(Money.ONE == Money("1"))
+ assert(Money.ONE == Money("1.0"))
+ assert(Money("0.1") == Money("0.100"))
+ }
+
+ @Test
+ fun `Should add Moneys`() {
+ assert(Money("10.25") + Money("5.75") == Money("16.00"))
+ }
+
+ @Test
+ fun `Should subtract Moneys`() {
+ assert(Money("20.00") - Money("7.50") == Money("12.50"))
+ }
+
+ @Test
+ fun `Should multiply Moneys`() {
+ assert(Money("5.01") * Money("3.00") == Money("15.03"))
+ }
+
+ @Test
+ fun `Should multiply Money by Int Ratio scalar`() {
+ assert(Money("5.01") * Money.Ratio("3") == Money("15.03"))
+ }
+
+ @Test
+ fun `Should multiply Money by proportion Ratio`() {
+ assert(Money("2.0") * Money.Ratio("1.25") == Money("2.5"))
+ }
+
+ @Test
+ fun `Should create Money instance for token price`() {
+ assert(Money("3.0") * Money.Ratio("0.000001") == Money("0.000003"))
+ }
+
+ @Test
+ fun `Should compare different Money amounts`() {
+ // given
+ val money1 = Money("10.00")
+ val money2 = Money("10.00")
+ val money3 = Money("20.00")
+
+ // then
+ assert(money1 == money2)
+ assert(money1 < money3)
+ assert(money3 > money1)
+ assert(money1 < money3)
+ assert(money3 > money2)
+ }
+
+ @Test
+ fun `Should multiply Int and Money`() {
+ assert(2 * Money("2.25") == Money("4.5"))
+ }
- @Test
- fun `Should not create Money instance from invalid String`() {
- assertFailsWith {
- Money.Ratio("foo")
+ @Test
+ fun `Should multiply Money and Int`() {
+ assert(Money("2.25") * 2 == Money("4.5"))
}
- }
-
- @Test
- fun `Should create Money instance with long fractional part`() {
- assert(Money("42.123456789").toString() == "42.123456789")
- }
-
- @Test
- fun `Should compare Money instances`() {
- assert(Money.ZERO == Money("0"))
- assert(Money.ONE == Money("1"))
- assert(Money.ONE == Money("1.0"))
- assert(Money("0.1") == Money("0.100"))
- }
-
- @Test
- fun `Should add Moneys`() {
- assert(Money("10.25") + Money("5.75") == Money("16.00"))
- }
-
- @Test
- fun `Should subtract Moneys`() {
- assert(Money("20.00") - Money("7.50") == Money("12.50"))
- }
-
- @Test
- fun `Should multiply Moneys`() {
- assert(Money("5.01") * Money("3.00") == Money("15.03"))
- }
-
- @Test
- fun `Should multiply Money by Int Ratio scalar`() {
- assert(Money("5.01") * Money.Ratio("3") == Money("15.03"))
- }
-
- @Test
- fun `Should multiply Money by proportion Ratio`() {
- assert(Money("2.0") * Money.Ratio("1.25") == Money("2.5"))
- }
-
- @Test
- fun `Should create Money instance for token price`() {
- assert(Money("3.0") * Money.Ratio("0.000001") == Money("0.000003"))
- }
-
- @Test
- fun `Should compare different Money amounts`() {
- // given
- val money1 = Money("10.00")
- val money2 = Money("10.00")
- val money3 = Money("20.00")
-
- // then
- assert(money1 == money2)
- assert(money1 < money3)
- assert(money3 > money1)
- assert(money1 < money3)
- assert(money3 > money2)
- }
-
- @Test
- fun `Should multiply Int and Money`() {
- assert(2 * Money("2.25") == Money("4.5"))
- }
-
- @Test
- fun `Should multiply Money and Int`() {
- assert(Money("2.25") * 2 == Money("4.5"))
- }
}
diff --git a/src/commonTest/kotlin/serialization/MoneyRatioSerializationTest.kt b/src/commonTest/kotlin/serialization/MoneyRatioSerializationTest.kt
index 8cb0792..d214f37 100644
--- a/src/commonTest/kotlin/serialization/MoneyRatioSerializationTest.kt
+++ b/src/commonTest/kotlin/serialization/MoneyRatioSerializationTest.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -25,41 +25,40 @@ import com.xemantic.kotlin.test.assert
import com.xemantic.kotlin.test.be
import com.xemantic.kotlin.test.should
import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.encodeToString
import kotlin.test.Test
class MoneyRatioSerializationTest {
- @Test
- fun `Should serialize Money Ratio to JSON`() {
- assert(
- testJson.encodeToString(
- Money.Ratio("0.000001")
- ) == """"0.000001""""
- )
- }
-
- @Test
- fun `Should deserialize Money Ratio from JSON`() {
- assert(
- testJson.decodeFromString(
- """"0.000001""""
- ) == Money.Ratio("0.000001")
- )
- }
+ @Test
+ fun `Should serialize Money Ratio to JSON`() {
+ assert(
+ testJson.encodeToString(
+ Money.Ratio("0.000001")
+ ) == """"0.000001""""
+ )
+ }
- @Test
- fun `Should preserve tool schema annotations of Money Ratio`() {
- @OptIn(ExperimentalSerializationApi::class)
- val meta = Money.Ratio.serializer().descriptor.annotations
- meta[0] should {
- be()
- assert(value == "Represents a ratio used to multiply Money")
+ @Test
+ fun `Should deserialize Money Ratio from JSON`() {
+ assert(
+ testJson.decodeFromString(
+ """"0.000001""""
+ ) == Money.Ratio("0.000001")
+ )
}
- meta[1] should {
- be()
- assert(regex == $$"""^-?\d*\.?\d+$""")
+
+ @Test
+ fun `Should preserve tool schema annotations of Money Ratio`() {
+ @OptIn(ExperimentalSerializationApi::class)
+ val meta = Money.Ratio.serializer().descriptor.annotations
+ meta[0] should {
+ be()
+ assert(value == "Represents a ratio used to multiply Money")
+ }
+ meta[1] should {
+ be()
+ assert(regex == $$"""^-?\d*\.?\d+$""")
+ }
}
- }
}
diff --git a/src/commonTest/kotlin/serialization/MoneySerializationTest.kt b/src/commonTest/kotlin/serialization/MoneySerializationTest.kt
index 4d3cdad..19399b5 100644
--- a/src/commonTest/kotlin/serialization/MoneySerializationTest.kt
+++ b/src/commonTest/kotlin/serialization/MoneySerializationTest.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -25,41 +25,40 @@ import com.xemantic.kotlin.test.be
import com.xemantic.kotlin.test.have
import com.xemantic.kotlin.test.should
import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.encodeToString
import kotlin.test.Test
class MoneySerializationTest {
- @Test
- fun `Should serialize Money to JSON`() {
- assert(
- testJson.encodeToString(
- Money("42.50")
- ) == """"42.5""""
- )
- }
-
- @Test
- fun `Should deserialize Money from JSON`() {
- assert(
- testJson.decodeFromString(
- """"42.5""""
- ) == Money("42.5")
- )
- }
+ @Test
+ fun `Should serialize Money to JSON`() {
+ assert(
+ testJson.encodeToString(
+ Money("42.50")
+ ) == """"42.5""""
+ )
+ }
- @Test
- fun `Should preserve tool schema annotations of Money`() {
- @OptIn(ExperimentalSerializationApi::class)
- val meta = Money.serializer().descriptor.annotations
- meta[0] should {
- be()
- have(value == "Represents a monetary amount with arbitrary precision and no currency information")
+ @Test
+ fun `Should deserialize Money from JSON`() {
+ assert(
+ testJson.decodeFromString(
+ """"42.5""""
+ ) == Money("42.5")
+ )
}
- meta[1] should {
- be()
- have(regex == $$"""^-?\d*\.?\d+$""")
+
+ @Test
+ fun `Should preserve tool schema annotations of Money`() {
+ @OptIn(ExperimentalSerializationApi::class)
+ val meta = Money.serializer().descriptor.annotations
+ meta[0] should {
+ be()
+ have(value == "Represents a monetary amount with arbitrary precision and no currency information")
+ }
+ meta[1] should {
+ be()
+ have(regex == $$"""^-?\d*\.?\d+$""")
+ }
}
- }
}
diff --git a/src/commonTest/kotlin/test/TestSupport.kt b/src/commonTest/kotlin/test/TestSupport.kt
index 9e932f7..3376c33 100644
--- a/src/commonTest/kotlin/test/TestSupport.kt
+++ b/src/commonTest/kotlin/test/TestSupport.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -23,7 +23,7 @@ import kotlinx.serialization.json.Json
* A pretty printing [Json] for tests.
*/
val testJson = Json {
- prettyPrint = true
- @OptIn(ExperimentalSerializationApi::class)
- prettyPrintIndent = " "
+ prettyPrint = true
+ @OptIn(ExperimentalSerializationApi::class)
+ prettyPrintIndent = " "
}
diff --git a/src/jvmMain/kotlin/JvmMoney.kt b/src/jvmMain/kotlin/Money.jvm.kt
similarity index 50%
rename from src/jvmMain/kotlin/JvmMoney.kt
rename to src/jvmMain/kotlin/Money.jvm.kt
index 9c536ab..86482a0 100644
--- a/src/jvmMain/kotlin/JvmMoney.kt
+++ b/src/jvmMain/kotlin/Money.jvm.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,68 +17,65 @@
package com.xemantic.ai.money
import java.math.BigDecimal
-import kotlin.minus
-import kotlin.plus
-import kotlin.times
public class JvmMoney(private val value: BigDecimal) : Money {
- override fun plus(amount: Money): Money = JvmMoney(
- (value + (amount as JvmMoney).value).stripTrailingZeros()
- )
-
- override fun minus(amount: Money): Money = JvmMoney(
- (value - (amount as JvmMoney).value).stripTrailingZeros()
- )
-
- override fun times(amount: Money): Money = JvmMoney(
- (value * (amount as JvmMoney).value).stripTrailingZeros()
- )
-
- override fun times(ratio: Money.Ratio): Money = JvmMoney(
- (value * (ratio as JvmRatio).value).stripTrailingZeros()
- )
-
- override fun compareTo(
- other: Money
- ): Int = value.compareTo((other as JvmMoney).value)
-
- override fun toString(): String {
- return value.toPlainString()
- }
-
- override fun equals(
- other: Any?
- ): Boolean = (other != null)
- && (other is JvmMoney)
- && (value == other.value)
-
- override fun hashCode(): Int = value.hashCode()
+ override fun plus(amount: Money): Money = JvmMoney(
+ (value + (amount as JvmMoney).value).stripTrailingZeros()
+ )
- internal class JvmRatio(internal val value: BigDecimal) : Money.Ratio {
+ override fun minus(amount: Money): Money = JvmMoney(
+ (value - (amount as JvmMoney).value).stripTrailingZeros()
+ )
override fun times(amount: Money): Money = JvmMoney(
- (value * (amount as JvmMoney).value).stripTrailingZeros()
+ (value * (amount as JvmMoney).value).stripTrailingZeros()
)
+ override fun times(ratio: Money.Ratio): Money = JvmMoney(
+ (value * (ratio as JvmRatio).value).stripTrailingZeros()
+ )
+
+ override fun compareTo(
+ other: Money
+ ): Int = value.compareTo((other as JvmMoney).value)
+
override fun toString(): String {
- return value.toPlainString()
+ return value.toPlainString()
}
override fun equals(
- other: Any?
+ other: Any?
): Boolean = (other != null)
- && (other is JvmRatio)
- && (value == other.value)
+ && (other is JvmMoney)
+ && (value == other.value)
override fun hashCode(): Int = value.hashCode()
- }
+ internal class JvmRatio(internal val value: BigDecimal) : Money.Ratio {
+
+ override fun times(amount: Money): Money = JvmMoney(
+ (value * (amount as JvmMoney).value).stripTrailingZeros()
+ )
+
+ override fun toString(): String {
+ return value.toPlainString()
+ }
+
+ override fun equals(
+ other: Any?
+ ): Boolean = (other != null)
+ && (other is JvmRatio)
+ && (value == other.value)
+
+ override fun hashCode(): Int = value.hashCode()
+
+ }
}
public actual fun Money(amount: String): Money = JvmMoney(
- BigDecimal(amount).stripTrailingZeros()
+ BigDecimal(amount).stripTrailingZeros()
)
@Suppress("ObjectPropertyName")
@@ -90,9 +87,9 @@ private val _ONE = JvmMoney(BigDecimal.ONE)
public actual val Money.Companion.ONE: Money get() = _ONE
public actual fun Money.Companion.Ratio(
- value: String
+ value: String
): Money.Ratio = JvmMoney.JvmRatio(
- BigDecimal(value).stripTrailingZeros()
+ BigDecimal(value).stripTrailingZeros()
)
@Suppress("ObjectPropertyName")
@@ -100,4 +97,4 @@ private val _RATIO_ONE = JvmMoney.JvmRatio(BigDecimal.ONE)
public actual val Money.Ratio.Companion.ONE: Money.Ratio get() = _RATIO_ONE
public actual fun Int.toMoneyRatio(): Money.Ratio =
- JvmMoney.JvmRatio(BigDecimal(this))
+ JvmMoney.JvmRatio(BigDecimal(this))
diff --git a/src/nonJvmMain/kotlin/NonJvmMoney.kt b/src/nonJvmMain/kotlin/Money.nonJvm.kt
similarity index 52%
rename from src/nonJvmMain/kotlin/NonJvmMoney.kt
rename to src/nonJvmMain/kotlin/Money.nonJvm.kt
index 025b6c5..25d94b0 100644
--- a/src/nonJvmMain/kotlin/NonJvmMoney.kt
+++ b/src/nonJvmMain/kotlin/Money.nonJvm.kt
@@ -1,11 +1,11 @@
/*
- * Copyright 2024 Kazimierz Pogoda / Xemantic
+ * Copyright 2024-2025 Kazimierz Pogoda / Xemantic
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -20,62 +20,62 @@ import com.ionspin.kotlin.bignum.decimal.BigDecimal
public class NonJvmMoney(private val value: BigDecimal) : Money {
- override fun plus(amount: Money): Money = NonJvmMoney(
- value + (amount as NonJvmMoney).value
- )
-
- override fun minus(amount: Money): Money = NonJvmMoney(
- value - (amount as NonJvmMoney).value
- )
-
- override fun times(amount: Money): Money = NonJvmMoney(
- value * (amount as NonJvmMoney).value
- )
-
- override fun times(ratio: Money.Ratio): Money = NonJvmMoney(
- value * (ratio as NonJvmRatio).value
- )
-
- override fun compareTo(
- other: Money
- ): Int = value.compareTo((other as NonJvmMoney).value)
-
- override fun toString(): String {
- return value.toPlainString()
- }
-
- override fun equals(
- other: Any?
- ): Boolean = (other != null)
- && (other is NonJvmMoney)
- && (value == other.value)
-
- override fun hashCode(): Int = value.hashCode()
+ override fun plus(amount: Money): Money = NonJvmMoney(
+ value + (amount as NonJvmMoney).value
+ )
- internal class NonJvmRatio(internal val value: BigDecimal) : Money.Ratio {
+ override fun minus(amount: Money): Money = NonJvmMoney(
+ value - (amount as NonJvmMoney).value
+ )
override fun times(amount: Money): Money = NonJvmMoney(
- (value * (amount as NonJvmMoney).value)
+ value * (amount as NonJvmMoney).value
)
+ override fun times(ratio: Money.Ratio): Money = NonJvmMoney(
+ value * (ratio as NonJvmRatio).value
+ )
+
+ override fun compareTo(
+ other: Money
+ ): Int = value.compareTo((other as NonJvmMoney).value)
+
override fun toString(): String {
- return value.toPlainString()
+ return value.toPlainString()
}
override fun equals(
- other: Any?
+ other: Any?
): Boolean = (other != null)
- && (other is NonJvmRatio)
- && (value == other.value)
+ && (other is NonJvmMoney)
+ && (value == other.value)
override fun hashCode(): Int = value.hashCode()
- }
+ internal class NonJvmRatio(internal val value: BigDecimal) : Money.Ratio {
+
+ override fun times(amount: Money): Money = NonJvmMoney(
+ (value * (amount as NonJvmMoney).value)
+ )
+
+ override fun toString(): String {
+ return value.toPlainString()
+ }
+
+ override fun equals(
+ other: Any?
+ ): Boolean = (other != null)
+ && (other is NonJvmRatio)
+ && (value == other.value)
+
+ override fun hashCode(): Int = value.hashCode()
+
+ }
}
public actual fun Money(amount: String): Money = NonJvmMoney(
- BigDecimal.parseString(amount)
+ BigDecimal.parseString(amount)
)
@Suppress("ObjectPropertyName")
@@ -87,9 +87,9 @@ private val _ONE = NonJvmMoney(BigDecimal.ONE)
public actual val Money.Companion.ONE: Money get() = _ONE
public actual fun Money.Companion.Ratio(
- value: String
+ value: String
): Money.Ratio = NonJvmMoney.NonJvmRatio(
- BigDecimal.parseString(value)
+ BigDecimal.parseString(value)
)
@Suppress("ObjectPropertyName")
@@ -97,4 +97,4 @@ private val _RATIO_ONE = NonJvmMoney.NonJvmRatio(BigDecimal.ONE)
public actual val Money.Ratio.Companion.ONE: Money.Ratio get() = _RATIO_ONE
public actual fun Int.toMoneyRatio(): Money.Ratio =
- NonJvmMoney.NonJvmRatio(BigDecimal.fromInt(this))
+ NonJvmMoney.NonJvmRatio(BigDecimal.fromInt(this))