mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-10 08:27:04 +00:00
Centralize the insert-rejection vocabulary in RejectionReason
Every store spelled its rejection reasons inline — the expired-event string was duplicated four times across ObservableEventStore and SQLiteEventStore. RejectionReason now carries the NIP-01 OK machine-readable prefixes plus the standard store reasons, so InsertOutcome.Rejected tallies and OK-frame building see one vocabulary no matter which store produced the outcome. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MfwV3xgMSmfxy16ujGPxGW
This commit is contained in:
+1
-1
@@ -122,7 +122,7 @@ interface IEventStore : AutoCloseable {
|
||||
insert(event)
|
||||
InsertOutcome.Accepted
|
||||
} catch (e: Throwable) {
|
||||
InsertOutcome.Rejected(e.message ?: e::class.simpleName ?: "insert failed")
|
||||
InsertOutcome.Rejected(e.message ?: e::class.simpleName ?: RejectionReason.INSERT_FAILED)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ class ObservableEventStore(
|
||||
if (event.kind.isEphemeral()) {
|
||||
outcomes[i] =
|
||||
if (event.isExpired()) {
|
||||
IEventStore.InsertOutcome.Rejected("blocked: Cannot insert an expired event")
|
||||
IEventStore.InsertOutcome.Rejected(RejectionReason.EXPIRED)
|
||||
} else {
|
||||
IEventStore.InsertOutcome.Accepted
|
||||
}
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.store
|
||||
|
||||
/**
|
||||
* The machine-readable insert-rejection vocabulary, shared by every
|
||||
* [IEventStore] implementation so the same condition always rejects with the
|
||||
* same words — a caller tallying [IEventStore.InsertOutcome.Rejected] reasons,
|
||||
* or a relay building `OK false` frames, must never see two stores spell
|
||||
* "duplicate" differently.
|
||||
*
|
||||
* NIP-01: an `OK false` message SHOULD begin with a single-word
|
||||
* machine-readable prefix followed by `:`. The `PREFIX_*` constants are that
|
||||
* vocabulary; the full-sentence constants are the standard reasons the
|
||||
* built-in stores emit. `replaced:` is not in NIP-01 but is the de-facto
|
||||
* prefix (strfry and others) for a replaceable event that lost to a stored
|
||||
* newer version — distinct from `duplicate:` (the exact event is already
|
||||
* held).
|
||||
*/
|
||||
object RejectionReason {
|
||||
// The NIP-01 machine-readable prefixes.
|
||||
const val PREFIX_DUPLICATE = "duplicate:"
|
||||
const val PREFIX_POW = "pow:"
|
||||
const val PREFIX_BLOCKED = "blocked:"
|
||||
const val PREFIX_RATE_LIMITED = "rate-limited:"
|
||||
const val PREFIX_INVALID = "invalid:"
|
||||
const val PREFIX_RESTRICTED = "restricted:"
|
||||
const val PREFIX_ERROR = "error:"
|
||||
|
||||
/** De-facto prefix (not in NIP-01) for a stale version of a replaceable/addressable event. */
|
||||
const val PREFIX_REPLACED = "replaced:"
|
||||
|
||||
// The standard store reasons.
|
||||
const val DUPLICATE = "duplicate: already have this event"
|
||||
const val EXPIRED = "blocked: Cannot insert an expired event"
|
||||
const val DELETED = "blocked: a deletion event exists"
|
||||
const val VANISHED = "blocked: a request to vanish event exists"
|
||||
const val REPLACED = "replaced: a newer version exists"
|
||||
const val INSERT_FAILED = "error: insert failed"
|
||||
}
|
||||
+5
-4
@@ -37,6 +37,7 @@ import com.vitorpamplona.quartz.nip01Core.store.FtsReindexProgress
|
||||
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
|
||||
import com.vitorpamplona.quartz.nip01Core.store.IdAndTime
|
||||
import com.vitorpamplona.quartz.nip01Core.store.RawEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.store.RejectionReason
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||
import com.vitorpamplona.quartz.nip50Search.strippingSearchExtensions
|
||||
@@ -433,7 +434,7 @@ class SQLiteEventStore(
|
||||
}
|
||||
|
||||
suspend fun insertEvent(event: Event) {
|
||||
if (event.isExpired()) throw SQLiteException("blocked: Cannot insert an expired event")
|
||||
if (event.isExpired()) throw SQLiteException(RejectionReason.EXPIRED)
|
||||
if (event.kind.isEphemeral()) return
|
||||
|
||||
pool.useWriter { db ->
|
||||
@@ -489,7 +490,7 @@ class SQLiteEventStore(
|
||||
delta: LiveIndexDelta?,
|
||||
): IEventStore.InsertOutcome {
|
||||
if (event.isExpired()) {
|
||||
return IEventStore.InsertOutcome.Rejected("blocked: Cannot insert an expired event")
|
||||
return IEventStore.InsertOutcome.Rejected(RejectionReason.EXPIRED)
|
||||
}
|
||||
if (event.kind.isEphemeral()) return IEventStore.InsertOutcome.Accepted
|
||||
|
||||
@@ -509,7 +510,7 @@ class SQLiteEventStore(
|
||||
// ROLLBACK shouldn't mask the original cause.
|
||||
runCatching { db.execSQL("ROLLBACK TRANSACTION TO SAVEPOINT $sp") }
|
||||
runCatching { db.execSQL("RELEASE SAVEPOINT $sp") }
|
||||
IEventStore.InsertOutcome.Rejected(e.message ?: e::class.simpleName ?: "insert failed")
|
||||
IEventStore.InsertOutcome.Rejected(e.message ?: e::class.simpleName ?: RejectionReason.INSERT_FAILED)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,7 +519,7 @@ class SQLiteEventStore(
|
||||
private val delta: LiveIndexDelta?,
|
||||
) : IEventStore.ITransaction {
|
||||
override fun insert(event: Event) {
|
||||
if (event.isExpired()) throw SQLiteException("blocked: Cannot insert an expired event")
|
||||
if (event.isExpired()) throw SQLiteException(RejectionReason.EXPIRED)
|
||||
if (event.kind.isEphemeral()) return
|
||||
|
||||
innerInsertEvent(event, db, delta)
|
||||
|
||||
Reference in New Issue
Block a user