Skip to content

[api] add RFC9727 support#228

Merged
capcom6 merged 3 commits into
masterfrom
api/rfc9727-api-catalog
May 28, 2026
Merged

[api] add RFC9727 support#228
capcom6 merged 3 commits into
masterfrom
api/rfc9727-api-catalog

Conversation

@capcom6

@capcom6 capcom6 commented May 5, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added an API catalog endpoint (/.well-known/api-catalog) with GET and HEAD that returns an RFC‑9727 linkset, sets Link and Cache‑Control headers, and enforces a modest rate limit.
  • Bug Fixes / Discoverability

    • Third‑party API routes now include a Link header pointing to the API catalog for easier discovery.
  • Chores

    • Ingress/router updated to expose /.well-known and match GET/HEAD; CI Terraform init/validate step adjusted.
  • Documentation

    • Added local request examples for the API catalog (HEAD and GET).

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 249c18df-4b7a-4fce-95e3-200bcffe2d0e

📥 Commits

Reviewing files that changed from the base of the PR and between 885562c and 38344d6.

📒 Files selected for processing (6)
  • .github/workflows/terraform-validate.yml
  • api/requests.http
  • deployments/docker-swarm-terraform/main.tf
  • internal/sms-gateway/handlers/3rdparty.go
  • internal/sms-gateway/handlers/api_catalog.go
  • internal/sms-gateway/handlers/module.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/sms-gateway/handlers/module.go
  • deployments/docker-swarm-terraform/main.tf
  • internal/sms-gateway/handlers/3rdparty.go
  • .github/workflows/terraform-validate.yml
  • internal/sms-gateway/handlers/api_catalog.go

📝 Walkthrough

Walkthrough

Adds a new RFC 9727 /.well-known/api-catalog endpoint (GET/HEAD) serving a linkset JSON with rate limiting, advertises the catalog via Link headers on third-party routes, wires the handler into the handlers module, updates Traefik labels for well-known routing, adds local HEAD/GET request examples, and adjusts the Terraform validate workflow.

Changes

API Catalog Feature

Layer / File(s) Summary
API Catalog Handler Implementation
internal/sms-gateway/handlers/api_catalog.go
APICatalogHandler struct and constructor; GET endpoint constructs and returns RFC 9727 linkset JSON (service-desc, service-doc, status) from configured host and path with application/linkset+json content type and Cache-Control; HEAD endpoint returns 200 with headers only; helper methods resolve public host/path; both routes registered with a 60 req/min sliding-window limiter.
Third-party Route Link Header Middleware
internal/sms-gateway/handlers/3rdparty.go
Adds middleware on the /3rdparty/v1 router that sets Link: </.well-known/api-catalog>; rel="api-catalog" and calls Next() for downstream handlers.
Handler Module Dependency Injection
internal/sms-gateway/handlers/module.go
newAPICatalogHandler wired into the fx handlers module as an additional root HTTP handler.
Traefik well-known Router Labels
deployments/docker-swarm-terraform/main.tf
Adds Traefik router labels traefik.http.routers.${var.app-name}-wellknown.* to match GET /.well-known on Host(api.sms-gate.app) with https entrypoints and le TLS resolver.
Local Request Examples
api/requests.http
HEAD and GET request examples for testing /.well-known/api-catalog endpoint locally.
Terraform Validate Workflow Adjustments
.github/workflows/terraform-validate.yml
Removes job-level AWS credential environment variables and updates Terraform init to terraform init -backend=false before running terraform validate in the deployments directory.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[api] add RFC9727 support' directly and clearly summarizes the main change: implementing RFC9727 support across the API codebase, evident from the new API catalog handler, headers, and infrastructure configuration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

🤖 Pull request artifacts

Platform File
🐳 Docker GitHub Container Registry
🍎 Darwin arm64 server_Darwin_arm64.tar.gz
🍎 Darwin x86_64 server_Darwin_x86_64.tar.gz
🐧 Linux arm64 server_Linux_arm64.tar.gz
🐧 Linux i386 server_Linux_i386.tar.gz
🐧 Linux x86_64 server_Linux_x86_64.tar.gz
🪟 Windows arm64 server_Windows_arm64.zip
🪟 Windows i386 server_Windows_i386.zip
🪟 Windows x86_64 server_Windows_x86_64.zip

@capcom6

capcom6 commented May 6, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@capcom6 capcom6 force-pushed the api/rfc9727-api-catalog branch 2 times, most recently from c133dc1 to 853ebfa Compare May 7, 2026 06:29
@capcom6

capcom6 commented May 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@capcom6

capcom6 commented May 9, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@capcom6 capcom6 force-pushed the api/rfc9727-api-catalog branch from 853ebfa to 0aed2c8 Compare May 12, 2026 04:32
@capcom6

capcom6 commented May 13, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@capcom6

