Skip to content

Fixed message channels#3440

Merged
tomakehurst merged 22 commits into
masterfrom
fixed-message-channels
Jun 3, 2026
Merged

Fixed message channels#3440
tomakehurst merged 22 commits into
masterfrom
fixed-message-channels

Conversation

@tomakehurst

Copy link
Copy Markdown
Member

Add support for fixed message channels.

These are intended to allow integration with message brokers/queues etc. via a new extension point.

There is also an in-memory type included.

tomakehurst and others added 22 commits May 26, 2026 12:37
Introduces named fixed channels that abstract an underlying messaging
technology via a channel provider/driver model. A stub action can now
target a fixed channel using sendMessage().onChannel(providerName, channelName),
which is recorded in the message journal for verification.

Includes an in-memory built-in driver, admin API endpoints for registering
providers (POST /channel-providers) and creating channels (POST /channels),
and corresponding WireMock client DSL (channelProvider(), fixedChannel(),
registerChannelProvider(), createFixedChannel()).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…pe.FIXED

Stubs can now be triggered by messages arriving on a fixed channel using
triggeredByMessageOnChannel(providerName, channelName), with an optional
body pattern via withBody(). The new FixedChannelMessageTrigger stores a
MessagePattern rather than a raw ContentPattern.

Sending messages to fixed channels is unified under the existing
Admin.sendChannelMessage overload and POST /channels/send endpoint, using
the new ChannelType.FIXED value and extended SendChannelMessageRequest
(providerName/channelName fields). The separate sendMessageToFixedChannel
method and SendFixedChannelMessageTask/Request are removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… model

ChannelProvider registrations are now backed by ChannelProviderStore
(InMemoryChannelProviderStore), wired through Stores/DefaultStores.

Fixed channels are unified with the existing MessageChannel model rather
than kept in a separate store: FixedMessageChannel implements MessageChannel
and lives in the existing MessageChannelStore alongside WebSocket channels.
MessageChannels.findFixed(providerName, channelName) provides lookup.

ChannelProviderRegistry no longer holds channel state or a send() method;
createChannel() now returns a FixedMessageChannel which WireMockApp adds to
MessageChannels. HttpStubServeEventListener drops its registry dependency and
resolves fixed channel sends via messageChannels.findFixed() instead.

InMemoryStringKeyedStore<T> is a shared base for string-keyed ConcurrentHashMap
stores; FixedChannelStore and InMemoryFixedChannelStore are removed.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… drivers

CustomChannelProviderDriver extends both ChannelProviderDriver and Extension,
letting implementors register a custom driver via WireMockConfiguration.extensions().
WireMockApp picks up all CustomChannelProviderDriver instances at startup and
registers them with ChannelProviderRegistry.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Restructured WireMockApp.sendChannelMessage to perform stub matching
before recording the journal event, so wasMatched correctly reflects
whether a stub was found. Also sets channelType=FIXED on the event.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
receivedOnFixedChannel now has an overload that accepts a stub mapping.
WireMockApp.sendChannelMessage passes the first matching stub when
recording the journal event, consistent with the WebSocket path.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
MessageChannels.add() throws ConflictException when a FixedMessageChannel
with the same providerName/channelName already exists, surfacing as a 409
to the caller rather than silently creating a duplicate entry.

Test setup moved to @BeforeAll so provider and channel are registered once
per suite run rather than before every test.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
MessageChannels.requireFixed() throws NotFoundException when the target
channel doesn't exist, returning HTTP 404 rather than doing nothing.
Both WireMockApp.sendChannelMessage and HttpStubServeEventListener now
use requireFixed() so missing channels are a hard failure in both paths.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…tion

ChannelProviderRegistry now throws InvalidInputException (HTTP 400) for
both unknown driver type and unregistered provider name, giving callers
a clear client error instead of a server 500.

Provider re-registration with the same name is idempotent (overwrites)
and is now explicitly documented by test.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…atch

Fixed channel message triggers now behave consistently with HTTP stubs:
only the first (highest-priority) matching stub fires. Previously all
matching stubs were executed, which was inconsistent and confusing.

findMatchingFixedChannelStubs renamed to findMatchingFixedChannelStub
and returns Optional<MessageStubMapping> to make the single-match
semantics explicit.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…hierarchy; createFixedChannel returns LoggedFixedChannel

FixedChannel (DTO) → FixedChannelDefinition; FixedMessageChannel (live channel) → FixedChannel,
following the Definition/Instance pattern used elsewhere in WireMock.

LoggedMessageChannel is now a sealed interface with two concrete types:
- LoggedFixedChannel: carries providerName, channelName
- LoggedRequestInitiatedChannel: carries initiatingRequest

Both have Builder and transform(), and use @JsonTypeInfo with the existing
"type" field as the polymorphic discriminator so round-trip JSON works cleanly.

Admin.createFixedChannel now returns LoggedFixedChannel (via LoggedMessageChannel),
which CreateFixedChannelTask returns as HTTP 201. WireMock.createFixedChannel
returns the channel UUID. FixedMessageChannelAcceptanceTest stores the UUID
and asserts channelType/channelId on the resulting journal event.

MessageServeEvent factory methods for fixed channels now accept MessageChannel
so channelId is populated from the live channel's UUID.

NonEmptyFieldsSerializer strips empty containers, false booleans, and -1
integers — applied to LoggedRequestInitiatedChannel.getInitiatingRequest()
so the initiating request JSON omits default/sentinel fields without
modifying LoggedRequest.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…l in MessageServeEvent

The three flat fields are replaced by a single nested LoggedMessageChannel,
using the sealed polymorphic hierarchy. JSON changes from:
  "channelType": "websocket", "channelId": "...", "channelRequest": {...}
to:
  "channel": { "type": "websocket", "id": "...", "open": true, ... }

Derived @JsonIgnore getters for getChannelType(), getChannelId(), and
getChannelRequest() preserve backward-compat for all Java callers.

Factory methods simplified: ChannelType+UUID+Request overloads removed;
sentToFixedChannel and receivedOnFixedChannel removed — all callers now
use the unified sent/receivedMatched/receivedUnmatched factories which
each have two overloads (MessageChannel and LoggedMessageChannel).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
GetMessageChannelTask serves GET /channels/{id}, returning the channel
as a LoggedMessageChannel JSON payload (200) or 404 if not found.

Admin.getMessageChannel(UUID) returns SingleMessageChannelResult,
consistent with the wrapper pattern used by other single-resource
methods on this interface (getMessageServeEvent, getStubMapping, etc.).
HttpAdminClient catches ClientError (from the 404) and returns an
empty result rather than propagating.

AdminApiTest.getChannelByIdReturnsCorrectPayload asserts the full JSON
payload of the response.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Closes a request-initiated channel before removal so the WebSocket
connection is properly terminated. Also exposes removeMessageChannel
on WireMock, WireMockServer, HttpAdminClient and DslWrapper.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@tomakehurst tomakehurst marked this pull request as ready for review June 3, 2026 08:49
@tomakehurst tomakehurst requested a review from a team as a code owner June 3, 2026 08:49
@tomakehurst tomakehurst merged commit d4773be into master Jun 3, 2026
5 checks passed
@tomakehurst tomakehurst deleted the fixed-message-channels branch June 3, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant