Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
7591f7c
Add public structured exception metadata
remdui Jul 21, 2026
2633d5d
Add public structured exception metadata
remdui Jul 21, 2026
ad93e6f
Add public structured exception metadata
remdui Jul 21, 2026
3ceb73c
Add public DataProvider exception base
remdui Jul 21, 2026
8d6a7f2
Add shared public failure context
remdui Jul 21, 2026
0790fbe
Use shared failure context in public exceptions
remdui Jul 21, 2026
ab6ef15
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
4ab44f0
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
06b7640
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
02a3f9a
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
ede1dfe
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
ae0ba7f
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
c2786d3
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
ba2114d
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
860c796
Add transaction failure phase
remdui Jul 21, 2026
5f099ae
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
3d277ad
Add concrete public DataProvider exceptions
remdui Jul 21, 2026
fd1cc66
Add strict registration API methods
remdui Jul 21, 2026
c382b7b
Add strict scoped registration API methods
remdui Jul 21, 2026
caf381a
Expose execution failure context
remdui Jul 21, 2026
c7097aa
Attach backend context to execution handles
remdui Jul 21, 2026
a3237a0
Attach backend context to provider execution
remdui Jul 21, 2026
6840e4f
Translate backend failures into public exceptions
remdui Jul 21, 2026
e944f6e
Expose structured async failures
remdui Jul 21, 2026
6b656a3
Complete structured failure translation
remdui Jul 21, 2026
5720226
Retain missing configuration failure details
remdui Jul 21, 2026
0426d66
Expose strict structured registration failures
remdui Jul 21, 2026
4d65104
Wire strict public API methods
remdui Jul 21, 2026
c298167
Expose structured scoped failures
remdui Jul 21, 2026
b59d037
Add structured transaction phase failures
remdui Jul 21, 2026
8a1702b
Add structured exception regression tests
remdui Jul 21, 2026
453668c
Test public exception safety contract
remdui Jul 21, 2026
784cc8b
Update async tests for structured failures
remdui Jul 21, 2026
94668e8
Update runtime rejection tests for public exceptions
remdui Jul 21, 2026
84708a7
Document structured exception handling
remdui Jul 21, 2026
89c5f3a
Update factory missing configuration test
remdui Jul 21, 2026
be744a0
Restore transaction connection state
remdui Jul 21, 2026
cf4d20d
Add synchronous structured failure helpers
remdui Jul 21, 2026
245f26a
Structure MongoDB serialization failures
remdui Jul 21, 2026
2556907
Structure Redis messaging failures
remdui Jul 21, 2026
0b4a227
Test structured MySQL failures
remdui Jul 21, 2026
91ffeea
Retain safe SQL diagnostics consistently
remdui Jul 21, 2026
f20dbfb
Improve CI test failure diagnostics
remdui Jul 22, 2026
1a7e6b9
Restore complete test workflow with failure diagnostics
remdui Jul 22, 2026
d44b83c
Preserve caller validation failures in async operations
remdui Jul 22, 2026
1667f8b
Preserve legacy lifecycle behavior and simplify handler paths
remdui Jul 22, 2026
5c3711a
Preserve legacy scope closure behavior
remdui Jul 22, 2026
20f53a5
Keep Mongo input validation failures compatible
remdui Jul 22, 2026
48a3d96
Cover async validation failure preservation
remdui Jul 22, 2026
0dedc8a
Test strict lifecycle exceptions
remdui Jul 22, 2026
94e8806
Validate diagnostic correlation identifiers
remdui Jul 22, 2026
88822a1
Test diagnostic identifier validation
remdui Jul 22, 2026
2412be0
Clarify exception compatibility and redaction guarantees
remdui Jul 22, 2026
874820d
Add generic operation failure code
remdui Jul 22, 2026
bcb832c
Add generic structured operation exception
remdui Jul 22, 2026
26f240a
Add explicit transaction cleanup phase
remdui Jul 22, 2026
b56ad9c
Improve exception classification and unwrapping
remdui Jul 22, 2026
6b36028
Harden transaction cleanup and redaction
remdui Jul 22, 2026
00b4da9
Keep fatal errors primary during transaction cleanup
remdui Jul 22, 2026
575ee7e
Cover generic failures and timeout semantics
remdui Jul 22, 2026
9cc811c
Cover transaction cleanup and generic failures
remdui Jul 22, 2026
84518cf
Document generic failures and cleanup semantics
remdui Jul 22, 2026
2627abb
Update async failure classification test
remdui Jul 22, 2026
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
21 changes: 16 additions & 5 deletions .github/workflows/ci-tests-and-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,31 @@ jobs:
cache: maven

- name: Run Unit and Backend Integration Tests
id: tests
run: mvn -U -B -ntp -Pintegration-tests verify

- name: Print failing test reports
if: failure() && steps.tests.outcome == 'failure'
shell: bash
run: |
find . -type f \( -path '*/target/surefire-reports/*.txt' -o -path '*/target/failsafe-reports/*.txt' \) -print0 \
| sort -z \
| xargs -0 -r -n1 sh -c 'echo "::group::$0"; cat "$0"; echo "::endgroup::"'

- name: Upload JaCoCo Report
if: always()
uses: actions/upload-artifact@v7
with:
name: jacoco-report
if-no-files-found: error
if-no-files-found: warn
path: "**/target/site/jacoco"

- name: Upload Integration Test Reports
- name: Upload Test Reports
if: always()
uses: actions/upload-artifact@v7
with:
name: integration-test-reports
if-no-files-found: error
path: "**/target/failsafe-reports"
name: test-reports
if-no-files-found: warn
path: |
**/target/surefire-reports
**/target/failsafe-reports
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
package nl.hauntedmc.dataprovider.api;

import nl.hauntedmc.dataprovider.api.orm.ORMContext;
import nl.hauntedmc.dataprovider.database.DataAccess;
import nl.hauntedmc.dataprovider.database.DatabaseProvider;
import nl.hauntedmc.dataprovider.database.DatabaseType;
import nl.hauntedmc.dataprovider.api.orm.ORMContext;
import nl.hauntedmc.dataprovider.exception.DataProviderFailureContext;
import nl.hauntedmc.dataprovider.exception.DataProviderRegistrationException;
import nl.hauntedmc.dataprovider.exception.ExecutionOutcome;
import nl.hauntedmc.dataprovider.exception.RetryAdvice;
import nl.hauntedmc.dataprovider.logging.LoggerAdapter;

import javax.sql.DataSource;
import java.util.Objects;
import java.util.Optional;

/**
* Public, platform-neutral facade for plugin-scoped database registrations.
*
* <p>The API artifact intentionally contains contracts only. Platform modules provide the
* runtime implementation and expose an instance through their native service mechanism.</p>
*/
/** Public, platform-neutral facade for plugin-scoped database registrations. */
public interface DataProviderAPI {

/**
* Creates an isolated ORM context owned by the calling plugin.
*
* @param pluginName plugin name used for ORM diagnostics
* @param dataSource relational data source obtained from a registered provider
* @param logger logger that receives ORM lifecycle diagnostics
* @param schemaMode Hibernate schema mode: validate, none, update, or create
* @param entityClasses annotated entity classes to register
* @return a new, initialized ORM context
*/
ORMContext createOrmContext(
String pluginName,
DataSource dataSource,
Expand All @@ -36,8 +25,31 @@ ORMContext createOrmContext(
Class<?>... entityClasses
);

/** Legacy nullable registration method retained for compatibility. */
DatabaseProvider registerDatabase(DatabaseType databaseType, String connectionIdentifier);

/**
* Registers a database or throws a structured public exception retaining the failure category.
* Implementations should override this method to preserve backend-specific failure details.
*/
default DatabaseProvider registerDatabaseOrThrow(DatabaseType databaseType, String connectionIdentifier) {
DatabaseProvider provider = registerDatabase(databaseType, connectionIdentifier);
if (provider != null) {
return provider;
}
throw new DataProviderRegistrationException(
"Database registration failed.",
DataProviderFailureContext.of(
databaseType,
connectionIdentifier,
"registerDatabase",
RetryAdvice.CONDITIONAL,
ExecutionOutcome.NOT_STARTED
),
null
);
}

DataProviderScope scope(OwnerScope ownerScope);

void unregisterDatabase(DatabaseType databaseType, String connectionIdentifier);
Expand All @@ -46,9 +58,28 @@ ORMContext createOrmContext(

void unregisterAllDatabasesForPlugin();

/** Legacy nullable lookup retained for compatibility. */
DatabaseProvider getRegisteredDatabase(DatabaseType databaseType, String connectionIdentifier);

/** Creates an isolated ownership scope for independently managed plugin components. */
/** Returns a registered provider or throws a structured registration-state failure. */
default DatabaseProvider requireRegisteredDatabase(DatabaseType databaseType, String connectionIdentifier) {
DatabaseProvider provider = getRegisteredDatabase(databaseType, connectionIdentifier);
if (provider != null) {
return provider;
}
throw new DataProviderRegistrationException(
"No active database registration exists for the requested connection.",
DataProviderFailureContext.of(
databaseType,
connectionIdentifier,
"requireRegisteredDatabase",
RetryAdvice.NEVER,
ExecutionOutcome.NOT_STARTED
),
null
);
}

default DataProviderScope scope(String ownerScope) {
return scope(OwnerScope.of(ownerScope));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
import nl.hauntedmc.dataprovider.database.DataAccess;
import nl.hauntedmc.dataprovider.database.DatabaseProvider;
import nl.hauntedmc.dataprovider.database.DatabaseType;
import nl.hauntedmc.dataprovider.exception.DataProviderFailureContext;
import nl.hauntedmc.dataprovider.exception.DataProviderRegistrationException;
import nl.hauntedmc.dataprovider.exception.ExecutionOutcome;
import nl.hauntedmc.dataprovider.exception.RetryAdvice;

import java.util.Objects;
import java.util.Optional;

/**
* Isolated lifecycle boundary for a logical component within one plugin.
*/
/** Isolated lifecycle boundary for a logical component within one plugin. */
public interface DataProviderScope extends AutoCloseable {

/** Lifecycle states for a scope. A closed scope cannot be reopened. */
enum LifecycleState {
OPEN,
CLOSING,
Expand All @@ -21,29 +22,56 @@ enum LifecycleState {

OwnerScope ownerScope();

/**
* Returns this scope's current lifecycle state.
* Implementations created by DataProvider transition from OPEN to CLOSING to CLOSED on close.
*/
default LifecycleState lifecycleState() {
return LifecycleState.OPEN;
}

DatabaseProvider registerDatabase(DatabaseType databaseType, String connectionIdentifier);

default DatabaseProvider registerDatabaseOrThrow(DatabaseType databaseType, String connectionIdentifier) {
DatabaseProvider provider = registerDatabase(databaseType, connectionIdentifier);
if (provider != null) {
return provider;
}
throw new DataProviderRegistrationException(
"Scoped database registration failed.",
DataProviderFailureContext.of(
databaseType,
connectionIdentifier,
"scope.registerDatabase",
RetryAdvice.CONDITIONAL,
ExecutionOutcome.NOT_STARTED
).withDiagnostics(java.util.Map.of("ownerScope", ownerScope().value())),
null
);
}

void unregisterDatabase(DatabaseType databaseType, String connectionIdentifier);

void unregisterAllDatabases();

/**
* Retrieves a provider registered by this scope.
*
* @throws UnsupportedOperationException if the scope implementation does not support scoped lookup
*/
default DatabaseProvider getRegisteredDatabase(DatabaseType databaseType, String connectionIdentifier) {
throw new UnsupportedOperationException("Scoped provider lookup is not supported by this implementation.");
}

default DatabaseProvider requireRegisteredDatabase(DatabaseType databaseType, String connectionIdentifier) {
DatabaseProvider provider = getRegisteredDatabase(databaseType, connectionIdentifier);
if (provider != null) {
return provider;
}
throw new DataProviderRegistrationException(
"No active scoped database registration exists for the requested connection.",
DataProviderFailureContext.of(
databaseType,
connectionIdentifier,
"scope.requireRegisteredDatabase",
RetryAdvice.NEVER,
ExecutionOutcome.NOT_STARTED
).withDiagnostics(java.util.Map.of("ownerScope", ownerScope().value())),
null
);
}

default Optional<DatabaseProvider> registerDatabaseOptional(
DatabaseType databaseType,
String connectionIdentifier
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nl.hauntedmc.dataprovider.exception;

/** Backend rejected configured authentication. */
public final class BackendAuthenticationException extends DataProviderException {
public BackendAuthenticationException(String message, DataProviderFailureContext context, Throwable cause) {
super(DataProviderErrorCode.AUTHENTICATION_FAILED, message, context, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package nl.hauntedmc.dataprovider.exception;

/** Backend is disabled, unreachable, or otherwise unavailable. */
public final class BackendUnavailableException extends DataProviderException {
public BackendUnavailableException(DataProviderErrorCode code, String message,
DataProviderFailureContext context, Throwable cause) {
super(code, message, context, cause);
if (code != DataProviderErrorCode.BACKEND_DISABLED
&& code != DataProviderErrorCode.BACKEND_UNAVAILABLE) {
throw new IllegalArgumentException("Unsupported backend availability error code: " + code);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nl.hauntedmc.dataprovider.exception;

/** A uniqueness, optimistic-locking, or compare-and-set conflict occurred. */
public final class DataConflictException extends DataProviderException {
public DataConflictException(String message, DataProviderFailureContext context, Throwable cause) {
super(DataProviderErrorCode.CONFLICT, message, context, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package nl.hauntedmc.dataprovider.exception;

/** Invalid or missing DataProvider configuration. */
public final class DataProviderConfigurationException extends DataProviderException {
public DataProviderConfigurationException(DataProviderErrorCode code, String message,
DataProviderFailureContext context, Throwable cause) {
super(code, message, context, cause);
if (code != DataProviderErrorCode.CONFIGURATION_INVALID
&& code != DataProviderErrorCode.CONFIGURATION_MISSING) {
throw new IllegalArgumentException("Unsupported configuration error code: " + code);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package nl.hauntedmc.dataprovider.exception;

/** Stable machine-readable failure codes exposed by the DataProvider API. */
public enum DataProviderErrorCode {
CONFIGURATION_INVALID,
CONFIGURATION_MISSING,
REGISTRATION_FAILED,
BACKEND_DISABLED,
BACKEND_UNAVAILABLE,
AUTHENTICATION_FAILED,
OPERATION_FAILED,
OPERATION_TIMED_OUT,
QUEUE_SATURATED,
SERIALIZATION_FAILED,
CONFLICT,
TRANSACTION_FAILED,
PROVIDER_CLOSED
}
Loading
Loading