capcom6 commented May 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/sms-gateway/handlers/api_catalog.go`:
- Line 40: The URL composition uses fmt.Sprintf("https://%s/%s/…", host, path)
which yields double slashes when PublicPath (path) is empty; normalize the path
by trimming surrounding slashes (e.g., using strings.Trim(path, "/")) and then
build the URL by conditionally prefixing a single "/" only when the trimmed path
is non-empty — update each occurrence (the fmt.Sprintf calls that produce
"anchor" and the other locations referenced: lines around the "anchor"
fmt.Sprintf plus the similar usages at the other spots) and add the strings
import if missing so the final URLs never contain "//" when PublicPath is empty.
- Around line 31-33: The Content-Type header set to the RFC 9727 media type at
the top of the handler is being overwritten by c.JSON() on line 63; update the
api catalog handler so the response uses the RFC9727 media type by either
passing the desired media type as the optional second argument to c.JSON(...) or
by calling c.Set(fiber.HeaderContentType, `application/linkset+json;
profile="https://www.rfc-editor.org/info/rfc9727"`) immediately after the
c.JSON(...) call in the same handler function (referencing the c.JSON call and
the header set at the top of the handler in api_catalog.go) so the final
Content-Type sent to clients is correct.

In `@internal/sms-gateway/jwt/metrics.go`:
- Around line 10-11: You changed metricsSubsystem from "auth" to "jwt", which
renames all metric series and can break dashboards; revert metricsSubsystem back
to "auth" (keep metricsNamespace = "sms" and metricsSubsystem = "auth") to
preserve identity, and if you truly need new "jwt" metrics introduce a new
constant (e.g., metricsSubsystemJWT) and register new metric names explicitly
while keeping the old ones for migration; also verify metric registration code
that concatenates namespace/subsystem does not produce duplicated prefixes like
"sms_jwt_jwt_" (check any register/WithLabel or NewCounterVec calls that use
metricsNamespace/metricsSubsystem).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 777b6d3b-32f8-4352-bf6b-4f18822a1616

📥 Commits

Reviewing files that changed from the base of the PR and between bae4b50 and 0aed2c8.

📒 Files selected for processing (10)
  • api/requests.http
  • internal/sms-gateway/handlers/3rdparty.go
  • internal/sms-gateway/handlers/api_catalog.go
  • internal/sms-gateway/handlers/module.go
  • internal/sms-gateway/jwt/metrics.go
  • internal/sms-gateway/modules/events/metrics.go
  • internal/sms-gateway/modules/push/metrics.go
  • internal/sms-gateway/modules/sse/metrics.go
  • internal/sms-gateway/online/metrics.go
  • internal/worker/executor/metrics.go

Comment thread internal/sms-gateway/handlers/api_catalog.go Outdated
Comment thread internal/sms-gateway/handlers/api_catalog.go Outdated
Comment thread internal/sms-gateway/jwt/metrics.go Outdated
@capcom6 capcom6 force-pushed the api/rfc9727-api-catalog branch 2 times, most recently from d4155f0 to 4eedd34 Compare May 20, 2026 00:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
internal/sms-gateway/handlers/api_catalog.go (2)

72-72: ⚡ Quick win

Use fiber.HeaderLink constant for consistency.

Line 31 uses the fiber.HeaderLink constant, but this line uses the string literal "Link". Use the constant for consistency.

♻️ Suggested fix
-	c.Set("Link", `</.well-known/api-catalog>; rel="api-catalog"`)
+	c.Set(fiber.HeaderLink, `</.well-known/api-catalog>; rel="api-catalog"`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/sms-gateway/handlers/api_catalog.go` at line 72, Replace the string
literal "Link" used in the c.Set call with the Fiber header constant to maintain
consistency; specifically, update the call that sets the Link header (the
c.Set("Link", `</.well-known/api-catalog>; rel="api-catalog"`) invocation) to
use fiber.HeaderLink instead of the raw string so the code consistently
references the header constant.

84-86: ⚡ Quick win

Return type should be string, not any.

The method returns strings.Trim(...) which is a string, and it's used as a string at line 35. Declare the correct return type for type safety.

