Skip to content

feat(methods): add blocks.validate method - #1622

Draft
zimeg wants to merge 16 commits into
mainfrom
blocks-validate
Draft

feat(methods): add blocks.validate method#1622
zimeg wants to merge 16 commits into
mainfrom
blocks-validate

Conversation

@zimeg

@zimeg zimeg commented Jul 14, 2026

Copy link
Copy Markdown
Member

This pull request adds the blocks.validate Web API method to the Slack API client for Java.

  • Callers validate Block Kit payloads via slack.methods().blocksValidate(req) and the async equivalent slack.methodsAsync().blocksValidate(req).
  • blocks.validate is unauthenticated — no token or scopes required — so the request carries no token (modeled like api.test).
  • Rate limiting: modeled as a special tier (SpecialTier_blocks_validate) per the method reference.

Request — typed inputs with string escape hatches

Each of the three payload kinds accepts a typed value or a raw JSON string (the *AsString field; the string wins if both are set), mirroring ChatPostMessageRequest / ViewsOpenRequest:

field typed string
blocks List<LayoutBlock> blocksAsString
message MessagePayload messageAsString
view View viewAsString

MessagePayload is a small nested {blocks, attachments} type rather than the response-shaped Message model. The accepted shape was verified against the live endpoint: attachments (with nested blocks) are validated, whereas text is rejected as an additional property — so text is intentionally omitted, and the type is valid-by-construction.

Response — validation errors

On a failed validation the response returns errors[], each with pointer (a JSON pointer to the offending element), code, message, and a constraint object. The constraint's expected/got are modeled as JsonElement because their shape is polymorphic across constraint types (a string array for enum, a number for min_length/max_length/max_items, or absent for all_of) — a single static type here caused a Gson JsonSyntaxException on numeric constraints.

Testing

  • ./mvnw test -pl slack-api-client -Dtest=test_locally.api.methods.BlocksTest — local mock server, sync + async, typed and string paths (4 tests).
  • test_with_remote_apis.methods.blocks_Test exercises the live API: a well-formed payload, a malformed payload (invalid_blocks + populated errors[]), and a numeric-constraint case (max_items) that guards the polymorphic expected/got deserialization (3 tests).

Companion example

An end-to-end example lives in slack-samples/bolt-java-examples#53, building blocks with the SDK builders and validating them.

Category

  • bolt (Bolt for Java)
  • bolt-{sub modules} (Bolt for Java - optional modules)
  • slack-api-client (Slack API Clients)
  • slack-api-model (Slack API Data Models)
  • slack-api-*-kotlin-extension (Kotlin Extensions for Slack API Clients)
  • slack-app-backend (The primitive layer of Bolt for Java)

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you agree to those rules.

🤖 Generated with Claude Code

Add the blocks.validate Web API method to the Slack API client. Callers
can validate Block Kit payloads via methods().blocksValidate(req) (and the
async equivalent), passing blocks, message, or view as JSON-encoded
strings. No scopes are required.

Adds the endpoint constant, sync and async interface methods and impls,
the request form builder, the request/response model classes (with an
errors[] list of code/message/pointer/relatedComponent), the Tier3 rate
limit, a local test, and a response sample for type generation.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.51724% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.86%. Comparing base (b7562e3) to head (69ea477).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...java/com/slack/api/methods/RequestFormBuilder.java 50.00% 5 Missing and 5 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1622      +/-   ##
============================================
- Coverage     72.87%   72.86%   -0.02%     
- Complexity     4522     4533      +11     
============================================
  Files           479      480       +1     
  Lines         14390    14418      +28     
  Branches       1503     1512       +9     
============================================
+ Hits          10487    10505      +18     
- Misses         3013     3018       +5     
- Partials        890      895       +5     
Flag Coverage Δ
jdk-14 72.86% <65.51%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

