Skip to content

Conversation

@GAlexIHU
Copy link
Contributor

@GAlexIHU GAlexIHU commented Sep 11, 2025

Overview

V2 for list and get

Fixes #(issue)

Notes for reviewer

Summary by CodeRabbit

  • New Features
    • Entitlements V2: list with multi-value filters (feature, customer keys/IDs, type, excludeInactive), pagination, ordering, and GET by entitlement ID (strict ID validation). Client libraries (Go/JS) updated with request builders, response types, and Zod validation defaults/limits.
  • Refactor
    • Consolidated/renamed V2 entitlement handler surface and routed V2 endpoints throughout server and drivers.
  • Documentation
    • OpenAPI/specs updated to include Entitlements V2 endpoints and models.

@GAlexIHU GAlexIHU requested a review from tothandras September 11, 2025 12:43
@GAlexIHU GAlexIHU requested a review from a team as a code owner September 11, 2025 12:43
@GAlexIHU GAlexIHU added the release-note/feature Release note: Exciting New Features label Sep 11, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 11, 2025

📝 Walkthrough

Walkthrough

Adds Entitlements V2: new list and get-by-id endpoints across OpenAPI/spec, generated server and client code, JS validators/schemas, router wiring, connector/adapter filters, and v2 driver handlers and interfaces.

Changes

Cohort / File(s) Summary
Generated server API
api/api.gen.go
Adds ListEntitlementsV2Params, middleware/wrappers for ListEntitlementsV2 and GetEntitlementByIdV2, ServerInterface additions and Unimplemented stubs, parameter binding middleware, route wiring, and updated embedded swagger.
Go client
api/client/go/client.gen.go
Adds ListEntitlementsV2Params, request constructors for /api/v2/entitlements and /api/v2/entitlements/{entitlementId}, client methods, typed response types/parsers, and updated embedded swagger spec.
JS client schemas
api/client/javascript/src/client/schemas.ts
Adds paths /api/v2/entitlements and /api/v2/entitlements/{entitlementId} and operations listEntitlementsV2 / getEntitlementByIdV2 with query/path typings and response/error schemas.
JS zod validators
api/client/javascript/src/zod/index.ts
Adds listEntitlementsV2QueryParams schema, pagination/limit/order defaults, getEntitlementByIdV2 ID regexp and path params validator, and exported constants.
OpenAPI specs
api/openapi.yaml, api/openapi.cloud.yaml
Declares GET /api/v2/entitlements and GET /api/v2/entitlements/{entitlementId} including parameters, patterns, responses, tags, operationIds (listEntitlementsV2, getEntitlementByIdV2).
TypeSpec additions
api/spec/src/cloud/main.tsp, api/spec/src/entitlements/v2/entitlements.tsp
Adds Entitlements V2 interface with list and get operations and new models: EntitlementV2 discriminated union (metered/static/boolean) plus customer/variant models.
Connector / adapter filters
openmeter/entitlement/connector.go, openmeter/entitlement/adapter/entitlement.go
Extends ListEntitlementsParams with CustomerKeys and CustomerIDs and applies corresponding HasCustomerWith + KeyIn/IDIn filters when provided.
V2 driver: handlers & interface rename
openmeter/entitlement/driver/v2/handler.go, openmeter/entitlement/driver/v2/customer.go, openmeter/entitlement/driver/v2/customer_metered.go
Renames CustomerEntitlementHandlerEntitlementHandler, updates receiver types/constructor names and adds ListEntitlements() / GetEntitlement() interface methods; adjusts receiver names across customer handlers.
V2 driver: new list/get handlers
openmeter/entitlement/driver/v2/entitlement.go
Implements ListEntitlements and GetEntitlement handlers, request/response/params types, validation/mapping from API params to connector calls, pagination handling, and mapping to api.EntitlementV2.
Router wiring
openmeter/server/router/router.go, openmeter/server/router/entitlement.go
Switches Router to use EntitlementHandler, wires top-level V2 entitlements endpoints (list/get) and additional V2 customer-entitlement endpoints, and adds corresponding router methods delegating to V2 handlers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat(api): add root entitlement v2 APIs" is concise, follows conventional-commit style, and accurately summarizes the primary change (adding root-level v2 entitlement endpoints for list and get). It clearly conveys the main intent without noisy file lists or emojis, so a teammate scanning history will understand the primary change.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/entitlement-root-v2

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (30)
api/openapi.yaml (3)

11410-11418: Remove redundant explode on boolean and clarify description.

explode has no effect for primitives and may confuse generators. The description is unclear.

Apply:

-          description: Exclude inactive entitlements in the response (those scheduled for later or earlier)
+          description: Exclude entitlements that are not currently active (scheduled in the future or already ended).
           schema:
             type: boolean
             default: false
-          explode: false
           style: form

11485-11489: Tighten path parameter constraints for entitlementId.

Add length constraints to match the 26-char ULID pattern and aid client validation.

Apply:

           schema:
             type: string
             pattern: ^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$
+            minLength: 26
+            maxLength: 26
             example: 01G65Z755AFWAKHE12NY0CQ9FH

11404-11409: Be explicit about array serialization for entitlementType.

Minor: some generators handle arrays better with explicit explode.

Apply:

           schema:
             type: array
             items:
               $ref: '#/components/schemas/EntitlementType'
           style: form
+          explode: true
openmeter/entitlement/connector.go (1)

73-74: Fix typo in comment

"featueres" → "features".

-// For consistency, it is forbidden for entitlements to be created for featueres the keys of which could be mistaken for entitlement IDs.
+// For consistency, entitlements must not be created for features whose keys could be mistaken for entitlement IDs.
openmeter/server/router/router.go (1)

280-283: Refresh comment: handler is not just customer-scoped anymore

Reflects root endpoints too.

-// V2 entitlement handler for customer-scoped operations
+// V2 entitlement handler (customer-scoped and root operations)
openmeter/entitlement/driver/v2/handler.go (1)

11-12: Doc comment: broaden scope beyond customer endpoints

Handler now also exposes root list/get.

-// EntitlementHandler exposes V2 customer entitlement endpoints
+// EntitlementHandler exposes V2 entitlement endpoints (customer-scoped and root)
openmeter/entitlement/driver/v2/customer.go (1)

302-305: Return nil response on error

Minor cleanup: return nil instead of request when emitting an error to avoid confusion.