♻️ Suggested fix
-func (h *APICatalogHandler) getPath() any {
+func (h *APICatalogHandler) getPath() string {
 	return strings.Trim(h.config.PublicPath, "/")
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/sms-gateway/handlers/api_catalog.go` around lines 84 - 86, Change
the return type of method getPath on APICatalogHandler from any to string:
update the signature of func (h *APICatalogHandler) getPath() any to return
string, since it returns strings.Trim(...) and callers (e.g., code that treats
getPath() as a string) rely on a string value; adjust any related uses if they
expect an interface{} but no other logic changes are needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/sms-gateway/handlers/3rdparty.go`:
- Around line 77-81: The middleware registered via router.Use currently sets the
Link header after calling c.Next(), which can miss responses already sent by
downstream handlers; move the c.Set(fiber.HeaderLink,
`</.well-known/api-catalog>; rel="api-catalog"`) call to before err := c.Next()
inside the anonymous middleware so the header is always added, then call
c.Next(), capture and return the error as before (keeping the //nolint:wrapcheck
comment).

---

Nitpick comments:
In `@internal/sms-gateway/handlers/api_catalog.go`:
- Line 72: Replace the string literal "Link" used in the c.Set call with the
Fiber header constant to maintain consistency; specifically, update the call
that sets the Link header (the c.Set("Link", `</.well-known/api-catalog>;
rel="api-catalog"`) invocation) to use fiber.HeaderLink instead of the raw
string so the code consistently references the header constant.
- Around line 84-86: Change the return type of method getPath on
APICatalogHandler from any to string: update the signature of func (h
*APICatalogHandler) getPath() any to return string, since it returns
strings.Trim(...) and callers (e.g., code that treats getPath() as a string)
rely on a string value; adjust any related uses if they expect an interface{}
but no other logic changes are needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bcfde4b3-c910-4b2b-bfe4-17f298d97ab2

📥 Commits

Reviewing files that changed from the base of the PR and between d4155f0 and 4eedd34.

📒 Files selected for processing (4)
  • api/requests.http
  • internal/sms-gateway/handlers/3rdparty.go
  • internal/sms-gateway/handlers/api_catalog.go
  • internal/sms-gateway/handlers/module.go
✅ Files skipped from review due to trivial changes (1)
  • api/requests.http

Comment thread internal/sms-gateway/handlers/3rdparty.go
@capcom6 capcom6 force-pushed the api/rfc9727-api-catalog branch from 434c35e to a991b72 Compare May 21, 2026 00:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/sms-gateway/handlers/api_catalog.go`:
- Around line 44-61: The catalog links in
internal/sms-gateway/handlers/api_catalog.go are inconsistent with the mounted
routes and hardcode https; update api_catalog.go so (1) base is not hardcoded to
"https" but derives the request scheme (e.g., from the Fiber context or
X-Forwarded-Proto) when building base, (2) make the "status" href match the
actual readiness endpoint (either change the href to
fmt.Sprintf("%s/health/ready", base) to point to the registered GET
/health/ready under the /3rdparty/v1 group or keep the href and instead adjust
the swagger `@Router` annotations in internal/sms-gateway/handlers/health.go so
the OpenAPI spec contains /3rdparty/v1/health/ready), and (3) ensure "anchor"
and "service-desc" remain pointing at the mounted group and docs (anchor,
service-desc, status in api_catalog.go; healthHandler in 3rdparty.go; health.go
`@Router` annotations) so the catalog and generated OpenAPI paths align across
deployments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2990675e-6969-46c0-aa83-c94e6306645b

📥 Commits

Reviewing files that changed from the base of the PR and between 434c35e and a991b72.

📒 Files selected for processing (4)
  • api/requests.http
  • internal/sms-gateway/handlers/3rdparty.go
  • internal/sms-gateway/handlers/api_catalog.go
  • internal/sms-gateway/handlers/module.go
✅ Files skipped from review due to trivial changes (1)
  • api/requests.http

Comment thread internal/sms-gateway/handlers/api_catalog.go
@capcom6 capcom6 marked this pull request as ready for review May 21, 2026 07:30
@capcom6 capcom6 added the ready label May 21, 2026
@capcom6 capcom6 force-pushed the api/rfc9727-api-catalog branch from a991b72 to 804c32d Compare May 24, 2026 05:09
@github-actions github-actions Bot removed the ready label May 24, 2026
@capcom6 capcom6 added ready deployed The PR is deployed on staging labels May 24, 2026
@github-actions github-actions Bot removed deployed The PR is deployed on staging ready labels May 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deployments/docker-swarm-terraform/main.tf`:
- Around line 164-166: Update the Traefik router rule value so HEAD requests are
allowed alongside GET: locate the label key
"traefik.http.routers.${var.app-name}-wellknown.rule" and change the value
string's Method(`GET`) matcher to include HEAD (for example replace
Method(`GET`) with (Method(`GET`) || Method(`HEAD`))) so both GET and HEAD
/.well-known requests match this router.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2aefbc09-1c2d-4c43-8814-0fc411277471

📥 Commits

Reviewing files that changed from the base of the PR and between 804c32d and 2b0a24c.

📒 Files selected for processing (1)
  • deployments/docker-swarm-terraform/main.tf

Comment thread deployments/docker-swarm-terraform/main.tf
@capcom6 capcom6 force-pushed the api/rfc9727-api-catalog branch from 885562c to da2fb5d Compare May 26, 2026 02:04
@capcom6 capcom6 added the deployed The PR is deployed on staging label May 26, 2026
@capcom6 capcom6 force-pushed the api/rfc9727-api-catalog branch from 89fec22 to 38344d6 Compare May 27, 2026 00:31
@github-actions github-actions Bot removed the deployed The PR is deployed on staging label May 27, 2026
@capcom6 capcom6 added the deployed The PR is deployed on staging label May 27, 2026
@capcom6 capcom6 merged commit a013bfd into master May 28, 2026
12 checks passed
@capcom6 capcom6 deleted the api/rfc9727-api-catalog branch May 28, 2026 02:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deployed The PR is deployed on staging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant