8.4 KiB
notification-services Specification
Purpose
Defines transport-independent notification services whose delivery configuration is bound before consumers submit semantically tagged text messages.
Requirements
Requirement: Transport-independent bound notification contract
The notification API SHALL provide a non-generic bound service under com.r35157.service.notification whose only delivery input is an ΩNotificationMessageΩ and whose synchronous failures use IOException. A consumer using an already bound service SHALL NOT need a transport-specific message type or an implementation-package dependency.
Scenario: Bound consumer sends a message
- WHEN a consumer receives a configured
BoundNotificationService - THEN it can call
push(message)using only anΩNotificationMessageΩ
Scenario: Delivery failure is reported synchronously
- WHEN a bound transport encounters a delivery failure
- THEN
push(message)reports the failure through itsIOExceptioncontract
Requirement: Addressed services bind destination configuration
The notification API SHALL provide an AddressedNotificationService generic only over a NotificationDestination type. Binding a destination SHALL produce a BoundNotificationService that captures the destination so later calls require only an ΩNotificationMessageΩ.
Scenario: Destination is bound once
- WHEN a destination is passed to an addressed notification service's binding operation
- THEN the returned bound service sends subsequent messages to that captured destination without requiring it again
Requirement: Notification message validation
Every public notification-service push(...) entry point SHALL reject a null, empty, or blank ΩNotificationMessageΩ with IllegalArgumentException before attempting any network or transport operation.
Scenario: Null message is rejected
- WHEN a caller pushes a null notification message
- THEN the service throws
IllegalArgumentExceptionbefore delivery is attempted
Scenario: Empty or blank message is rejected
- WHEN a caller pushes an empty or whitespace-only notification message
- THEN the service throws
IllegalArgumentExceptionbefore delivery is attempted
Requirement: Pushover delivery remains functional
The Pushover implementation SHALL accept ΩNotificationMessageΩ directly, preserve its existing synchronous HTTP delivery behavior, and expose it as PushoverNotificationService implementing BoundNotificationService.
Scenario: Valid Pushover message is submitted
- WHEN a caller pushes a valid message through a configured Pushover notification service
- THEN the service submits the same message through the existing Pushover API request flow
Scenario: Pushover rejects delivery
- WHEN Pushover returns a non-successful HTTP response or communication fails
- THEN the service reports the delivery failure as an
IOException
Requirement: SMTP binding captures address and subject
An SMTP destination SHALL contain both an ΩEmailAddressΩ and a fixed ΩEmailSubjectΩ. SMTP SHALL support binding these two values into a BoundNotificationService, and SHALL reject a null, empty, or blank address or subject with IllegalArgumentException when the destination is created.
Scenario: SMTP service is fully bound
- WHEN a caller binds a valid email address and subject
- THEN the returned bound service requires only an
ΩNotificationMessageΩfor each push
Scenario: Invalid SMTP binding is rejected
- WHEN a caller creates or requests an SMTP binding with a null, empty, or blank email address or subject
- THEN the operation throws
IllegalArgumentException
Scenario: Valid SMTP delivery remains unimplemented
- WHEN a valid message is pushed through a valid SMTP binding
- THEN the service reports that SMTP delivery is not implemented
Requirement: Discord webhook delivery
The Discord notification service SHALL remain a BoundNotificationService that is fully configured at construction from a non-null structurally valid canonical Discord incoming-webhook ΩRestEndpointΩ and a non-null positive Duration. It SHALL make no construction-time network request, follow no HTTP redirects, and SHALL NOT expose a raw URL or raw String constructor.
The endpoint SHALL be an absolute HTTPS URI with a case-insensitive discord.com host, no user-info, fragment, query, or non-HTTPS port, and either an unversioned /api/webhooks/{id}/{token} path or a versioned /api/v{version}/webhooks/{id}/{token} path using a non-discontinued Discord API version from 6 through 10, with non-blank ID and token segments. Constructor validation diagnostics SHALL NOT disclose the endpoint or token.
Every public push(...) entry point SHALL reject a null, empty, blank, or over-2,000-character ΩNotificationMessageΩ with IllegalArgumentException before JSON serialization, request construction, or transport work. For every valid message, the service SHALL synchronously send exactly one POST to the configured endpoint with wait=true, the configured request timeout, JSON UTF-8 content and accept headers, a DiscordBot (url, version) User-Agent identifying Nenjim, and a safely serialized JSON object whose content preserves the message exactly. The webhook token SHALL be the only authentication mechanism.
Every 2xx HTTP response SHALL return normally. Every non-2xx response, including redirects and 429 responses, and every communication or request-preparation failure SHALL cause IOException without retrying, waiting, or sending another request. Response failures SHALL include the HTTP status and useful response body when available, without disclosing the endpoint or token. When synchronous HTTP delivery is interrupted, the service SHALL restore the thread interrupt flag and throw an IOException retaining the InterruptedException as its cause.
Scenario: Fully configured construction
- WHEN a caller constructs the service with a canonical Discord webhook endpoint and positive timeout
- THEN it receives a bound notification service without a network request
Scenario: Invalid endpoint or timeout is rejected
- WHEN a caller supplies a null or structurally invalid endpoint, or a null, zero, or negative timeout
- THEN construction throws
IllegalArgumentExceptionwithout disclosing the endpoint token
Scenario: Invalid notification message is rejected before delivery
- WHEN a caller pushes a null, empty, blank, or over-2,000-character message
- THEN the service throws
IllegalArgumentExceptionbefore serialization or HTTP work
Scenario: Valid message is synchronously confirmed
- WHEN a caller pushes a valid notification message and Discord returns a 2xx response
- THEN the service sends one synchronous JSON POST with
wait=trueand returns normally
Scenario: Discord rejects delivery
- WHEN Discord returns a non-2xx response, including 429 or a redirect
- THEN the service throws
IOExceptioncontaining the status and does not retry or follow the redirect
Scenario: Delivery communication fails
- WHEN preparing or sending the webhook request fails
- THEN the service throws
IOExceptionwithout exposing the webhook endpoint or token
Scenario: Delivery is interrupted
- WHEN synchronous webhook delivery is interrupted
- THEN the service restores the thread interrupt status and throws
IOExceptionwith the interruption as its cause
Scenario: Webhook secret remains protected
- WHEN validation or delivery reports an error
- THEN no log or exception message includes the complete webhook endpoint or token
Requirement: Obsolete notification API is removed
The notification framework SHALL reside exclusively under com.r35157.service.notification. It SHALL NOT retain compatibility aliases under com.r35157.libs.notification, notifier-named public interfaces or implementations, the NotificationMessage marker, or transport-specific message wrapper types.
Scenario: Production code uses the new service API
- WHEN the production source tree is inspected after migration
- THEN notification framework code and consumers reference only the new package and service-oriented types
Scenario: Removed types are unavailable
- WHEN downstream code attempts to import an old notifier or message-wrapper type
- THEN that type is no longer part of the source or compiled API