-			if cus != nil && cus.IsDeleted() {
-				return request, models.NewGenericPreConditionFailedError(
+			if cus != nil && cus.IsDeleted() {
+				return nil, models.NewGenericPreConditionFailedError(
 					fmt.Errorf("customer is deleted [namespace=%s customer.id=%s]", cus.Namespace, cus.ID),
 				)
 			}
openmeter/entitlement/driver/v2/entitlement.go (2)

63-65: Remove duplicated OrderBy mapping

OrderBy is set twice (first via camel→snake, then a switch). Keep one path to avoid drift.

-				OrderBy: entitlement.ListEntitlementsOrderBy(
-					strcase.CamelToSnake(defaultx.WithDefault((*string)(params.OrderBy), string(entitlement.ListEntitlementsOrderByCreatedAt))),
-				),
+				OrderBy: entitlement.ListEntitlementsOrderByCreatedAt,
@@
-			switch defaultx.WithDefault(params.OrderBy, "") {
-			case "createdAt":
-				p.OrderBy = entitlement.ListEntitlementsOrderByCreatedAt
-			case "updatedAt":
-				p.OrderBy = entitlement.ListEntitlementsOrderByUpdatedAt
-			default:
-				p.OrderBy = entitlement.ListEntitlementsOrderByCreatedAt
-			}
+			if params.OrderBy != nil {
+				switch *params.OrderBy {
+				case "createdAt":
+					p.OrderBy = entitlement.ListEntitlementsOrderByCreatedAt
+				case "updatedAt":
+					p.OrderBy = entitlement.ListEntitlementsOrderByUpdatedAt
+				}
+			}

Also applies to: 78-85


44-51: Include allowed values in validation errors

Helps clients debug bad inputs quickly.

-					return entitlement.ListEntitlementsParams{}, commonhttp.NewHTTPError(http.StatusBadRequest, errors.New("invalid entitlement type"))
+					return entitlement.ListEntitlementsParams{}, commonhttp.NewHTTPError(
+						http.StatusBadRequest,
+						fmt.Errorf("invalid entitlement type: %q (allowed: %v)", et, entitlement.EntitlementType("").StrValues()),
+					)
api/openapi.cloud.yaml (4)

11361-11364: Clarify/validate mixed pagination (page/pageSize vs offset/limit).

Spec allows both styles simultaneously but only mentions that “page takes precedence.” This is ambiguous and client-hostile.

  • Either enforce mutual exclusivity (400 if both styles are provided) or remove one style.
  • At minimum, codify behavior in the description:
-        If page is provided that takes precedence and the paginated response is returned.
+        If both pagination styles are provided, the request is invalid and MUST return 400.
+        Use either page/pageSize or offset/limit, not both. If page is provided, the server will ignore offset/limit.

If server already enforces this, please add a brief note under 400 explaining the validation error. Do you want me to propose an OpenAPI oneOf param grouping pattern to document exclusivity?

Also applies to: 11422-11427


11413-11421: Remove redundant explode for boolean query param.

explode: false is irrelevant for a non-array, non-object param and can confuse generators.

         - name: excludeInactive
           in: query
           required: false
           description: Exclude inactive entitlements in the response (those scheduled for later or earlier)
           schema:
             type: boolean
             default: false
-          explode: false
           style: form

Also consider rephrasing to: “Exclude entitlements not currently active (start in the future or ended).”


11358-11364: Document required auth scope for admin-only endpoint.

Description says “administrative purposes only” but no per-operation security block is present here. If global security allows user tokens, tighten scopes for this operation.

I can add a security: section referencing your admin scope (e.g., OAuth2: [entitlements:read:all]) if you share the canonical scope name.


11401-11412: Minor: add explicit explode:true for arrays or inline examples.

Default is explode=true for form, but some generators are sensitive. Optionally set explode: true and/or provide examples: to lock behavior.

api/client/javascript/src/client/schemas.ts (3)

2363-2367: Consistent capitalization: “ID” instead of “id”.

Use “ID” consistently in title/description.

Apply this diff:

-    /**
-     * Get entitlement by id
-     * @description Get entitlement by id.
-     */
+    /**
+     * Get entitlement by ID
+     * @description Get entitlement by ID.
+     */

26079-26080: Tighten wording for excludeInactive.

Minor clarity improvement.

Apply this diff:

-        /** @description Exclude inactive entitlements in the response (those scheduled for later or earlier) */
+        /** @description Exclude inactive entitlements (scheduled for the future or already expired). */

26081-26096: Clarify precedence between page-based and offset/limit pagination.

You accept both styles but precedence isn’t documented here. Recommend stating that page-based pagination overrides offset/limit when both are supplied.

Apply this diff:

-        /** @description Page index.
-         *
-         *     Default is 1. */
+        /** @description Page index.
+         *
+         *     Default is 1. If provided together with offset/limit, page takes precedence and offset/limit are ignored. */
@@
-        /** @description Number of items to skip.
-         *
-         *     Default is 0. */
+        /** @description Number of items to skip.
+         *
+         *     Default is 0. Ignored when 'page' is provided. */
@@
-        /** @description Number of items to return.
-         *
-         *     Default is 100. */
+        /** @description Number of items to return.
+         *
+         *     Default is 100. Ignored when 'page' is provided. */
openmeter/server/router/entitlement.go (1)

142-154: Add route-doc comments for consistency with V1 and other V2 sections.
Keeps router readable and aligned with generated specs.

Apply:

 // ------------------------------------------------------------
 // Entitlement APIs (V2)
 // ------------------------------------------------------------

+// List entitlements (V2)
+// (GET /api/v2/entitlements)
 func (a *Router) ListEntitlementsV2(w http.ResponseWriter, r *http.Request, params api.ListEntitlementsV2Params) {
 	a.entitlementV2Handler.ListEntitlements().With(params).ServeHTTP(w, r)
 }

+// Get an entitlement (V2)
+// (GET /api/v2/entitlements/{entitlementId})
 func (a *Router) GetEntitlementByIdV2(w http.ResponseWriter, r *http.Request, entitlementId string) {
 	a.entitlementV2Handler.GetEntitlement().With(entitlementdriverv2.GetEntitlementHandlerParams{
 		EntitlementId: entitlementId,
 	}).ServeHTTP(w, r)
 }
api/spec/src/entitlements/v2/entitlements.tsp (5)

32-39: customerKeys example and wording are incorrect

Clarify it’s “customer keys” and fix the example/query name.

   /**
-   * Filtering by multiple customers.
-   *
-   * Usage: `?customer=customer-1&customer-3`
+   * Filtering by multiple customer keys.
+   *
+   * Usage: `?customerKeys=customer-1&customerKeys=customer-3`
    */
   @query(#{ explode: true })
   customerKeys?: string[],

56-61: Clarify excludeInactive semantics

Spell out what “inactive” means and note the default.

   /**
-   * Exclude inactive entitlements in the response (those scheduled for later or earlier)
+   * Exclude entitlements not active at the current time (future-scheduled or expired). Default: false.
    */
   @query
   excludeInactive?: boolean = false,

15-19: Pagination docs don’t match behavior; both styles are always paginated

Spec always returns a paginated envelope; doc should state cursor vs offset precedence.

   /**
-   * List all entitlements for all the subjects and features. This endpoint is intended for administrative purposes only.
-   * To fetch the entitlements of a specific subject please use the /api/v1/subjects/{subjectKeyOrID}/entitlements endpoint.
-   * If page is provided that takes precedence and the paginated response is returned.
+   * List all entitlements for all subjects and features (admin use).
+   * For a specific subject use /api/v1/subjects/{subjectKeyOrID}/entitlements.
+   * Supports cursor (page) and offset/limit; if both provided, cursor takes precedence. The response is always paginated.
    */

Also applies to: 62-65


48-55: Include all enum values in example

Add “static” to the usage example for completeness.

   /**
    * Filtering by multiple entitlement types.
    *
-   * Usage: `?entitlementType=metered&entitlementType=boolean`
+   * Usage: `?entitlementType=metered&entitlementType=boolean&entitlementType=static`
    */

74-76: Optional: add example for entitlementId

Improves SDK docs and try-it-out UX.

   get(
     @path
-    entitlementId: ULID,
+    @example("01ARZ3NDEKTSV4RRFFQ69G5FAV") entitlementId: ULID,
   ): EntitlementV2 | OpenMeter.NotFoundError | OpenMeter.CommonErrors;
api/client/javascript/src/zod/index.ts (5)

17984-17985: Fix JSDoc formatting and grammar.

Missing leading asterisks and a minor grammar issue in the doc block.

- To fetch the entitlements of a specific subject please use the /api/v1/subjects/{subjectKeyOrID}/entitlements endpoint.
- If page is provided that takes precedence and the paginated response is returned.
+ * To fetch the entitlements of a specific subject please use the /api/v1/subjects/{subjectKeyOrID}/entitlements endpoint.
+ * If page is provided, it takes precedence and a paginated response is returned.

17988-17996: Avoid drift: constants defined but not used in schema defaults.

listEntitlementsV2QueryExcludeInactiveDefault and listEntitlementsV2QueryOffsetDefault aren’t applied. Either wire them into the schema or drop the constants to prevent divergence.

Option A — apply the defaults (see related pagination comment for page/pageSize handling):

   excludeInactive: zod.coerce
     .boolean()
-    .optional()
+    .default(listEntitlementsV2QueryExcludeInactiveDefault)
   offset: zod.coerce
     .number()
     .min(listEntitlementsV2QueryOffsetMin)
-    .optional()
+    .default(listEntitlementsV2QueryOffsetDefault)

Option B — remove unused constants:

-export const listEntitlementsV2QueryExcludeInactiveDefault = false
...
-export const listEntitlementsV2QueryOffsetDefault = 0

18043-18047: Validate sort pairing.

Consider requiring orderBy when order is provided (already included in the refine suggestion), or set a sane default for orderBy if order is present.


18065-18073: ULID: add description (and optional normalization).

The regex correctly matches ULIDs. Add a brief description to aid errors, and optionally normalize to uppercase to avoid case-related surprises.

 export const getEntitlementByIdV2Params = zod.object({
   entitlementId: zod.coerce
     .string()
-    .regex(getEntitlementByIdV2PathEntitlementIdRegExp),
+    .regex(getEntitlementByIdV2PathEntitlementIdRegExp)
+    .describe('ULID (26 chars, Crockford Base32)'),
 })

If desired:

// ... .string().transform(s => s.toUpperCase()).regex(ULID_REGEX)

17997-18059: Optionally export TS types for consumers.

Handy for client callers and keeps types aligned with the schema.

 })
 
+export type ListEntitlementsV2Query = zod.infer<typeof listEntitlementsV2QueryParams>
api/client/go/client.gen.go (3)

8071-8096: Clarify mutual exclusivity/precedence for pagination params.

Both page/pageSize and offset/limit are exposed. Define precedence or enforce mutual exclusivity in the server/spec to avoid ambiguous requests. Consider OpenAPI oneOf for the two pagination styles and 400 on mixed usage.

Would you like me to propose an OpenAPI snippet using oneOf for these param groups?


28988-29015: LGTM: typed response wrappers align with problem+json conventions.

Optional: if the API returns 429, you may want an explicit typed field for it; otherwise it falls into the default bucket.

Also applies to: 29017-29045


42492-42565: Parsing is fine; consider more robust Content-Type checks.

Using strings.Contains("json") works but is loose. Using mime.ParseMediaType and matching application/json or application/problem+json is safer and more explicit. Low priority given this is generated code.

Also applies to: 42567-42647

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd605a4 and f752488.

📒 Files selected for processing (16)
  • api/api.gen.go (6 hunks)
  • api/client/go/client.gen.go (9 hunks)
  • api/client/javascript/src/client/schemas.ts (2 hunks)
  • api/client/javascript/src/zod/index.ts (1 hunks)
  • api/openapi.cloud.yaml (1 hunks)
  • api/openapi.yaml (1 hunks)
  • api/spec/src/cloud/main.tsp (1 hunks)
  • api/spec/src/entitlements/v2/entitlements.tsp (1 hunks)
  • openmeter/entitlement/adapter/entitlement.go (1 hunks)
  • openmeter/entitlement/connector.go (1 hunks)
  • openmeter/entitlement/driver/v2/customer.go (6 hunks)
  • openmeter/entitlement/driver/v2/customer_metered.go (4 hunks)
  • openmeter/entitlement/driver/v2/entitlement.go (1 hunks)
  • openmeter/entitlement/driver/v2/handler.go (2 hunks)
  • openmeter/server/router/entitlement.go (1 hunks)
  • openmeter/server/router/router.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
openmeter/entitlement/driver/v2/entitlement.go (7)
api/api.gen.go (8)
  • ListEntitlementsParams (7978-8022)
  • EntitlementV2 (3216-3218)
  • ListEntitlementsV2Params (8689-8738)
  • EntitlementType (3212-3212)
  • SortOrderASC (758-758)
  • Feature (3273-3302)
  • Entitlement (2571-2573)
  • Customer (2188-2238)
openmeter/entitlement/connector.go (4)
  • ListEntitlementsParams (32-52)
  • ListEntitlementsOrderBy (12-12)
  • ListEntitlementsOrderByCreatedAt (15-15)
  • ListEntitlementsOrderByUpdatedAt (16-16)
pkg/framework/transport/httptransport/argshandler.go (2)
  • HandlerWithArgs (11-14)
  • NewHandlerWithArgs (18-29)
pkg/framework/commonhttp/errors.go (1)
  • NewHTTPError (35-41)
pkg/convert/ptr.go (1)
  • DerefHeaderPtr (59-64)
openmeter/entitlement/driver/v2/mapping.go (1)
  • ParserV2 (21-21)
pkg/framework/commonhttp/encoder.go (1)
  • JSONResponseEncoder (39-41)
openmeter/server/router/router.go (1)
openmeter/entitlement/driver/v2/handler.go (2)
  • EntitlementHandler (12-24)
  • NewEntitlementHandler (34-48)
api/client/go/client.gen.go (1)
api/api.gen.go (19)
  • Feature (3273-3302)
  • EntitlementType (3212-3212)
  • PaginationPage (7635-7635)
  • PaginationPageSize (7638-7638)
  • LimitOffsetOffset (7557-7557)
  • LimitOffsetLimit (7554-7554)
  • EntitlementOrderByOrderingOrder (7497-7497)
  • EntitlementOrderByOrderingOrderBy (7500-7500)
  • ListEntitlementsV2Params (8689-8738)
  • EntitlementV2PaginatedResponse (3221-3233)
  • BadRequestProblemResponse (1134-1134)
  • UnauthorizedProblemResponse (7222-7222)
  • ForbiddenProblemResponse (3512-3512)
  • PreconditionFailedProblemResponse (5975-5975)
  • InternalServerErrorProblemResponse (3621-3621)
  • ServiceUnavailableProblemResponse (6317-6317)
  • UnexpectedProblemResponse (7226-7226)
  • EntitlementV2 (3216-3218)
  • NotFoundProblemResponse (4964-4964)
openmeter/entitlement/adapter/entitlement.go (2)
openmeter/ent/db/entitlement/where.go (2)
  • HasCustomerWith (1401-1410)
  • IDIn (30-32)
openmeter/ent/db/customer/where.go (2)
  • KeyIn (1062-1064)
  • IDIn (31-33)
openmeter/entitlement/driver/v2/handler.go (2)
openmeter/entitlement/driver/v2/entitlement.go (2)
  • ListEntitlementsHandler (26-26)
  • GetEntitlementHandler (120-120)
openmeter/entitlement/connector.go (1)
  • Connector (54-79)
api/api.gen.go (1)
api/client/go/client.gen.go (9)
  • Feature (2987-3016)
  • EntitlementType (2926-2926)
  • PaginationPage (6993-6993)
  • PaginationPageSize (6996-6996)
  • LimitOffsetOffset (6915-6915)
  • LimitOffsetLimit (6912-6912)
  • EntitlementOrderByOrderingOrder (6855-6855)
  • EntitlementOrderByOrderingOrderBy (6858-6858)
  • ListEntitlementsV2Params (8047-8096)
openmeter/server/router/entitlement.go (3)
openmeter/server/router/router.go (1)
  • Router (218-244)
api/api.gen.go (1)
  • ListEntitlementsV2Params (8689-8738)
openmeter/entitlement/driver/v2/entitlement.go (1)
  • GetEntitlementHandlerParams (116-118)
🪛 Gitleaks (8.27.2)
api/client/go/client.gen.go

[high] 43758-43759: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Artifacts / Container image
  • GitHub Check: Code Generators
  • GitHub Check: Test
  • GitHub Check: Lint
  • GitHub Check: Build
  • GitHub Check: Analyze (go)
🔇 Additional comments (28)
openmeter/entitlement/connector.go (1)

36-37: LGTM: new customer filters added to ListEntitlementsParams

The additions are consistent with the V2 surface and downstream adapter changes.

api/spec/src/cloud/main.tsp (1)

216-218: Expose V2 root entitlements in Cloud surface — verified

api/api.gen.go exposes ListEntitlementsV2 and GetEntitlementByIdV2 and they forward to entitlementV2Handler.ListEntitlements() and entitlementV2Handler.GetEntitlement(), so the interface wiring matches the V2 handlers.

openmeter/server/router/router.go (2)

235-236: Update handler type to v2 EntitlementHandler — OK

Field type switch aligns with handler rename.


281-287: Constructor switch to NewEntitlementHandler — verified

Params preserved and balance connector included; V2 routes are bound to entitlementV2Handler (constructed in openmeter/server/router/router.go and used in openmeter/server/router/entitlement.go for ListEntitlementsV2, GetEntitlementByIdV2 and the customer-scoped V2 endpoints).

openmeter/entitlement/driver/v2/customer_metered.go (1)

43-106: Receiver rename only — no functional changes

Looks consistent with handler rename.

Also applies to: 126-209, 229-328, 348-409

openmeter/entitlement/driver/v2/customer.go (1)

30-91: Receiver rename only — OK

Interfaces and router wiring align with the new type.

Also applies to: 104-195, 212-259, 276-322, 340-405, 409-416

openmeter/entitlement/driver/v2/entitlement.go (1)

36-42: No action required — repo targets Go 1.24.1 so stdlib slices is supported.

go.mod declares "go 1.24.1", so using slices.Contains is safe; no change needed.

api/openapi.cloud.yaml (2)

11485-11492: ULID pattern looks OK; ensure consistency across clients and server.

Pattern permits lower/upper Crockford Base32 and enforces 26 chars starting with 0–7. Verify all generators/validators (Go/JS) use the same regex and error messaging.

Do you want me to add a shared #/components/parameters/EntitlementId so all paths reuse the same schema?


11429-11435: Response schemas reference EntitlementV2 types—confirm components exist and align.

Ensure #/components/schemas/EntitlementV2PaginatedResponse and EntitlementV2 are present in this cloud spec and match the server payload shape.

Also applies to: 11493-11499

api/client/javascript/src/client/schemas.ts (3)

2341-2347: LGTM on path and method mapping for listEntitlementsV2.

Path registration and method gating look correct. Operation correctly points to operations['listEntitlementsV2'].


26197-26201: Action: EntitlementV2 schemas present in OpenAPI; confirm server handlers use them

EntitlementV2 and EntitlementV2PaginatedResponse are defined and referenced in api/openapi.yaml and api/openapi.cloud.yaml; client types and zod validators in api/client/javascript/src/client/schemas.ts and api/client/javascript/src/zod/index.ts match the expected query/response shapes. Server handler implementations were not inspected — confirm the handlers are wired to return/use these response schemas.


2334-2355: Confirm consumers don't rely on path-level typing

No in-repo references to paths['/api/v2/entitlements'].parameters.query were found; the generated schema sets path-level query?: never (api/client/javascript/src/client/schemas.ts:2334–2355). Verify downstream/external consumers or utilities do not read path-level parameters instead of using the operation-level typing.

openmeter/server/router/entitlement.go (2)

146-154: LGTM: V2 root endpoints are correctly wired to entitlementV2Handler.
The delegation and param passing look consistent with the v2 driver/types.


146-154: Router signatures match generated ServerInterface — no action required.

Generated ServerInterface declares ListEntitlementsV2(w http.ResponseWriter, r *http.Request, params ListEntitlementsV2Params) and GetEntitlementByIdV2(w http.ResponseWriter, r *http.Request, entitlementId string), which match the router methods.

api/api.gen.go (6)

13512-13522: Unimplemented stubs are fine.

501 placeholder responses are appropriate for the generated Unimplemented handler.


18960-18983: Path binding for entitlementId looks correct.

chi URL param name matches the router path; error path uses InvalidParamFormatError as expected.


19709-19714: Routes wired correctly.

Static and parameterized GET routes for /api/v2/entitlements don’t conflict; baseURL prefixing follows existing pattern.


20606-20911: Swagger embed updated.

Assuming the spec changes are source of truth, this looks fine.


12612-12617: Approve — ServerInterface methods implemented and signatures match client.
Signatures align with the client and the concrete Router implements both methods (openmeter/server/router/entitlement.go:146–152).


18853-18958: Query binding LGTM — validations already implemented

OrderBy is validated in openmeter/entitlement/driver/v2/entitlement.go (and openmeter/entitlement/driver/entitlement.go); pagination normalization/support for page/pageSize vs limit/offset is implemented in openmeter/entitlement/driver/v2/customer.go and handled (Page.IsZero -> use limit/offset) in openmeter/entitlement/adapter/entitlement.go.

api/client/javascript/src/zod/index.ts (2)

17997-18059: ```shell
#!/bin/bash
set -euo pipefail

file=$(fd -a 'openapi*.ya?ml' | head -n1 || true)
if [ -z "$file" ]; then
echo "openapi file not found"
exit 0
fi

echo "OPENAPI FILE: $file"
echo "---- lines 11370-11396 ----"
sed -n '11370,11396p' "$file" || true

echo
echo "---- occurrences of 'customerIds' with context ----"
rg -n -C3 'customerIds' "$file" || true

echo
echo "---- search for entitlementId / ULID / pattern / format strings ----"
rg -n -C3 'entitlementId|ULID|ulid|pattern:|format:' "$file" || true

echo
echo "---- wider context for entitlementId / customerId occurrences ----"
rg -n -C20 'entitlementId|customerId' "$file" || true

echo
echo "---- search repo for regex/constant used in code (zod regex var) ----"
rg -n --hidden 'getEntitlementByIdV2PathEntitlementIdRegExp|EntitlementIdRegExp|entitlementIdRegExp|ulid' || true


---

`17997-18059`: **Prevent accidental precedence: page/pageSize defaults make paging always “present”.**

Defaults for page and pageSize will be injected even when callers use limit/offset, causing page-based pagination to take precedence and produce ambiguous server behavior. Remove .default() for page and pageSize, keep/explicitly set defaults for limit/offset, and enforce mutual exclusivity between {page,pageSize} and {limit,offset} (e.g., .superRefine(...) rejecting mixed use) or model pagination as a union. Also require orderBy when order is provided. File: api/client/javascript/src/zod/index.ts (17997–18059). Verify whether parsed results are serialized into query strings (search for parse/safeParse and URLSearchParams|qs.stringify|searchParams.set) before merging this change.

</blockquote></details>
<details>
<summary>api/client/go/client.gen.go (6)</summary><blockquote>

`11857-11862`: **LGTM: new ClientInterface methods added consistently.**

---

`14318-14340`: **LGTM: client methods build requests and apply editors correctly.**

---

`23523-23731`: **LGTM: ListEntitlementsV2 request builder encodes arrays with form+explode as expected.**

---

`23732-23764`: **LGTM: GetEntitlementByIdV2 request builder path-param handling looks correct.**

---

`24632-24636`: **LGTM: WithResponse interface mirrors the non-typed interface.**

---

`43678-43949`: **Add a gitleaks allowlist for the embedded swaggerSpec (decoded & scanned — no obvious secrets)**

I decoded api/client/go/client.gen.go's swaggerSpec and scanned for common secret/token patterns; none were found. Add a minimal gitleaks allowlist rule that targets the swaggerSpec block (match filename api/client/go/client.gen.go and the `var swaggerSpec = []string{...}` block) to suppress this recurring false positive.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines 11377 to 11385
description: |-
Filtering by multiple customers.

Usage: `?customer=customer-1&customer-3`
schema:
type: array
items:
type: string
style: form
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix parameter name and example for customerKeys; improve wording.

The example uses an undefined query key (“customer”) and is malformed.

Apply:

-          description: |-
-            Filtering by multiple customers.
-
-            Usage: `?customer=customer-1&customer-3`
+          description: |-
+            Filter by multiple customer keys.
+
+            Usage: `?customerKeys=customer-1&customerKeys=customer-3`
+          explode: true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: |-
Filtering by multiple customers.
Usage: `?customer=customer-1&customer-3`
schema:
type: array
items:
type: string
style: form
description: |-
Filter by multiple customer keys.
Usage: `?customerKeys=customer-1&customerKeys=customer-3`
explode: true
schema:
type: array
items:
type: string
style: form
🤖 Prompt for AI Agents
In api/openapi.yaml around lines 11377 to 11385, the query example and wording
refer to a non-existent key "customer" and the example is malformed; update the
description to reference the correct parameter name "customerKeys" and show the
proper multi-value form encoding (e.g.
?customerKeys=customer-1&customerKeys=customer-3), adjust the sentence to
"Filter by one or more customer keys." and ensure the schema and style remain as
type: array / items: type: string / style: form so the example matches the
parameter name and format.

Comment on lines 11389 to 11397
description: |-
Filtering by multiple customers.

Usage: `?customer=01K4WAQ0J99ZZ0MD75HXR112H8&customer=01K4WAQ0J99ZZ0MD75HXR112H9`
schema:
type: array
items:
type: string
style: form
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix parameter name and example for customerIds; consider referencing a ULID schema.

The example again uses “customer” instead of “customerIds”.

Apply:

-          description: |-
-            Filtering by multiple customers.
-
-            Usage: `?customer=01K4WAQ0J99ZZ0MD75HXR112H8&customer=01K4WAQ0J99ZZ0MD75HXR112H9`
+          description: |-
+            Filter by multiple customer IDs.
+
+            Usage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9`
           schema:
             type: array
             items:
-              type: string
+              type: string
+              # If available in components, prefer: $ref: '#/components/schemas/ULID'
           style: form
+          explode: true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: |-
Filtering by multiple customers.
Usage: `?customer=01K4WAQ0J99ZZ0MD75HXR112H8&customer=01K4WAQ0J99ZZ0MD75HXR112H9`
schema:
type: array
items:
type: string
style: form
description: |-
Filter by multiple customer IDs.
Usage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9`
schema:
type: array
items:
type: string
# If available in components, prefer: $ref: '#/components/schemas/ULID'
style: form
explode: true
🤖 Prompt for AI Agents
In api/openapi.yaml around lines 11389 to 11397, the parameter/example uses
"customer" instead of the intended "customerIds" and the item type should
reference the existing ULID schema; change the parameter name and example to use
customerIds (e.g.
?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9),
and update the items/schema to $ref: '#/components/schemas/ULID' (or the correct
ULID schema path) so the parameter is named customerIds and validates as ULIDs.

Comment on lines +70 to +76
@get
@operationId("getEntitlementByIdV2")
@summary("Get entitlement by id")
get(
@path
entitlementId: ULID,
): EntitlementV2 | OpenMeter.NotFoundError | OpenMeter.CommonErrors;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing @route for path parameter; GET by ID won’t bind entitlementId

Without a route segment, @path entitlementId won’t materialize in OpenAPI. Add @route("/{entitlementId}").

   @get
   @operationId("getEntitlementByIdV2")
   @summary("Get entitlement by id")
+  @route("/{entitlementId}")
   get(
     @path
     entitlementId: ULID,
   ): EntitlementV2 | OpenMeter.NotFoundError | OpenMeter.CommonErrors;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@get
@operationId("getEntitlementByIdV2")
@summary("Get entitlement by id")
get(
@path
entitlementId: ULID,
): EntitlementV2 | OpenMeter.NotFoundError | OpenMeter.CommonErrors;
@get
@operationId("getEntitlementByIdV2")
@summary("Get entitlement by id")
@route("/{entitlementId}")
get(
@path
entitlementId: ULID,
): EntitlementV2 | OpenMeter.NotFoundError | OpenMeter.CommonErrors;
🤖 Prompt for AI Agents
In api/spec/src/entitlements/v2/entitlements.tsp around lines 70 to 76, the GET
operation is missing a route segment so the @path entitlementId parameter will
not be bound; add a route decorator such as @route("/{entitlementId}") directly
above the get() declaration so the entitlementId path parameter is included in
the OpenAPI path and will bind correctly.

Comment on lines +421 to +431
if len(params.CustomerKeys) > 0 {
query = query.Where(db_entitlement.HasCustomerWith(
customerdb.KeyIn(params.CustomerKeys...),
))
}

if len(params.CustomerIDs) > 0 {
query = query.Where(db_entitlement.HasCustomerWith(
customerdb.IDIn(params.CustomerIDs...),
))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Combine customerId/customerKey filters with OR and exclude deleted customers

Current logic stacks two HasCustomerWith() clauses, effectively AND-ing key and ID. If both filters are provided, no rows match unless a single customer satisfies both, which is likely unintended. It also omits the customer deletion predicate used elsewhere, leading to inconsistent results.

Apply this refactor to:

  • OR keys and IDs
  • AND with not-deleted
  • Optionally scope customer namespace when provided (consistent with other filters)
-			if len(params.CustomerKeys) > 0 {
-				query = query.Where(db_entitlement.HasCustomerWith(
-					customerdb.KeyIn(params.CustomerKeys...),
-				))
-			}
-
-			if len(params.CustomerIDs) > 0 {
-				query = query.Where(db_entitlement.HasCustomerWith(
-					customerdb.IDIn(params.CustomerIDs...),
-				))
-			}
+			if len(params.CustomerKeys) > 0 || len(params.CustomerIDs) > 0 {
+				// OR across IDs and Keys, AND with not-deleted (and namespace when provided)
+				orPreds := []predicate.Customer{}
+				if len(params.CustomerKeys) > 0 {
+					orPreds = append(orPreds, customerdb.KeyIn(params.CustomerKeys...))
+				}
+				if len(params.CustomerIDs) > 0 {
+					orPreds = append(orPreds, customerdb.IDIn(params.CustomerIDs...))
+				}
+				andPreds := []predicate.Customer{
+					customerdb.Or(orPreds...),
+					customerNotDeletedAt(now),
+				}
+				if len(params.Namespaces) > 0 {
+					andPreds = append(andPreds, customerdb.NamespaceIn(params.Namespaces...))
+				}
+				query = query.Where(db_entitlement.HasCustomerWith(andPreds...))
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(params.CustomerKeys) > 0 {
query = query.Where(db_entitlement.HasCustomerWith(
customerdb.KeyIn(params.CustomerKeys...),
))
}
if len(params.CustomerIDs) > 0 {
query = query.Where(db_entitlement.HasCustomerWith(
customerdb.IDIn(params.CustomerIDs...),
))
}
if len(params.CustomerKeys) > 0 || len(params.CustomerIDs) > 0 {
// OR across IDs and Keys, AND with not-deleted (and namespace when provided)
orPreds := []predicate.Customer{}
if len(params.CustomerKeys) > 0 {
orPreds = append(orPreds, customerdb.KeyIn(params.CustomerKeys...))
}
if len(params.CustomerIDs) > 0 {
orPreds = append(orPreds, customerdb.IDIn(params.CustomerIDs...))
}
andPreds := []predicate.Customer{
customerdb.Or(orPreds...),
customerNotDeletedAt(now),
}
if len(params.Namespaces) > 0 {
andPreds = append(andPreds, customerdb.NamespaceIn(params.Namespaces...))
}
query = query.Where(db_entitlement.HasCustomerWith(andPreds...))
}
🤖 Prompt for AI Agents
In openmeter/entitlement/adapter/entitlement.go around lines 421 to 431, the
current code adds two separate HasCustomerWith() clauses which ANDs customer key
and ID filters and also omits the "not deleted" predicate and optional namespace
scoping; instead, build a single HasCustomerWith(...) call that ORs the
KeyIn(...) and IDIn(...) predicates when either is present, ANDs a predicate
that excludes deleted customers (use the same deleted check used elsewhere), and
when a customer namespace is supplied include that namespace predicate as well
so the combined customer filter reads: (KeyIn OR IDIn) AND NotDeleted AND
optional NamespaceEquals. Ensure you replace the two separate Where(...) calls
with this single consolidated predicate construction.

@GAlexIHU GAlexIHU requested a review from tothandras September 11, 2025 14:57
@GAlexIHU GAlexIHU enabled auto-merge (squash) September 11, 2025 14:58
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (9)
api/openapi.cloud.yaml (2)

11376-11387: Fix query example and label for customerKeys (wrong param name, malformed example).

Use the correct parameter name and repeat the key per RFC3986; also clarify the label as “customer keys.”

-          description: |-
-            Filtering by multiple customers.
+          description: |-
+            Filtering by multiple customer keys.
@@
-            Usage: `?customer=customer-1&customer-3`
+            Usage: `?customerKeys=customer-1&customerKeys=customer-3`

11388-11399: Fix query example and label for customerIds (wrong param name).

Use the correct parameter name and repeat the key.

-          description: |-
-            Filtering by multiple customers.
+          description: |-
+            Filtering by multiple customer IDs.
@@
-            Usage: `?customer=01K4WAQ0J99ZZ0MD75HXR112H8&customer=01K4WAQ0J99ZZ0MD75HXR112H9`
+            Usage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9`
api/api.gen.go (3)

8713-8731: Clarify and enforce pagination mode (page/pageSize vs offset/limit).

Define exclusivity or precedence in the OpenAPI (source of truth) and enforce in the handler; current middleware accepts both without guidance.

Verify enforcement exists server-side:

#!/bin/bash
rg -n -C3 -S 'ListEntitlementsV2\(' openmeter | rg -n -C3 -e '\bpage\b|\boffset\b|\blimit\b|\bpageSize\b'

20605-20615: Embedded swagger updated—ensure spec fixes propagate.

Once the examples/pagination rules are corrected in the spec, regenerate this blob and all clients to keep docs consistent.

Quick check after regeneration:

#!/bin/bash
rg -n -S 'Usage: `\?customer=' api/client api | sed -n '1,200p'

8695-8704: Fix incorrect query-param examples for customer filters.

Comments show ?customer= but params are customerKeys and customerIds. Update the spec sources and regenerate; don’t hand-edit generated files. For reference only, this is the intended wording.

Apply in spec (then regenerate):

-// Usage: `?customer=customer-1&customer-3`
+// Usage: `?customerKeys=customer-1&customerKeys=customer-3`

-// Usage: `?customer=01K4WAQ0J99ZZ0MD75HXR112H8&customer=01K4WAQ0J99ZZ0MD75HXR112H9`
+// Usage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9`

Run to locate lingering wrong examples:

#!/bin/bash
fd -a -t f 'openapi.*\.ya?ml$' | xargs -I{} rg -n -C2 -S 'Usage: `\?customer=' {}
rg -n -C2 -S 'Usage: `\?customer=' api
api/client/javascript/src/zod/index.ts (1)

18000-18008: Fix parameter names in examples.

The examples reference customer while the actual parameters are customerIds and customerKeys. Also, the second example is missing the parameter name before customer-3.

Apply this diff to correct the parameter names in the examples:

-      'Filtering by multiple customers.\n\nUsage: `?customer=01K4WAQ0J99ZZ0MD75HXR112H8&customer=01K4WAQ0J99ZZ0MD75HXR112H9`'
+      'Filtering by multiple customers.\n\nUsage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9`'
-      'Filtering by multiple customers.\n\nUsage: `?customer=customer-1&customer-3`'
+      'Filtering by multiple customers.\n\nUsage: `?customerKeys=customer-1&customerKeys=customer-3`'
api/client/go/client.gen.go (1)

8053-8061: Fix customer filter usage examples (wrong query keys) — same issue as earlier comment.

Examples use customer=... but params are customerKeys and customerIds. Also the second value in the first example is missing the key. Please fix in the OpenAPI descriptions so regen preserves it.

Apply this diff in the generated file (and mirror it in the OpenAPI spec descriptions):

-  // Usage: `?customer=customer-1&customer-3`
+  // Usage: `?customerKeys=customer-1&customerKeys=customer-3`
...
-  // Usage: `?customer=01K4WAQ0J99ZZ0MD75HXR112H8&customer=01K4WAQ0J99ZZ0MD75HXR112H9`
+  // Usage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9`

Run to find and fix all occurrences across the repo (source-of-truth should be the spec):

#!/bin/bash
rg -n -C2 -e 'Usage:\s*`\\\?customer=' -e 'Usage:\s*`\?customer='
rg -n -C2 'customer=customer-1&customer-3'
api/client/javascript/src/client/schemas.ts (2)

26066-26070: Fix customerKeys usage example (wrong param name and missing repeated key).

Replace ?customer=customer-1&customer-3 with the correct repeated customerKeys examples.

Apply this diff:

-        /** @description Filtering by multiple customers.
-         *
-         *     Usage: `?customer=customer-1&customer-3` */
+        /** @description Filter by multiple customer keys.
+         *
+         *     Usage: `?customerKeys=customer-1&customerKeys=customer-3` */

26070-26073: Fix customerIds usage example (wrong param name).

Use customerIds in the example and keep repeated form consistent with other params.

Apply this diff:

-        /** @description Filtering by multiple customers.
-         *
-         *     Usage: `?customer=01K4WAQ0J99ZZ0MD75HXR112H8&customer=01K4WAQ0J99ZZ0MD75HXR112H9` */
+        /** @description Filter by multiple customer IDs.
+         *
+         *     Usage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9` */
🧹 Nitpick comments (10)
api/openapi.cloud.yaml (2)

11412-11420: Simplify boolean query param and clarify meaning.

explode is unnecessary for a boolean; refine wording to define “inactive.”

-          description: Exclude inactive entitlements in the response (those scheduled for later or earlier)
+          description: Exclude entitlements not currently active (startAt in the future or already ended).
           schema:
             type: boolean
             default: false
-          explode: false
           style: form

11421-11426: Document/validate pagination strategy (page vs. offset).

Both page/pageSize and offset/limit are exposed. Define precedence or enforce mutual exclusivity (return 400 on mix) to avoid ambiguous pagination.

api/api.gen.go (1)

8710-8711: Wording nit: make “ExcludeInactive” clearer.

Suggest: “Exclude inactive entitlements (scheduled to start in the future or already ended).”

api/client/go/client.gen.go (3)

8068-8069: Nit: clarify wording for ExcludeInactive.

Current: “those scheduled for later or earlier.” Consider clearer phrasing.

-  // ExcludeInactive Exclude inactive entitlements in the response (those scheduled for later or earlier)
+  // ExcludeInactive Exclude inactive entitlements (scheduled to activate in the future or already expired).

8046-8096: Document pagination parameter expectations (avoid mixing page/pageSize with limit/offset).

To reduce 400s and ambiguity, note mutual exclusivity in the spec description so the client doc reflects it.

If intended, add this note to the OpenAPI parameter descriptions for page/pageSize and limit/offset.


23523-23731: Set explicit Accept header for JSON/problem+json.

Helps content negotiation and avoids proxies returning text/html.

@@
-  req, err := http.NewRequest("GET", queryURL.String(), nil)
+  req, err := http.NewRequest("GET", queryURL.String(), nil)
   if err != nil {
     return nil, err
   }
+  req.Header.Set("Accept", "application/json, application/problem+json")
@@
-  req, err := http.NewRequest("GET", queryURL.String(), nil)
+  req, err := http.NewRequest("GET", queryURL.String(), nil)
   if err != nil {
     return nil, err
   }
+  req.Header.Set("Accept", "application/json, application/problem+json")

Note: Prefer fixing this via the OpenAPI spec’s produces/content to keep generated code in sync.

Also applies to: 23732-23765

api/client/javascript/src/client/schemas.ts (4)

2342-2345: Clarify admin-only note and cross-reference correctness.

Doc mentions “administrative purposes only” and points to /api/v2/customers/{customerIdOrKey}/entitlements. Please confirm that endpoint exists in v2 and consider stating required role/scope explicitly to avoid ambiguity.


26078-26079: Tighten wording for excludeInactive (ambiguous phrasing).

“scheduled for later or earlier” is unclear; propose explicit wording and default.

Apply this diff:

-        /** @description Exclude inactive entitlements in the response (those scheduled for later or earlier) */
+        /** @description Exclude entitlements that are not currently active (future-scheduled or expired). Default: false. */

26080-26095: Document mutual exclusivity between page/pageSize and offset/limit.

Support for both pagination styles is great; please document they are mutually exclusive to avoid ambiguous requests.

Apply this diff:

-        /** @description Page index.
-         *
-         *     Default is 1. */
+        /** @description Page index (mutually exclusive with offset/limit).
+         *
+         *     Default is 1. */
         page?: components['parameters']['Pagination.page']
-        /** @description The maximum number of items per page.
-         *
-         *     Default is 100. */
+        /** @description The maximum number of items per page (mutually exclusive with offset/limit).
+         *
+         *     Default is 100. */
         pageSize?: components['parameters']['Pagination.pageSize']
-        /** @description Number of items to skip.
-         *
-         *     Default is 0. */
+        /** @description Number of items to skip (mutually exclusive with page/pageSize).
+         *
+         *     Default is 0. */
         offset?: components['parameters']['LimitOffset.offset']
-        /** @description Number of items to return.
-         *
-         *     Default is 100. */
+        /** @description Number of items to return (mutually exclusive with page/pageSize).
+         *
+         *     Default is 100. */
         limit?: components['parameters']['LimitOffset.limit']

Also applies to: 26088-26095


26185-26188: Add param doc for entitlementId (format/example).

If IDs are ULIDs, add a short doc and example to guide clients.

Apply this diff:

-      path: {
-        entitlementId: string
-      }
+      path: {
+        /** @description Entitlement ID (e.g., ULID). Example: `01K4WAQ0J99ZZ0MD75HXR112H8` */
+        entitlementId: string
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38a11b3 and 40a70e2.

📒 Files selected for processing (6)
  • api/api.gen.go (6 hunks)
  • api/client/go/client.gen.go (9 hunks)
  • api/client/javascript/src/client/schemas.ts (2 hunks)
  • api/client/javascript/src/zod/index.ts (1 hunks)
  • api/openapi.cloud.yaml (1 hunks)
  • api/openapi.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/openapi.yaml
🧰 Additional context used
🧬 Code graph analysis (2)
api/api.gen.go (1)
api/client/go/client.gen.go (9)
  • Feature (2987-3016)
  • EntitlementType (2926-2926)
  • PaginationPage (6993-6993)
  • PaginationPageSize (6996-6996)
  • LimitOffsetOffset (6915-6915)
  • LimitOffsetLimit (6912-6912)
  • EntitlementOrderByOrderingOrder (6855-6855)
  • EntitlementOrderByOrderingOrderBy (6858-6858)
  • ListEntitlementsV2Params (8047-8096)
api/client/go/client.gen.go (1)
api/api.gen.go (19)
  • Feature (3273-3302)
  • EntitlementType (3212-3212)
  • PaginationPage (7635-7635)
  • PaginationPageSize (7638-7638)
  • LimitOffsetOffset (7557-7557)
  • LimitOffsetLimit (7554-7554)
  • EntitlementOrderByOrderingOrder (7497-7497)
  • EntitlementOrderByOrderingOrderBy (7500-7500)
  • ListEntitlementsV2Params (8689-8738)
  • EntitlementV2PaginatedResponse (3221-3233)
  • BadRequestProblemResponse (1134-1134)
  • UnauthorizedProblemResponse (7222-7222)
  • ForbiddenProblemResponse (3512-3512)
  • PreconditionFailedProblemResponse (5975-5975)
  • InternalServerErrorProblemResponse (3621-3621)
  • ServiceUnavailableProblemResponse (6317-6317)
  • UnexpectedProblemResponse (7226-7226)
  • EntitlementV2 (3216-3218)
  • NotFoundProblemResponse (4964-4964)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Quickstart
  • GitHub Check: E2E
  • GitHub Check: Lint
  • GitHub Check: Test
  • GitHub Check: Code Generators
  • GitHub Check: Build
  • GitHub Check: Analyze (go)
🔇 Additional comments (20)
api/openapi.cloud.yaml (1)

11484-11491: ULID path param pattern looks correct.

Pattern and example align with ULID (Crockford base32, 26 chars, time prefix 0–7).

api/api.gen.go (5)

12612-12617: ServerInterface additions LGTM.

New methods and paths align with the described V2 surface.


13512-13522: Unimplemented stubs LGTM.

Consistent 501 behavior for unimplemented operations.


18853-18958: Query binding for ListEntitlementsV2 looks correct.

Multi-valued params use explode; error mapping is consistent.


18960-18983: Path binding for GetEntitlementByIdV2 LGTM.


19709-19714: Route wiring LGTM.

GET /api/v2/entitlements and GET /api/v2/entitlements/{entitlementId} registered as expected.

api/client/javascript/src/zod/index.ts (4)

17982-17986: LGTM!

The JSDoc comment clearly explains the purpose of the admin endpoint and provides helpful guidance to users about using the customer-specific endpoint for individual subjects.


17987-17994: LGTM!

The default values and limits are well-defined and follow consistent naming conventions. The pagination parameters provide both offset-based and page-based options with reasonable defaults.


17996-18058: LGTM!

The query parameters schema is comprehensive and well-structured. The validation rules are appropriate with sensible defaults and limits. The field descriptions provide clear usage examples for developers.


18064-18072: LGTM!

The entitlement ID validation uses a proper regex pattern that appears to validate ULID format (base32 encoding with specific character set), and the Zod schema correctly applies this validation.

api/client/go/client.gen.go (7)

11857-11861: Interfaces extension looks good.

Methods and signatures align with server shapes.


14318-14341: Client methods: LGTM.

Context propagation and editor application are consistent with existing patterns.


24632-24636: WithResponse interfaces: LGTM.

Typed wrappers are correctly added.


28988-29045: Response types: LGTM.

Status helpers and payload fields match the API.


30884-30900: WithResponse wrappers: LGTM.

Happy path and error propagation are consistent.


42492-42647: Parsers: LGTM.

Content-type handling covers json and problem+json; default branch maps to unexpected problem.


43681-43949: Embedded swagger blob.

Generated asset; no review feedback.

api/client/javascript/src/client/schemas.ts (3)

2334-2354: V2 route shape LGTM.

Path registered with GET-only and other methods explicitly disallowed looks correct.


2355-2361: Confirm op-level path param is sufficient.

Path-level parameters.path?: never with op-level path.entitlementId is fine, but verify your client/server codegen doesn’t rely on path-level param declarations for templating.


26192-26274: Response surface LGTM.

Comprehensive problem responses with 404 for not found are in place.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (5)
api/openapi.cloud.yaml (1)

11376-11394: Past issue resolved: query examples now match parameter names.

Examples for customerKeys and customerIds correctly use repeated keys and the right param names. Thanks for fixing.

api/spec/src/entitlements/v2/entitlements.tsp (3)

16-18: Use “customer” instead of “subject” in docs

Aligns with the rest of the API surface.

-   * To fetch the entitlements of a specific subject please use the /api/v2/customers/{customerIdOrKey}/entitlements endpoint.
+   * To fetch the entitlements of a specific customer please use the /api/v2/customers/{customerIdOrKey}/entitlements endpoint.

39-46: Type customerIds as ULID[] and fix doc to say “customer IDs”

Ensures schema/clients carry ULID intent and improves generated validators.

-    /**
-     * Filtering by multiple customers.
-     *
-     * Usage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9`
-     */
+    /**
+     * Filtering by multiple customer IDs.
+     *
+     * Usage: `?customerIds=01K4WAQ0J99ZZ0MD75HXR112H8&customerIds=01K4WAQ0J99ZZ0MD75HXR112H9`
+     */
     @query(#{ explode: true })
-    customerIds?: string[],
+    customerIds?: ULID[],

69-75: Add @route for path parameter; entitlementId won’t bind otherwise

Without a route segment, @path entitlementId won’t materialize in OpenAPI.

   @get
   @operationId("getEntitlementByIdV2")
   @summary("Get entitlement by id")
+  @route("/{entitlementId}")
   get(
     @path
     entitlementId: ULID,
   ): EntitlementV2 | OpenMeter.NotFoundError | OpenMeter.CommonErrors;
api/client/go/client.gen.go (1)

8053-8061: Customer filter example keys fixed — looks good.

Examples now use customerKeys and customerIds correctly, matching the actual query params.

🧹 Nitpick comments (16)
api/openapi.cloud.yaml (5)

11421-11426: Avoid ambiguity between page/pageSize and offset/limit.

Support for both pagination modes can confuse clients. Document precedence or reject mixed usage.

Apply this doc tweak to the operation description:

       description: |-
         List all entitlements for all the customers and features. This endpoint is intended for administrative purposes only.
         To fetch the entitlements of a specific subject please use the /api/v2/customers/{customerIdOrKey}/entitlements endpoint.
+        Note: Use either page/pageSize or offset/limit, not both. Requests providing both will be rejected with 400.

11413-11420: Clarify “inactive” wording.

Current phrasing is vague; specify future-start or past-end.

-          description: Exclude inactive entitlements in the response (those scheduled for later or earlier)
+          description: Exclude entitlements that are not active at the time of the request (scheduled to start in the future or already ended).

11428-11476: Add 429 Too Many Requests response.

Rate-limiting is common on list endpoints; include an explicit 429 mapping.

         '403':
           description: The server understood the request but refuses to authorize it.
           content:
             application/problem+json:
               schema:
                 $ref: '#/components/schemas/ForbiddenProblemResponse'
+        '429':
+          description: Too many requests.
+          content:
+            application/problem+json:
+              schema:
+                $ref: '#/components/schemas/TooManyRequestsProblemResponse'
         '412':

Please verify that TooManyRequestsProblemResponse exists in components or adjust to the correct schema name used in this spec.


11481-11482: Nit: capitalize ID in summary/description.

Consistency with other operations.

-      summary: Get entitlement by id
-      description: Get entitlement by id.
+      summary: Get entitlement by ID
+      description: Get entitlement by ID.

11505-11539: Also add 429 to the get-by-ID responses.

Single-resource GETs are often rate-limited too.

         '403':
           description: The server understood the request but refuses to authorize it.
           content:
             application/problem+json:
               schema:
                 $ref: '#/components/schemas/ForbiddenProblemResponse'
+        '429':
+          description: Too many requests.
+          content:
+            application/problem+json:
+              schema:
+                $ref: '#/components/schemas/TooManyRequestsProblemResponse'
         '404':

Please verify the referenced 429 problem schema name.

api/spec/src/entitlements/v2/entitlements.tsp (4)

31-38: Clarify that these are customer keys

The description currently says “customers” but the param is customerKeys.

-    /**
-     * Filtering by multiple customers.
-     *
-     * Usage: `?customerKeys=customer-1&customerKeys=customer-3`
-     */
+    /**
+     * Filtering by multiple customer keys.
+     *
+     * Usage: `?customerKeys=customer-1&customerKeys=customer-3`
+     */

47-54: Optional: pluralize entitlementType param for consistency with other array params

Keeps naming consistent with customerIds/customerKeys. If you keep the current name for backward compatibility, feel free to ignore.

-    @query(#{ explode: true })
-    entitlementType?: EntitlementType[],
+    @query(#{ explode: true })
+    entitlementTypes?: EntitlementType[],

56-60: Tighten wording of excludeInactive help text

Clarify what “later or earlier” means.

-    /**
-     * Exclude inactive entitlements in the response (those scheduled for later or earlier)
-     */
+    /**
+     * Exclude entitlements that are not currently active (scheduled to start in the future or already expired).
+     */

28-29: Optional: consider naming this featureKeys for clarity

Matches the “keys/ids” pattern used elsewhere. If existing clients already rely on feature, skip.

-    @query(#{ explode: true })
-    feature?: string[],
+    @query(#{ explode: true })
+    featureKeys?: string[],
api/client/javascript/src/client/schemas.ts (4)

26062-26065: Nit: clarify feature filter phrasing.

Prefer explicit “feature keys” to match terminology used elsewhere.

Apply this diff:

-        /** @description Filtering by multiple features.
+        /** @description Filter by multiple feature keys.

26066-26073: Docs: be explicit about keys vs IDs in filter descriptions.

The examples are correct; align the description lines to reduce ambiguity.

Apply this diff:

-        /** @description Filtering by multiple customers.
+        /** @description Filter by multiple customer keys.
@@
-        /** @description Filtering by multiple customers.
+        /** @description Filter by multiple customer IDs.

26078-26079: Clarify excludeInactive semantics.

Current text is vague (“scheduled for later or earlier”).

Apply this diff:

-        /** @description Exclude inactive entitlements in the response (those scheduled for later or earlier) */
+        /** @description Exclude entitlements not active at request time (now not within their active window, e.g., startAt..endAt). */

26080-26095: Document pagination parameter exclusivity (page/pageSize vs offset/limit).

Having both styles without guidance is confusing.

Apply this diff:

-        /** @description Page index.
-         *
-         *     Default is 1. */
+        /** @description Page index (1-based). Do not use together with offset/limit.
+         *
+         *     Default is 1. */
@@
-        /** @description The maximum number of items per page.
-         *
-         *     Default is 100. */
+        /** @description The maximum number of items per page. Do not use together with offset/limit.
+         *
+         *     Default is 100. */
@@
-        /** @description Number of items to skip.
-         *
-         *     Default is 0. */
+        /** @description Number of items to skip. Do not use together with page/pageSize.
+         *
+         *     Default is 0. */
@@
-        /** @description Number of items to return.
-         *
-         *     Default is 100. */
+        /** @description Number of items to return. Do not use together with page/pageSize.
+         *
+         *     Default is 100. */

Also confirm the zod validators enforce mutual exclusivity (or precedence) for these params.

api/client/go/client.gen.go (3)

8068-8069: Clarify docs for ExcludeInactive and ordering examples (update OpenAPI, then regen).

The ExcludeInactive description is ambiguous; add explicit “relative to now” wording and provide order/orderBy examples to reduce client misuse.

Apply in the OpenAPI descriptions so regeneration preserves it:

-  // ExcludeInactive Exclude inactive entitlements in the response (those scheduled for later or earlier)
+  // ExcludeInactive Exclude entitlements not active at request time
+  // (scheduled to start in the future or already expired).
-  // Order The order direction.
+  // Order The order direction.
+  // Usage: `?order=asc` or `?order=desc`
   Order *EntitlementOrderByOrderingOrder `form:"order,omitempty" json:"order,omitempty"`

-  // OrderBy The order by field.
+  // OrderBy The order by field.
+  // Example: `?orderBy=feature`
   OrderBy *EntitlementOrderByOrderingOrderBy `form:"orderBy,omitempty" json:"orderBy,omitempty"`

Also applies to: 8091-8095


23523-23731: Prevent mixing page/pageSize with offset/limit (optional guard or spec constraint).

Right now both pagination styles can be sent together. Consider a defensive guard here, or encode mutual exclusivity in the OpenAPI (oneOf) so generators enforce it.

Example guard (OK to keep in a thin wrapper instead of editing generated code):

 func NewListEntitlementsV2Request(server string, params *ListEntitlementsV2Params) (*http.Request, error) {
@@
-  if params != nil {
+  if params != nil {
+    // Defensive: don't allow mixing page/pageSize with offset/limit
+    if (params.Page != nil || params.PageSize != nil) && (params.Offset != nil || params.Limit != nil) {
+      return nil, fmt.Errorf("do not mix page/pageSize with offset/limit")
+    }
     queryValues := queryURL.Query()

28988-29000: Response wrapper for ListEntitlementsV2 is fine; consider small convenience helpers in non-generated code.

Optional: add helpers like Ok() to simplify client checks.

Example (place in a hand-written helper file):

func (r *ListEntitlementsV2Response) Ok() bool { return r.StatusCode() >= 200 && r.StatusCode() < 300 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40a70e2 and c72f7ea.

📒 Files selected for processing (7)
  • api/api.gen.go (6 hunks)
  • api/client/go/client.gen.go (9 hunks)
  • api/client/javascript/src/client/schemas.ts (2 hunks)
  • api/client/javascript/src/zod/index.ts (1 hunks)
  • api/openapi.cloud.yaml (1 hunks)
  • api/openapi.yaml (1 hunks)
  • api/spec/src/entitlements/v2/entitlements.tsp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • api/openapi.yaml
  • api/client/javascript/src/zod/index.ts
  • api/api.gen.go
🧰 Additional context used
🧬 Code graph analysis (1)
api/client/go/client.gen.go (1)
api/api.gen.go (19)
  • Feature (3273-3302)
  • EntitlementType (3212-3212)
  • PaginationPage (7635-7635)
  • PaginationPageSize (7638-7638)
  • LimitOffsetOffset (7557-7557)
  • LimitOffsetLimit (7554-7554)
  • EntitlementOrderByOrderingOrder (7497-7497)
  • EntitlementOrderByOrderingOrderBy (7500-7500)
  • ListEntitlementsV2Params (8689-8738)
  • EntitlementV2PaginatedResponse (3221-3233)
  • BadRequestProblemResponse (1134-1134)
  • UnauthorizedProblemResponse (7222-7222)
  • ForbiddenProblemResponse (3512-3512)
  • PreconditionFailedProblemResponse (5975-5975)
  • InternalServerErrorProblemResponse (3621-3621)
  • ServiceUnavailableProblemResponse (6317-6317)
  • UnexpectedProblemResponse (7226-7226)
  • EntitlementV2 (3216-3218)
  • NotFoundProblemResponse (4964-4964)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: E2E
  • GitHub Check: Quickstart
  • GitHub Check: Test
  • GitHub Check: Lint
  • GitHub Check: Analyze (go)
🔇 Additional comments (18)
api/openapi.cloud.yaml (4)

11356-11547: Overall: V2 endpoints look consistent and well-formed.

Good use of array query params with form style, proper ULID pattern on path, and standard problem responses.


11484-11491: ULID path param looks correct.

Pattern and example match a 26-char Crockford Base32 ULID (case-insensitive with leading [0–7]).


11499-11516: Confirm security is applied.

401/403 responses imply auth, but no per-op security block is shown. If these rely on global security, all good; otherwise add the appropriate security: stanza here.

Also applies to: 11441-11447


11452-11457: Validate 412 usage.

412 is listed, but the operations don’t advertise precondition headers. Confirm server supports ETag/If-* on these GETs; otherwise remove 412 from responses.

Also applies to: 11522-11527

api/spec/src/entitlements/v2/entitlements.tsp (3)

7-9: Imports look correct for decorators in use

TypeSpec.Http and TypeSpec.OpenAPI are appropriate here.


12-14: Surface and tagging LGTM

Route base and tag are consistent with v2 naming.


61-64: Confirm dual pagination styles are intentional and define precedence

Having both page/pageSize (QueryPagination) and limit/offset (QueryLimitOffset) can cause ambiguity if callers send both. Define precedence in docs or drop one.

api/client/javascript/src/client/schemas.ts (3)

2334-2354: LGTM: GET-only V2 entitlements paths are wired correctly.

GET allowed, others explicitly forbidden; path-level params set to never; op-level params defined below. Looks good.

Also applies to: 2355-2374


2342-2345: Verify admin-only enforcement for list endpoint.

Docs mark it “administrative purposes only.” Please confirm server enforces appropriate authz/roles and returns 403 for non-admins.


26106-26179: LGTM: response shapes and error coverage look complete.

200 payloads and problem+json mappings (400/401/403/404/412/500/503/default) align with expectations.

Also applies to: 26191-26273

api/client/go/client.gen.go (8)

11857-11861: New V2 client interface methods are consistent and minimal.

Signatures match the params/types added in the server and keep parity with existing patterns.


14318-14328: Client methods follow established pattern (builder → editors → do).

Correct use of WithContext and applyEditors; no issues.

Also applies to: 14330-14340


23732-23764: Path builder for GetEntitlementByIdV2 looks correct.

Uses simple style for path param and preserves server base path semantics.


29017-29029: GetEntitlementByIdV2Response maps 404 explicitly — good.

Covers the expected error set including NotFound.


30884-30900: WithResponse wrappers correctly delegate and parse.

Pattern matches existing endpoints; no issues.


43681-43949: Embedded swagger updated — aligns with new V2 endpoints.

No further action from client side.


23523-23731: Confirm entitlementType is an exploded enum array in the OpenAPI spec.

Search shows entitlementType defined as a query array (style: form) at api/openapi.yaml:4001 and :11397 (and the matching api/openapi.cloud.yaml locations) but no explicit explode: true or schema.items.enum were found. Add explode: true on the parameter and specify schema.items.enum with the allowed values (e.g., metered, boolean) so clients serialize as repeated params (?entitlementType=metered&entitlementType=boolean).


42492-42565: Add 429 Too Many Requests to OpenAPI responses for rate-limited entitlement endpoints

Add explicit 429 responses in api/openapi*.yaml for entitlement routes so the generated Go client maps them to ApplicationProblemJSON429 instead of the generic default.
Verify generated client mapping at api/client/go/client.gen.go (approx. lines 42492–42565 and 42567–42648).
Repo search failed ("server: No such file or directory"); OpenAPI files weren't found in the sandbox — confirm api/openapi*.yaml paths, add 429 responses, and regenerate the client for verification.

@GAlexIHU GAlexIHU merged commit 724de44 into main Sep 11, 2025
21 checks passed
@GAlexIHU GAlexIHU deleted the feat/entitlement-root-v2 branch September 11, 2025 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants