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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '25'
java-version: '17'
cache: maven

- name: Make Maven wrapper executable
Expand Down
120 changes: 120 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Release

on:
workflow_dispatch:
inputs:
release_version:
description: Release version in MAJOR.MINOR.PATCH format
required: true
type: string

concurrency:
group: release-${{ github.event.repository.default_branch }}
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
steps:
- name: Check out default branch
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.repository.default_branch }}

- name: Set up Java and Maven Central credentials
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
cache: maven
server-id: central
server-username: ${{ secrets.SONATYPE_USERNAME }}
server-password: ${{ secrets.SONATYPE_PASSWORD }}
gpg-private-key: ${{ secrets.GPG_KEY_CONTENTS }}
gpg-passphrase: ${{ secrets.SIGNING_PASSWORD }}

- name: Make Maven wrapper executable
run: chmod +x ./mvnw

- name: Derive release metadata
env:
RELEASE_VERSION_INPUT: ${{ inputs.release_version }}
run: |
RELEASE_VERSION="${RELEASE_VERSION_INPUT}"

if ! [[ "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Release version '${RELEASE_VERSION}' is not in the expected MAJOR.MINOR.PATCH format." >&2
exit 1
fi

IFS='.' read -r major minor patch <<EOF
${RELEASE_VERSION}
EOF

NEXT_PATCH=$((patch + 1))
NEXT_SNAPSHOT_VERSION="${major}.${minor}.${NEXT_PATCH}-SNAPSHOT"
RELEASE_NOTES_FILE="docs/releases/${RELEASE_VERSION}.md"

echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "${GITHUB_ENV}"
echo "NEXT_SNAPSHOT_VERSION=${NEXT_SNAPSHOT_VERSION}" >> "${GITHUB_ENV}"
echo "RELEASE_NOTES_FILE=${RELEASE_NOTES_FILE}" >> "${GITHUB_ENV}"

- name: Verify release tag does not already exist
run: |
if git rev-parse -q --verify "refs/tags/${RELEASE_VERSION}" >/dev/null; then
echo "Tag ${RELEASE_VERSION} already exists locally." >&2
exit 1
fi
if git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_VERSION}" >/dev/null; then
echo "Tag ${RELEASE_VERSION} already exists on origin." >&2
exit 1
fi

- name: Configure git author
run: |
git config user.name "github-actions"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Create release commit and tag
run: |
./mvnw -B versions:set -DnewVersion="${RELEASE_VERSION}" -DgenerateBackupPoms=false
git add pom.xml
git commit -m "Release ${RELEASE_VERSION}"
git tag -a "${RELEASE_VERSION}" -m "Release ${RELEASE_VERSION}"

- name: Create next snapshot commit
run: |
./mvnw -B versions:set -DnewVersion="${NEXT_SNAPSHOT_VERSION}" -DgenerateBackupPoms=false
git add pom.xml
git commit -m "Bump version to ${NEXT_SNAPSHOT_VERSION}"

- name: Push release tag and default branch
run: |
git push --atomic origin HEAD:"${DEFAULT_BRANCH}" "refs/tags/${RELEASE_VERSION}"

- name: Check out release tag for publishing
run: git checkout --detach "${RELEASE_VERSION}"

- name: Build, sign, and publish release bundle
run: ./mvnw -B -Prelease deploy

- name: Create draft GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -f "${RELEASE_NOTES_FILE}" ]; then
gh release create "${RELEASE_VERSION}" \
--draft \
--title "${RELEASE_VERSION}" \
--notes-file "${RELEASE_NOTES_FILE}"
else
gh release create "${RELEASE_VERSION}" \
--draft \
--title "${RELEASE_VERSION}" \
--generate-notes
fi
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Maciej Bartczak

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Inspired by [better-result](https://github.com/dmmulroy/better-result).

## Contents

- [Installation](#installation)
- [Java Compatibility](#java-compatibility)
- [Quick Start](#quick-start)
- [The Core Types](#the-core-types)
- [Creating Results](#creating-results)
- [Transforming Success Values](#transforming-success-values)
Expand All @@ -17,6 +20,65 @@ Inspired by [better-result](https://github.com/dmmulroy/better-result).
- [Panic](#panic)
- [API Reference](#api-reference)

## Installation

Maven:

```xml
<dependency>
<groupId>io.github.b6k-dev</groupId>
<artifactId>outcome</artifactId>
<version>1.0.0</version>
</dependency>
```

Gradle (Groovy DSL):

```groovy
implementation 'io.github.b6k-dev:outcome:1.0.0'
```

Gradle (Kotlin DSL):

```kotlin
implementation("io.github.b6k-dev:outcome:1.0.0")
```

## Java Compatibility

Outcome targets Java 17 and newer. The library API is compiled with `--release 17`, and examples in this README use sealed types, records, and pattern matching features available in modern Java.

## Quick Start

```java
import b6k.dev.outcome.Result;

sealed interface CreateUserError permits InvalidEmail, DuplicateEmail {}
record InvalidEmail(String value) implements CreateUserError {}
record DuplicateEmail(String value) implements CreateUserError {}
record User(String id, String email) {}

Result<User, CreateUserError> createUser(String email) {
if (email == null || email.isBlank() || !email.contains("@")) {
return Result.err(new InvalidEmail(String.valueOf(email)));
}
if (email.equals("ada@example.com")) {
return Result.err(new DuplicateEmail(email));
}
return Result.ok(new User("u-123", email));
}

String message = createUser("ada@example.com").fold(
user -> "Created user " + user.email(),
error -> switch (error) {
case InvalidEmail e -> "Invalid email: " + e.value();
case DuplicateEmail e -> "Email already exists: " + e.email();
}
);
```

Use `Result.ok(...)` and `Result.err(...)` to model expected outcomes, then compose them with methods like `map`, `flatMap`, `orElse`, and `fold` instead of mixing nullable values, sentinel states, and exceptions.

## The Core Types

Outcome revolves around four ideas:
Expand Down
23 changes: 23 additions & 0 deletions docs/releases/1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Outcome 1.0.0 Release Notes

## Highlights

- First public release of Outcome, a lightweight `Result<T, E>` type for Java

## Coordinates

Maven:

```xml
<dependency>
<groupId>io.github.b6k-dev</groupId>
<artifactId>outcome</artifactId>
<version>1.0.0</version>
</dependency>
```

Gradle:

```groovy
implementation 'io.github.b6k-dev:outcome:1.0.0'
```
Loading