zimeg and others added 4 commits August 14, 2026 18:30
blocks.validate has special rate-limiting conditions rather than a standard
tier (see https://docs.slack.dev/reference/methods/blocks.validate), so model
it as SpecialTier_blocks_validate instead of Tier3. Regenerate the rate-limit
metadata so the committed rate_limit_tiers.json matches the generated output,
which also sorts blocks.validate into its correct alphabetical position — this
is what was tripping the CI tree-drift check.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Add a blocks_Test integration test under test_with_remote_apis that exercises
blocks.validate against the real API: a well-formed payload validates with no
errors, and a malformed payload surfaces the live validation feedback
(recording the actual contract — ok=false with an error, or ok=true with a
populated errors[] carrying code/message/pointer). Refresh the method-coverage
scrape marker in MethodsTest to reflect the current 310-endpoint list.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
The method was consistently placed after the bookmarks family, but "blocks"
sorts before "bookmarks". Move every insertion site into alphabetical order:
request/response imports, the sync/async interface declarations and their
implementations, the RequestFormBuilder toForm mapping, the rate-limit tier
registration, and the method-coverage string in MethodsTest. Pure reordering —
no behavior change.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
blocks.validate requires no token or scopes
(https://docs.slack.dev/reference/methods/blocks.validate), so it should not
send an Authorization header. Model it like api.test: drop the token field from
BlocksValidateRequest (overriding getToken() to return null) and call it through
the tokenless postFormAndParseResponse path. The local BlocksTest now asserts the
request round-trips (the shared mock answers a tokenless call with not_authed);
end-to-end ok/errors[] behavior is covered by the remote blocks_Test.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
zimeg and others added 2 commits August 25, 2026 14:49
The `blocks.validate` response `errors[]` entries carry pointer / code /
message / constraint per the method reference. The Error model previously
declared a `relatedComponent` field (copied from apps.manifest.validate),
which is not part of the blocks.validate contract, and omitted `constraint`
— the structured object describing what was expected
(e.g. {"type": "enum", "expected": ["plain_text", "mrkdwn"]}).

Replace `relatedComponent` with a `Constraint` nested type, fix the mock
sample to the documented shape, and clarify the remote-test comments and
assertions to reflect the real contract.

Ref: https://docs.slack.dev/reference/methods/blocks.validate#validation-errors

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
zimeg and others added 9 commits August 25, 2026 15:06
Verified against the real (unauthenticated) blocks.validate endpoint:

- A section with an unsupported text "type" fails; a section with no
  text/fields returns ok=true. Switch the malformed remote test to the
  former so it deterministically exercises the errors[] path, and assert
  the confirmed shape (ok=false, error="invalid_blocks").
- constraint carries a "got" field for enum failures (e.g.
  {"type":"enum","expected":[...],"got":"invalid"}) and may be just
  {"type":"all_of"} for composite-schema failures; add Constraint.got.
- Update the mock fixture to a real captured response, and trim the
  redundant comments down to the field contract + doc link.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
The json-logs/samples/api fixtures are schema skeletons the mock server
replays (blank strings, placeholder ids), not real responses — see
apps.manifest.validate.json. Revert the fixture to that convention: one
errors[] entry with every field blanked, including the constraint object
(type/expected/got) so deserialization is still exercised. The real
contract learned from the live API stays reflected in the model and the
remote test, which is where it belongs.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
- Set the SpecialTier_blocks_validate quota to 60 requests/minute (was an
  unverified 600 copied from other special tiers).
- Call blocks.validate without a token in both tests, matching its
  unauthenticated contract; exempt it from the mock server's token gate
  (like api.test) so the recorded fixture is served.
- Local BlocksTest now asserts the deserialized errors[]/constraint shape
  instead of not_authed; the remote test exercises a payload that actually
  fails validation. Regenerate the mock fixture via the recorder.
- Drop the unsourced "hundreds of requests per minute" claim from the tier
  Javadoc.

Verified: test_locally.api.methods.BlocksTest, test_locally.api.MethodsTest,
and test_with_remote_apis.methods.blocks_Test all pass (JDK 17).

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Callers building Block Kit with the SDK's own LayoutBlock builders can now
pass them directly instead of hand-serializing to JSON. Add a typed
List<LayoutBlock> blocks field and rename the raw-JSON field to
blocksAsString, mirroring ChatPostMessageRequest (blocksAsString wins if
both are set). Tests exercise both the typed and string paths.

Verified: test_locally.api.methods.BlocksTest and
test_with_remote_apis.methods.blocks_Test pass (JDK 17).

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Add typed inputs for the message and view payloads, alongside the existing
*AsString escape hatches (string wins if both are set), matching the blocks
handling and views.open's view/viewAsString precedent.

- view uses the existing View model.
- message uses a small nested MessagePayload{blocks, attachments} rather
  than the response-shaped Message model. The accepted shape was verified
  against the live blocks.validate endpoint: attachments (with nested
  blocks) are validated, whereas text is rejected as an additional property,
  so text is intentionally omitted.

Verified: test_locally.api.methods.BlocksTest (typed blocks/message/view +
string paths) and test_with_remote_apis.methods.blocks_Test pass (JDK 17).

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…ment

The constraint's expected/got fields are polymorphic: an array of strings
for enum constraints (e.g. ["plain_text","mrkdwn"]), but a number for
length/count constraints (min_length/max_length/max_items) and absent for
all_of. Modeling expected as List<String> caused a Gson JsonSyntaxException
("Expected BEGIN_ARRAY but was NUMBER") whenever a numeric constraint was
returned. Use JsonElement so any shape deserializes; callers inspect it via
getAsInt()/getAsJsonArray()/etc.

Adds a remote test that triggers a max_items constraint (numeric
expected/got) to guard the regression.

Verified: test_locally.api.methods.BlocksTest (4) and
test_with_remote_apis.methods.blocks_Test (3) pass (JDK 17).

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
Move the docs link to a class-level Javadoc and drop the comment on the
getToken() override, matching ApiTestRequest and other unauthenticated
request objects.

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

@zimeg zimeg left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Callouts for the kind folks checking this out!

Comment on lines +54 to +59
@Data
@Builder
public static class MessagePayload {
private List<LayoutBlock> blocks;
private List<Attachment> attachments;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️‍🗨️ note: This is added to the request class for blocks.validate to bring matching arguments for:

  • blocks: blocksAsString
  • message: messageAsString
  • view: viewsAsString

Comment on lines +37 to +38
private JsonElement expected;
private JsonElement got;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📣 note: These can be handled with a typecheck in calling code but for now we avoid parsing responses into particular types here.

@zimeg zimeg added enhancement M-T: A feature request for new functionality project:slack-api-client project:slack-api-client semver:minor labels Aug 26, 2026
@zimeg zimeg self-assigned this Aug 26, 2026
@zimeg zimeg added this to the 1.50.1 milestone Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement M-T: A feature request for new functionality project:slack-api-client project:slack-api-client semver:minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant