Skip to main content

Specs

Introduction

Specs, short for Specification(s), are the way Discord4J encapsulates API request parameter building from our core module. They can handle requests that contain multiple optional properties.

There are multiple ways of interacting with them and building them, starting from v3.2.

New immutable Specs (v3.2)

Fluent Mono request

An API request without parameters is a Mono that can be subscribed or composed to perform the request.

channel.createMessage("Hey")
.withComponents(ActionRow.of(Button.success("hey", "Click me!")))
.subscribe();

Remember that nothing happens until you subscribe! This can be used to your advantage by reusing the Mono pipeline you build after each withX.

Spec with copy methods

An immutable Spec that can be shared across components and used for templating. Each with call copies the previous instance to maintain immutability and ensure thread-safety.

MessageCreateSpec spec = MessageCreateSpec.create()
.withContent("Hey")
.withComponents(ActionRow.of(Button.success("hey", "Click me!")));

channel.createMessage(spec).subscribe();

Builder pattern

A more traditional builder pattern approach. Make sure you call builder on the right spec for an instance you can configure to your liking.

MessageCreateSpec spec = MessageCreateSpec.builder()
.content("Hey")
.addComponent(ActionRow.of(Button.success("hey", "Click me!")))
.build();

channel.createMessage(spec).subscribe();

Consumer-based Specs

Prior to Discord4J v3.2 this was the only alternative to building requests, it follows two principles:

  1. The end-user does not construct the builder.
  2. The end-user does not construct the finalized object.

These two characteristics provide Discord4J with a flexible approach regarding constructing requests, without breaking the API at a future date.

Example

All Specs that an end user interacts with will be provided via a Consumer. For example, for MessageChannel#createMessage:

messageChannel.createMessage(spec -> /* manipulate the spec */)

One may note that all Spec instances have an asRequest method. This method is an internal behaviorally implementation-specific method and should never be called by the end-user. Once the Spec has been "built", simply leave it alone.

Mono<Message> message = messageChannel.createMessage(messageSpec -> {
messageSpec.setContent("Content not in an embed!");
// You can see in this example, even with simple singular property defining specs
// the syntax is concise
messageSpec.setEmbed(embedSpec ->
embedSpec.setDescription("Description is in an embed!")
);
});

Templates

note

For templates, it is more convenient to use the new immutable specs.

Using Consumer#andThen allows this pattern to be implemented:

Consumer<EmbedCreateSpec> template = spec -> {
// Edit the spec as you normally would
};
...
// embedSpec can be edited as you normally would, but the edits from template will
// already be applied
Mono<Message> message = messageChannel.createMessage(messageSpec ->
messageSpec.setEmbed(template.andThen(embedSpec -> {}))
);

This pattern additionally helps protect the end-user from accidentally sharing specs across multiple invocations, as the state is never "reset" and mutating these legacy Spec instances is not thread-safe.