diff --git a/go/apps/api/openapi/gen.go b/go/apps/api/openapi/gen.go index 24f03a19be..be9624fe14 100644 --- a/go/apps/api/openapi/gen.go +++ b/go/apps/api/openapi/gen.go @@ -1819,11 +1819,8 @@ type V2RatelimitDeleteOverrideRequestBody struct { // After deletion, any identifiers previously affected by this override will immediately revert to using the default rate limit for the namespace. Identifier string `json:"identifier"` - // NamespaceId The unique ID of the rate limit namespace containing the override. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceId` is more precise and less prone to naming conflicts, making it ideal for automation and scripts. - NamespaceId *string `json:"namespaceId,omitempty"` - - // NamespaceName The name of the rate limit namespace containing the override. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceName` is more human-readable and convenient for manual operations and configurations. - NamespaceName *string `json:"namespaceName,omitempty"` + // Namespace The id or name of the namespace containing the override. + Namespace string `json:"namespace"` } // V2RatelimitDeleteOverrideResponseBody defines model for V2RatelimitDeleteOverrideResponseBody. @@ -1857,20 +1854,10 @@ type V2RatelimitGetOverrideRequestBody struct { // This field is used to look up the specific override configuration for this pattern. Identifier string `json:"identifier"` - // NamespaceId The unique ID of the rate limit namespace. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceId` is more precise and less prone to naming conflicts, making it ideal for scripts and automated operations. - NamespaceId *string `json:"namespaceId,omitempty"` - - // NamespaceName The name of the rate limit namespace. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceName` is more human-readable and easier to work with for manual operations and configurations. - NamespaceName *string `json:"namespaceName,omitempty"` - union json.RawMessage + // Namespace The id or name of the namespace containing the override. + Namespace string `json:"namespace"` } -// V2RatelimitGetOverrideRequestBody0 defines model for . -type V2RatelimitGetOverrideRequestBody0 = interface{} - -// V2RatelimitGetOverrideRequestBody1 defines model for . -type V2RatelimitGetOverrideRequestBody1 = interface{} - // V2RatelimitGetOverrideResponseBody defines model for V2RatelimitGetOverrideResponseBody. type V2RatelimitGetOverrideResponseBody struct { Data RatelimitOverride `json:"data"` @@ -1906,11 +1893,7 @@ type V2RatelimitLimitRequestBody struct { // Consider system capacity, business requirements, and fair usage policies in limit determination. Limit int64 `json:"limit"` - // Namespace Identifies the rate limit category using hierarchical naming for organization and monitoring. - // Namespaces must start with a letter and can contain letters, numbers, underscores, dots, slashes, or hyphens. - // Use descriptive, hierarchical names like 'auth.login', 'api.requests', or 'media.uploads' for clear categorization. - // Namespaces must be unique within your workspace and support segmentation of different API operations. - // Consistent naming conventions across your application improve monitoring and debugging capabilities. + // Namespace The id or name of the namespace. Namespace string `json:"namespace"` } @@ -1978,11 +1961,8 @@ type V2RatelimitListOverridesRequestBody struct { // Results exceeding this limit will be paginated, with a cursor provided for fetching subsequent pages. Limit *int `json:"limit,omitempty"` - // NamespaceId The unique ID of the rate limit namespace to list overrides for. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceId` guarantees you're targeting the exact namespace intended, even if names change over time. - NamespaceId *string `json:"namespaceId,omitempty"` - - // NamespaceName The name of the rate limit namespace to list overrides for. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceName` is more human-readable and convenient for manual operations and dashboards. - NamespaceName *string `json:"namespaceName,omitempty"` + // Namespace The id or name of the rate limit namespace to list overrides for. + Namespace string `json:"namespace"` } // V2RatelimitListOverridesResponseBody defines model for V2RatelimitListOverridesResponseBody. @@ -2042,11 +2022,8 @@ type V2RatelimitSetOverrideRequestBody struct { // This limit entirely replaces the default limit for matching identifiers. Limit int64 `json:"limit"` - // NamespaceId The unique ID of the rate limit namespace. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceId` guarantees you're targeting the exact namespace intended, even if names change, making it ideal for automation and scripts. - NamespaceId *string `json:"namespaceId,omitempty"` - - // NamespaceName The name of the rate limit namespace. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceName` is more human-readable and convenient for manual operations and configurations. - NamespaceName *string `json:"namespaceName,omitempty"` + // Namespace The ID or name of the rate limit namespace. + Namespace string `json:"namespace"` } // V2RatelimitSetOverrideResponseBody defines model for V2RatelimitSetOverrideResponseBody. @@ -2349,125 +2326,3 @@ func (t *V2KeysGetKeyRequestBody) UnmarshalJSON(b []byte) error { return err } - -// AsV2RatelimitGetOverrideRequestBody0 returns the union data inside the V2RatelimitGetOverrideRequestBody as a V2RatelimitGetOverrideRequestBody0 -func (t V2RatelimitGetOverrideRequestBody) AsV2RatelimitGetOverrideRequestBody0() (V2RatelimitGetOverrideRequestBody0, error) { - var body V2RatelimitGetOverrideRequestBody0 - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromV2RatelimitGetOverrideRequestBody0 overwrites any union data inside the V2RatelimitGetOverrideRequestBody as the provided V2RatelimitGetOverrideRequestBody0 -func (t *V2RatelimitGetOverrideRequestBody) FromV2RatelimitGetOverrideRequestBody0(v V2RatelimitGetOverrideRequestBody0) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeV2RatelimitGetOverrideRequestBody0 performs a merge with any union data inside the V2RatelimitGetOverrideRequestBody, using the provided V2RatelimitGetOverrideRequestBody0 -func (t *V2RatelimitGetOverrideRequestBody) MergeV2RatelimitGetOverrideRequestBody0(v V2RatelimitGetOverrideRequestBody0) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsV2RatelimitGetOverrideRequestBody1 returns the union data inside the V2RatelimitGetOverrideRequestBody as a V2RatelimitGetOverrideRequestBody1 -func (t V2RatelimitGetOverrideRequestBody) AsV2RatelimitGetOverrideRequestBody1() (V2RatelimitGetOverrideRequestBody1, error) { - var body V2RatelimitGetOverrideRequestBody1 - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromV2RatelimitGetOverrideRequestBody1 overwrites any union data inside the V2RatelimitGetOverrideRequestBody as the provided V2RatelimitGetOverrideRequestBody1 -func (t *V2RatelimitGetOverrideRequestBody) FromV2RatelimitGetOverrideRequestBody1(v V2RatelimitGetOverrideRequestBody1) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeV2RatelimitGetOverrideRequestBody1 performs a merge with any union data inside the V2RatelimitGetOverrideRequestBody, using the provided V2RatelimitGetOverrideRequestBody1 -func (t *V2RatelimitGetOverrideRequestBody) MergeV2RatelimitGetOverrideRequestBody1(v V2RatelimitGetOverrideRequestBody1) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -func (t V2RatelimitGetOverrideRequestBody) MarshalJSON() ([]byte, error) { - b, err := t.union.MarshalJSON() - if err != nil { - return nil, err - } - object := make(map[string]json.RawMessage) - if t.union != nil { - err = json.Unmarshal(b, &object) - if err != nil { - return nil, err - } - } - - object["identifier"], err = json.Marshal(t.Identifier) - if err != nil { - return nil, fmt.Errorf("error marshaling 'identifier': %w", err) - } - - if t.NamespaceId != nil { - object["namespaceId"], err = json.Marshal(t.NamespaceId) - if err != nil { - return nil, fmt.Errorf("error marshaling 'namespaceId': %w", err) - } - } - - if t.NamespaceName != nil { - object["namespaceName"], err = json.Marshal(t.NamespaceName) - if err != nil { - return nil, fmt.Errorf("error marshaling 'namespaceName': %w", err) - } - } - b, err = json.Marshal(object) - return b, err -} - -func (t *V2RatelimitGetOverrideRequestBody) UnmarshalJSON(b []byte) error { - err := t.union.UnmarshalJSON(b) - if err != nil { - return err - } - object := make(map[string]json.RawMessage) - err = json.Unmarshal(b, &object) - if err != nil { - return err - } - - if raw, found := object["identifier"]; found { - err = json.Unmarshal(raw, &t.Identifier) - if err != nil { - return fmt.Errorf("error reading 'identifier': %w", err) - } - } - - if raw, found := object["namespaceId"]; found { - err = json.Unmarshal(raw, &t.NamespaceId) - if err != nil { - return fmt.Errorf("error reading 'namespaceId': %w", err) - } - } - - if raw, found := object["namespaceName"]; found { - err = json.Unmarshal(raw, &t.NamespaceName) - if err != nil { - return fmt.Errorf("error reading 'namespaceName': %w", err) - } - } - - return err -} diff --git a/go/apps/api/openapi/openapi-generated.yaml b/go/apps/api/openapi/openapi-generated.yaml index 9124f055a7..c87f6909b7 100644 --- a/go/apps/api/openapi/openapi-generated.yaml +++ b/go/apps/api/openapi/openapi-generated.yaml @@ -1,5 +1,5 @@ # Code generated by generate_bundle.go; DO NOT EDIT. -# Generated at: 2025-07-28T10:13:32Z +# Generated at: 2025-07-28T12:12:38Z # Source: openapi-split.yaml components: @@ -37,7 +37,7 @@ components: type: string description: Processing status example: "OK" - badRequestErrorResponse: + BadRequestErrorResponse: type: object required: - meta @@ -48,7 +48,7 @@ components: error: $ref: "#/components/schemas/BadRequestErrorDetails" description: Error response for invalid requests that cannot be processed due to client-side errors. This typically occurs when request parameters are missing, malformed, or fail validation rules. The response includes detailed information about the specific errors in the request, including the location of each error and suggestions for fixing it. When receiving this error, check the 'errors' array in the response for specific validation issues that need to be addressed before retrying. - internalServerErrorResponse: + InternalServerErrorResponse: type: object required: - meta @@ -121,17 +121,6 @@ components: data: $ref: "#/components/schemas/V2ApisCreateApiResponseData" additionalProperties: false - BadRequestErrorResponse: - type: object - required: - - meta - - error - properties: - meta: - $ref: "#/components/schemas/Meta" - error: - $ref: "#/components/schemas/BadRequestErrorDetails" - description: Error response for invalid requests that cannot be processed due to client-side errors. This typically occurs when request parameters are missing, malformed, or fail validation rules. The response includes detailed information about the specific errors in the request, including the location of each error and suggestions for fixing it. When receiving this error, check the 'errors' array in the response for specific validation issues that need to be addressed before retrying. UnauthorizedErrorResponse: type: object required: @@ -166,23 +155,6 @@ components: - Access to the requested resource is restricted based on workspace settings To resolve this error, ensure your root key has the necessary permissions or contact your workspace administrator. - InternalServerErrorResponse: - type: object - required: - - meta - - error - properties: - meta: - $ref: "#/components/schemas/Meta" - error: - $ref: "#/components/schemas/BaseError" - description: |- - Error response when an unexpected error occurs on the server. This indicates a problem with Unkey's systems rather than your request. - - When you encounter this error: - - The request ID in the response can help Unkey support investigate the issue - - The error is likely temporary and retrying may succeed - - If the error persists, contact Unkey support with the request ID V2ApisDeleteApiRequestBody: type: object required: @@ -1778,13 +1750,8 @@ components: Once deleted, the override cannot be recovered, and the operation takes effect immediately. additionalProperties: false properties: - namespaceId: - description: The unique ID of the rate limit namespace containing the override. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceId` is more precise and less prone to naming conflicts, making it ideal for automation and scripts. - type: string - minLength: 1 - maxLength: 255 - namespaceName: - description: The name of the rate limit namespace containing the override. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceName` is more human-readable and convenient for manual operations and configurations. + namespace: + description: The id or name of the namespace containing the override. type: string minLength: 1 maxLength: 255 @@ -1802,6 +1769,7 @@ components: minLength: 1 maxLength: 255 required: + - namespace - identifier type: object V2RatelimitDeleteOverrideResponseBody: @@ -1826,13 +1794,8 @@ components: - Retrieving override settings for modification additionalProperties: false properties: - namespaceId: - description: The unique ID of the rate limit namespace. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceId` is more precise and less prone to naming conflicts, making it ideal for scripts and automated operations. - type: string - minLength: 1 - maxLength: 255 - namespaceName: - description: The name of the rate limit namespace. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceName` is more human-readable and easier to work with for manual operations and configurations. + namespace: + description: The id or name of the namespace containing the override. type: string minLength: 1 maxLength: 255 @@ -1850,15 +1813,9 @@ components: minLength: 1 maxLength: 255 required: + - namespace - identifier type: object - oneOf: - - required: - - namespaceName - - identifier - - required: - - namespaceId - - identifier V2RatelimitGetOverrideResponseBody: type: object required: @@ -1877,12 +1834,7 @@ components: minLength: 1 maxLength: 255 pattern: "^[a-zA-Z][a-zA-Z0-9_./-]*$" - description: | - Identifies the rate limit category using hierarchical naming for organization and monitoring. - Namespaces must start with a letter and can contain letters, numbers, underscores, dots, slashes, or hyphens. - Use descriptive, hierarchical names like 'auth.login', 'api.requests', or 'media.uploads' for clear categorization. - Namespaces must be unique within your workspace and support segmentation of different API operations. - Consistent naming conventions across your application improve monitoring and debugging capabilities. + description: The id or name of the namespace. example: sms.sign_up cost: type: integer @@ -1949,14 +1901,11 @@ components: V2RatelimitListOverridesRequestBody: additionalProperties: false properties: - namespaceId: - description: The unique ID of the rate limit namespace to list overrides for. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceId` guarantees you're targeting the exact namespace intended, even if names change over time. + namespace: + description: The id or name of the rate limit namespace to list overrides for. type: string minLength: 1 maxLength: 255 - namespaceName: - description: The name of the rate limit namespace to list overrides for. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceName` is more human-readable and convenient for manual operations and dashboards. - type: string cursor: description: Pagination cursor from a previous response. Include this when fetching subsequent pages of results. Each response containing more results than the requested limit will include a cursor value in the pagination object that can be used here. type: string @@ -1973,6 +1922,8 @@ components: default: 10 minimum: 1 maximum: 100 + required: + - namespace type: object V2RatelimitListOverridesResponseBody: type: object @@ -1998,14 +1949,11 @@ components: - Prioritizing important clients with higher limits additionalProperties: false properties: - namespaceId: - description: The unique ID of the rate limit namespace. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceId` guarantees you're targeting the exact namespace intended, even if names change, making it ideal for automation and scripts. + namespace: + description: The ID or name of the rate limit namespace. type: string minLength: 1 maxLength: 255 - namespaceName: - description: The name of the rate limit namespace. Either `namespaceId` or `namespaceName` must be provided, but not both. Using `namespaceName` is more human-readable and convenient for manual operations and configurations. - type: string duration: description: |- The duration in milliseconds for the rate limit window. This defines how long the rate limit counter accumulates before resetting to zero. @@ -2050,6 +1998,7 @@ components: type: integer minimum: 0 required: + - namespace - identifier - limit - duration @@ -3364,13 +3313,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/badRequestErrorResponse' + $ref: '#/components/schemas/BadRequestErrorResponse' description: Invalid request body or malformed events "529": content: application/json: schema: - $ref: '#/components/schemas/internalServerErrorResponse' + $ref: '#/components/schemas/InternalServerErrorResponse' description: Service overloaded, unable to process events security: [] summary: Internal ClickHouse proxy for API request metrics @@ -3402,13 +3351,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/badRequestErrorResponse' + $ref: '#/components/schemas/BadRequestErrorResponse' description: Invalid request body or malformed events "529": content: application/json: schema: - $ref: '#/components/schemas/internalServerErrorResponse' + $ref: '#/components/schemas/InternalServerErrorResponse' description: Service overloaded, unable to process events security: [] summary: Internal ClickHouse proxy for ratelimit events @@ -3440,13 +3389,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/badRequestErrorResponse' + $ref: '#/components/schemas/BadRequestErrorResponse' description: Invalid request body or malformed events "529": content: application/json: schema: - $ref: '#/components/schemas/internalServerErrorResponse' + $ref: '#/components/schemas/InternalServerErrorResponse' description: Service overloaded, unable to process events security: [] summary: Internal ClickHouse proxy for verification events diff --git a/go/apps/api/openapi/spec/paths/chproxy/metrics/index.yaml b/go/apps/api/openapi/spec/paths/chproxy/metrics/index.yaml index a2dfa57eb4..489ad29538 100644 --- a/go/apps/api/openapi/spec/paths/chproxy/metrics/index.yaml +++ b/go/apps/api/openapi/spec/paths/chproxy/metrics/index.yaml @@ -27,11 +27,11 @@ post: content: application/json: schema: - $ref: "../../../error/badRequestErrorResponse.yaml" + $ref: "../../../error/BadRequestErrorResponse.yaml" description: Invalid request body or malformed events "529": content: application/json: schema: - $ref: "../../../error/internalServerErrorResponse.yaml" - description: Service overloaded, unable to process events \ No newline at end of file + $ref: "../../../error/InternalServerErrorResponse.yaml" + description: Service overloaded, unable to process events diff --git a/go/apps/api/openapi/spec/paths/chproxy/ratelimits/index.yaml b/go/apps/api/openapi/spec/paths/chproxy/ratelimits/index.yaml index 28f40b8e20..b4a3599a27 100644 --- a/go/apps/api/openapi/spec/paths/chproxy/ratelimits/index.yaml +++ b/go/apps/api/openapi/spec/paths/chproxy/ratelimits/index.yaml @@ -27,11 +27,11 @@ post: content: application/json: schema: - $ref: "../../../error/badRequestErrorResponse.yaml" + $ref: "../../../error/BadRequestErrorResponse.yaml" description: Invalid request body or malformed events "529": content: application/json: schema: - $ref: "../../../error/internalServerErrorResponse.yaml" - description: Service overloaded, unable to process events \ No newline at end of file + $ref: "../../../error/InternalServerErrorResponse.yaml" + description: Service overloaded, unable to process events diff --git a/go/apps/api/openapi/spec/paths/chproxy/verifications/index.yaml b/go/apps/api/openapi/spec/paths/chproxy/verifications/index.yaml index 0bf5b66718..f118826d98 100644 --- a/go/apps/api/openapi/spec/paths/chproxy/verifications/index.yaml +++ b/go/apps/api/openapi/spec/paths/chproxy/verifications/index.yaml @@ -27,11 +27,11 @@ post: content: application/json: schema: - $ref: "../../../error/badRequestErrorResponse.yaml" + $ref: "../../../error/BadRequestErrorResponse.yaml" description: Invalid request body or malformed events "529": content: application/json: schema: - $ref: "../../../error/internalServerErrorResponse.yaml" - description: Service overloaded, unable to process events \ No newline at end of file + $ref: "../../../error/InternalServerErrorResponse.yaml" + description: Service overloaded, unable to process events diff --git a/go/apps/api/openapi/spec/paths/v2/ratelimit/deleteOverride/V2RatelimitDeleteOverrideRequestBody.yaml b/go/apps/api/openapi/spec/paths/v2/ratelimit/deleteOverride/V2RatelimitDeleteOverrideRequestBody.yaml index 82773f97b8..ef3e56e720 100644 --- a/go/apps/api/openapi/spec/paths/v2/ratelimit/deleteOverride/V2RatelimitDeleteOverrideRequestBody.yaml +++ b/go/apps/api/openapi/spec/paths/v2/ratelimit/deleteOverride/V2RatelimitDeleteOverrideRequestBody.yaml @@ -11,20 +11,8 @@ description: |- Once deleted, the override cannot be recovered, and the operation takes effect immediately. additionalProperties: false properties: - namespaceId: - description: - The unique ID of the rate limit namespace containing the override. - Either `namespaceId` or `namespaceName` must be provided, but not both. Using - `namespaceId` is more precise and less prone to naming conflicts, making - it ideal for automation and scripts. - type: string - minLength: 1 - maxLength: 255 - namespaceName: - description: The name of the rate limit namespace containing the override. - Either `namespaceId` or `namespaceName` must be provided, but not both. Using - `namespaceName` is more human-readable and convenient for manual operations - and configurations. + namespace: + description: The id or name of the namespace containing the override. type: string minLength: 1 maxLength: 255 @@ -42,5 +30,6 @@ properties: minLength: 1 maxLength: 255 required: + - namespace - identifier type: object diff --git a/go/apps/api/openapi/spec/paths/v2/ratelimit/getOverride/V2RatelimitGetOverrideRequestBody.yaml b/go/apps/api/openapi/spec/paths/v2/ratelimit/getOverride/V2RatelimitGetOverrideRequestBody.yaml index 1d2a36d3a6..174cff4c83 100644 --- a/go/apps/api/openapi/spec/paths/v2/ratelimit/getOverride/V2RatelimitGetOverrideRequestBody.yaml +++ b/go/apps/api/openapi/spec/paths/v2/ratelimit/getOverride/V2RatelimitGetOverrideRequestBody.yaml @@ -9,18 +9,8 @@ description: |- - Retrieving override settings for modification additionalProperties: false properties: - namespaceId: - description: The unique ID of the rate limit namespace. Either `namespaceId` - or `namespaceName` must be provided, but not both. Using `namespaceId` is - more precise and less prone to naming conflicts, making it ideal for scripts - and automated operations. - type: string - minLength: 1 - maxLength: 255 - namespaceName: - description: The name of the rate limit namespace. Either `namespaceId` or - `namespaceName` must be provided, but not both. Using `namespaceName` is more - human-readable and easier to work with for manual operations and configurations. + namespace: + description: The id or name of the namespace containing the override. type: string minLength: 1 maxLength: 255 @@ -38,12 +28,6 @@ properties: minLength: 1 maxLength: 255 required: + - namespace - identifier type: object -oneOf: - - required: - - namespaceName - - identifier - - required: - - namespaceId - - identifier diff --git a/go/apps/api/openapi/spec/paths/v2/ratelimit/limit/V2RatelimitLimitRequestBody.yaml b/go/apps/api/openapi/spec/paths/v2/ratelimit/limit/V2RatelimitLimitRequestBody.yaml index 26e2c758d9..3243008f73 100644 --- a/go/apps/api/openapi/spec/paths/v2/ratelimit/limit/V2RatelimitLimitRequestBody.yaml +++ b/go/apps/api/openapi/spec/paths/v2/ratelimit/limit/V2RatelimitLimitRequestBody.yaml @@ -5,12 +5,7 @@ properties: minLength: 1 maxLength: 255 # Reasonable upper bound for namespace identifiers pattern: "^[a-zA-Z][a-zA-Z0-9_./-]*$" - description: | - Identifies the rate limit category using hierarchical naming for organization and monitoring. - Namespaces must start with a letter and can contain letters, numbers, underscores, dots, slashes, or hyphens. - Use descriptive, hierarchical names like 'auth.login', 'api.requests', or 'media.uploads' for clear categorization. - Namespaces must be unique within your workspace and support segmentation of different API operations. - Consistent naming conventions across your application improve monitoring and debugging capabilities. + description: The id or name of the namespace. example: sms.sign_up cost: type: integer diff --git a/go/apps/api/openapi/spec/paths/v2/ratelimit/listOverrides/V2RatelimitListOverridesRequestBody.yaml b/go/apps/api/openapi/spec/paths/v2/ratelimit/listOverrides/V2RatelimitListOverridesRequestBody.yaml index b4a50441d7..353e745e9b 100644 --- a/go/apps/api/openapi/spec/paths/v2/ratelimit/listOverrides/V2RatelimitListOverridesRequestBody.yaml +++ b/go/apps/api/openapi/spec/paths/v2/ratelimit/listOverrides/V2RatelimitListOverridesRequestBody.yaml @@ -1,19 +1,11 @@ additionalProperties: false properties: - namespaceId: - description: The unique ID of the rate limit namespace to list overrides - for. Either `namespaceId` or `namespaceName` must be provided, but not both. - Using `namespaceId` guarantees you're targeting the exact namespace intended, - even if names change over time. + namespace: + description: The id or name of the rate limit namespace to list overrides + for. type: string minLength: 1 maxLength: 255 - namespaceName: - description: The name of the rate limit namespace to list overrides for. - Either `namespaceId` or `namespaceName` must be provided, but not both. Using - `namespaceName` is more human-readable and convenient for manual operations - and dashboards. - type: string cursor: description: Pagination cursor from a previous response. Include this when fetching subsequent pages of results. Each response containing more results @@ -33,4 +25,6 @@ properties: default: 10 minimum: 1 maximum: 100 +required: + - namespace type: object diff --git a/go/apps/api/openapi/spec/paths/v2/ratelimit/setOverride/V2RatelimitSetOverrideRequestBody.yaml b/go/apps/api/openapi/spec/paths/v2/ratelimit/setOverride/V2RatelimitSetOverrideRequestBody.yaml index 3c1ada9a84..10a5bf17ee 100644 --- a/go/apps/api/openapi/spec/paths/v2/ratelimit/setOverride/V2RatelimitSetOverrideRequestBody.yaml +++ b/go/apps/api/openapi/spec/paths/v2/ratelimit/setOverride/V2RatelimitSetOverrideRequestBody.yaml @@ -9,19 +9,11 @@ description: |- - Prioritizing important clients with higher limits additionalProperties: false properties: - namespaceId: - description: The unique ID of the rate limit namespace. Either `namespaceId` - or `namespaceName` must be provided, but not both. Using `namespaceId` guarantees - you're targeting the exact namespace intended, even if names change, making - it ideal for automation and scripts. + namespace: + description: The ID or name of the rate limit namespace. type: string minLength: 1 maxLength: 255 - namespaceName: - description: The name of the rate limit namespace. Either `namespaceId` or - `namespaceName` must be provided, but not both. Using `namespaceName` is more - human-readable and convenient for manual operations and configurations. - type: string duration: description: |- The duration in milliseconds for the rate limit window. This defines how long the rate limit counter accumulates before resetting to zero. @@ -66,6 +58,7 @@ properties: type: integer minimum: 0 required: + - namespace - identifier - limit - duration diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/200_test.go b/go/apps/api/routes/v2_ratelimit_delete_override/200_test.go index a6358e9806..0899bcbed1 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/200_test.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/200_test.go @@ -63,8 +63,8 @@ func TestDeleteOverrideSuccessfully(t *testing.T) { // Test deleting by namespace name t.Run("delete by namespace name", func(t *testing.T) { req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: identifier, + Namespace: namespaceName, + Identifier: identifier, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -79,4 +79,38 @@ func TestDeleteOverrideSuccessfully(t *testing.T) { require.NoError(t, err) require.True(t, override.DeletedAtM.Valid, "Override should be marked as deleted") }) + + // Test deleting by namespace ID + t.Run("delete by namespace ID", func(t *testing.T) { + // Create another override to test ID-based deletion + identifier2 := "test_identifier_2" + overrideID2 := uid.New(uid.RatelimitOverridePrefix) + err = db.Query.InsertRatelimitOverride(ctx, h.DB.RW(), db.InsertRatelimitOverrideParams{ + ID: overrideID2, + WorkspaceID: h.Resources().UserWorkspace.ID, + NamespaceID: namespaceID, + Identifier: identifier2, + Limit: 10, + Duration: 1000, + CreatedAt: time.Now().UnixMilli(), + }) + require.NoError(t, err) + + req := handler.Request{ + Namespace: namespaceID, + Identifier: identifier2, + } + + res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) + require.Equal(t, 200, res.Status, "expected 200, received: %s", res.RawBody) + + // Verify the override was deleted (check soft delete) + override, err := db.Query.FindRatelimitOverrideByID(ctx, h.DB.RO(), db.FindRatelimitOverrideByIDParams{ + WorkspaceID: h.Resources().UserWorkspace.ID, + OverrideID: overrideID2, + }) + + require.NoError(t, err) + require.True(t, override.DeletedAtM.Valid, "Override should be marked as deleted") + }) } diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/400_test.go b/go/apps/api/routes/v2_ratelimit_delete_override/400_test.go index f36a94fbdc..fe1d02ae9e 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/400_test.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/400_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/unkeyed/unkey/go/apps/api/openapi" handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_ratelimit_delete_override" - "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/testutil" "github.com/unkeyed/unkey/go/pkg/uid" ) @@ -49,7 +48,7 @@ func TestBadRequests(t *testing.T) { t.Run("missing identifier", func(t *testing.T) { req := openapi.V2RatelimitDeleteOverrideRequestBody{ - NamespaceId: ptr.P("test_namespace_id"), + Namespace: "test_namespace_id", } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -67,8 +66,8 @@ func TestBadRequests(t *testing.T) { t.Run("empty identifier", func(t *testing.T) { req := openapi.V2RatelimitDeleteOverrideRequestBody{ - NamespaceId: ptr.P("test_namespace_id"), - Identifier: "", + Namespace: "test_namespace_id", + Identifier: "", } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -84,11 +83,10 @@ func TestBadRequests(t *testing.T) { require.Greater(t, len(res.Body.Error.Errors), 0) }) - t.Run("neither namespace ID nor name provided", func(t *testing.T) { + t.Run("namespace not provided", func(t *testing.T) { req := openapi.V2RatelimitDeleteOverrideRequestBody{ - NamespaceId: nil, - NamespaceName: nil, - Identifier: "user_123", + Namespace: "", + Identifier: "user_123", } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -97,11 +95,11 @@ func TestBadRequests(t *testing.T) { require.NotNil(t, res.Body) require.Equal(t, "https://unkey.com/docs/errors/unkey/application/invalid_input", res.Body.Error.Type) - require.Equal(t, "You must provide either a namespace ID or name.", res.Body.Error.Detail) + require.Equal(t, "POST request body for '/v2/ratelimit.deleteOverride' failed to validate schema", res.Body.Error.Detail) require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) require.NotEmpty(t, res.Body.Meta.RequestId) - require.Equal(t, len(res.Body.Error.Errors), 0) + require.Greater(t, len(res.Body.Error.Errors), 0) }) t.Run("missing authorization header", func(t *testing.T) { @@ -110,10 +108,9 @@ func TestBadRequests(t *testing.T) { // No Authorization header } - namespaceName := uid.New("test") req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", + Namespace: uid.New("test"), + Identifier: "test_identifier", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -127,10 +124,9 @@ func TestBadRequests(t *testing.T) { "Authorization": {"malformed_header"}, } - namespaceName := uid.New("test") req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", + Namespace: uid.New("test"), + Identifier: "test_identifier", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/401_test.go b/go/apps/api/routes/v2_ratelimit_delete_override/401_test.go index c34f054203..b9f1430d1f 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/401_test.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/401_test.go @@ -29,10 +29,9 @@ func TestUnauthorizedAccess(t *testing.T) { "Authorization": {"Bearer invalid_token"}, } - namespaceName := uid.New("test") req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", + Namespace: uid.New("test"), + Identifier: "test_identifier", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/403_test.go b/go/apps/api/routes/v2_ratelimit_delete_override/403_test.go index 4e0ab744b6..37651dac45 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/403_test.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/403_test.go @@ -21,11 +21,10 @@ func TestWorkspacePermissions(t *testing.T) { // Create a namespace in the default workspace namespaceID := uid.New(uid.RatelimitNamespacePrefix) - namespaceName := uid.New("test") err := db.Query.InsertRatelimitNamespace(ctx, h.DB.RW(), db.InsertRatelimitNamespaceParams{ ID: namespaceID, WorkspaceID: h.Resources().UserWorkspace.ID, // Use the default workspace - Name: namespaceName, + Name: uid.New("test"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) @@ -66,8 +65,8 @@ func TestWorkspacePermissions(t *testing.T) { // Try to delete an override using a namespace from the default workspace // but with a key from a different workspace req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: identifier, + Namespace: namespaceID, + Identifier: identifier, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/404_test.go b/go/apps/api/routes/v2_ratelimit_delete_override/404_test.go index 8b1be3dc6f..baaa30211d 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/404_test.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/404_test.go @@ -21,11 +21,10 @@ func TestNotFound(t *testing.T) { // Create a namespace but no override namespaceID := uid.New("test_ns") - namespaceName := uid.New("test") err := db.Query.InsertRatelimitNamespace(ctx, h.DB.RW(), db.InsertRatelimitNamespaceParams{ ID: namespaceID, WorkspaceID: h.Resources().UserWorkspace.ID, - Name: namespaceName, + Name: uid.New("test"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) @@ -51,8 +50,8 @@ func TestNotFound(t *testing.T) { t.Run("override not found", func(t *testing.T) { req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: "non_existent_identifier", + Namespace: namespaceID, + Identifier: "non_existent_identifier", } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) @@ -64,10 +63,9 @@ func TestNotFound(t *testing.T) { // Test with non-existent namespace t.Run("namespace not found", func(t *testing.T) { - nonExistentNamespaceId := "ns_nonexistent" req := handler.Request{ - NamespaceId: &nonExistentNamespaceId, - Identifier: "some_identifier", + Namespace: "ns_nonexistent", + Identifier: "some_identifier", } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) @@ -79,10 +77,9 @@ func TestNotFound(t *testing.T) { // Test with non-existent namespace name t.Run("namespace name not found", func(t *testing.T) { - nonExistentNamespaceName := "nonexistent_namespace" req := handler.Request{ - NamespaceName: &nonExistentNamespaceName, - Identifier: "some_identifier", + Namespace: "nonexistent_namespace", + Identifier: "some_identifier", } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_delete_override/handler.go b/go/apps/api/routes/v2_ratelimit_delete_override/handler.go index f52f243212..c678d5297a 100644 --- a/go/apps/api/routes/v2_ratelimit_delete_override/handler.go +++ b/go/apps/api/routes/v2_ratelimit_delete_override/handler.go @@ -54,43 +54,44 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { return err } - namespace, err := getNamespace(ctx, h, auth.AuthorizedWorkspaceID, req) - if db.IsNotFound(err) { - return fault.New("namespace not found", - fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), - fault.Internal("namespace not found"), - fault.Public("This namespace does not exist."), - ) - } - if err != nil { - return err - } - - if namespace.WorkspaceID != auth.AuthorizedWorkspaceID { - return fault.New("namespace not found", - fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), - fault.Internal("wrong workspace, masking as 404"), - fault.Public("This namespace does not exist."), - ) - } + err = db.Tx(ctx, h.DB.RW(), func(ctx context.Context, tx db.DBTX) error { + namespace, err := db.Query.FindRatelimitNamespace(ctx, tx, db.FindRatelimitNamespaceParams{ + WorkspaceID: auth.AuthorizedWorkspaceID, + Namespace: req.Namespace, + }) + if err != nil { + if db.IsNotFound(err) { + return fault.New("namespace not found", + fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.Internal("namespace not found"), + fault.Public("This namespace does not exist."), + ) + } + return err + } - err = auth.Verify(ctx, keys.WithPermissions(rbac.Or( - rbac.T(rbac.Tuple{ - ResourceType: rbac.Ratelimit, - ResourceID: namespace.ID, - Action: rbac.DeleteOverride, - }), - rbac.T(rbac.Tuple{ - ResourceType: rbac.Ratelimit, - ResourceID: "*", - Action: rbac.DeleteOverride, - }), - ))) - if err != nil { - return err - } + if namespace.DeletedAtM.Valid { + return fault.New("namespace was deleted", + fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.Public("This namespace does not exist."), + ) + } - err = db.Tx(ctx, h.DB.RW(), func(ctx context.Context, tx db.DBTX) error { + err = auth.Verify(ctx, keys.WithPermissions(rbac.Or( + rbac.T(rbac.Tuple{ + ResourceType: rbac.Ratelimit, + ResourceID: namespace.ID, + Action: rbac.DeleteOverride, + }), + rbac.T(rbac.Tuple{ + ResourceType: rbac.Ratelimit, + ResourceID: "*", + Action: rbac.DeleteOverride, + }), + ))) + if err != nil { + return err + } // Check if the override exists before deleting override, overrideErr := db.Query.FindRatelimitOverrideByIdentifier(ctx, tx, db.FindRatelimitOverrideByIdentifierParams{ WorkspaceID: auth.AuthorizedWorkspaceID, @@ -180,21 +181,3 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { Data: openapi.V2RatelimitDeleteOverrideResponseData{}, }) } - -func getNamespace(ctx context.Context, h *Handler, workspaceID string, req Request) (db.RatelimitNamespace, error) { - switch { - case req.NamespaceId != nil: - return db.Query.FindRatelimitNamespaceByID(ctx, h.DB.RO(), *req.NamespaceId) - case req.NamespaceName != nil: - return db.Query.FindRatelimitNamespaceByName(ctx, h.DB.RO(), db.FindRatelimitNamespaceByNameParams{ - WorkspaceID: workspaceID, - Name: *req.NamespaceName, - }) - } - - return db.RatelimitNamespace{}, fault.New("missing namespace id or name", - fault.Code(codes.App.Validation.InvalidInput.URN()), - fault.Internal("missing namespace id or name"), - fault.Public("You must provide either a namespace ID or name."), - ) -} diff --git a/go/apps/api/routes/v2_ratelimit_get_override/200_test.go b/go/apps/api/routes/v2_ratelimit_get_override/200_test.go index 12dec85e4e..4e3e70757f 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/200_test.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/200_test.go @@ -65,12 +65,12 @@ func TestGetOverrideSuccessfully(t *testing.T) { // Test getting by namespace name t.Run("get by namespace name", func(t *testing.T) { req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: identifier, + Namespace: namespaceName, + Identifier: identifier, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) - require.Equal(t, 200, res.Status, "expected 200, received: %v", res.Body) + require.Equal(t, 200, res.Status, "expected 200, received: %v", *res.Body) require.NotNil(t, res.Body) require.Equal(t, overrideID, res.Body.Data.OverrideId) require.Equal(t, namespaceID, res.Body.Data.NamespaceId) @@ -82,8 +82,8 @@ func TestGetOverrideSuccessfully(t *testing.T) { // Test getting by namespace ID t.Run("get by namespace ID", func(t *testing.T) { req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: identifier, + Namespace: namespaceID, + Identifier: identifier, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_get_override/400_test.go b/go/apps/api/routes/v2_ratelimit_get_override/400_test.go index 8b6df0616b..f39122710b 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/400_test.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/400_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/require" "github.com/unkeyed/unkey/go/apps/api/openapi" handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_ratelimit_get_override" - "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/testutil" "github.com/unkeyed/unkey/go/pkg/uid" ) @@ -50,7 +49,7 @@ func TestBadRequests(t *testing.T) { t.Run("missing identifier", func(t *testing.T) { req := openapi.V2RatelimitGetOverrideRequestBody{ - NamespaceId: ptr.P("not_empty"), + Namespace: "not_empty", } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -68,9 +67,8 @@ func TestBadRequests(t *testing.T) { t.Run("empty identifier", func(t *testing.T) { req := openapi.V2RatelimitGetOverrideRequestBody{ - NamespaceId: ptr.P("not_empty"), - NamespaceName: nil, - Identifier: "", + Namespace: "not_empty", + Identifier: "", } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -88,9 +86,8 @@ func TestBadRequests(t *testing.T) { t.Run("neither namespace ID nor name provided", func(t *testing.T) { req := openapi.V2RatelimitGetOverrideRequestBody{ - NamespaceId: nil, - NamespaceName: nil, - Identifier: "user_123", + Namespace: "", + Identifier: "user_123", } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -103,7 +100,7 @@ func TestBadRequests(t *testing.T) { require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) require.NotEmpty(t, res.Body.Meta.RequestId) - require.Equal(t, len(res.Body.Error.Errors), 3) + require.Greater(t, len(res.Body.Error.Errors), 0) }) t.Run("missing authorization header", func(t *testing.T) { @@ -112,10 +109,9 @@ func TestBadRequests(t *testing.T) { // No Authorization header } - namespaceName := uid.New("test") req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", + Namespace: uid.New("test"), + Identifier: "test_identifier", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -129,10 +125,9 @@ func TestBadRequests(t *testing.T) { "Authorization": {"malformed_header"}, } - namespaceName := uid.New("test") req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", + Namespace: uid.New("test"), + Identifier: "test_identifier", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_get_override/401_test.go b/go/apps/api/routes/v2_ratelimit_get_override/401_test.go index e4f3b62abf..60e5a706df 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/401_test.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/401_test.go @@ -28,10 +28,9 @@ func TestUnauthorizedAccess(t *testing.T) { "Authorization": {"Bearer invalid_token"}, } - namespaceName := uid.New("test") req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", + Namespace: uid.New("test"), + Identifier: "test_identifier", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_get_override/403_test.go b/go/apps/api/routes/v2_ratelimit_get_override/403_test.go index c076d5bc28..3101d46515 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/403_test.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/403_test.go @@ -21,11 +21,10 @@ func TestWorkspacePermissions(t *testing.T) { // Create a namespace namespaceID := uid.New(uid.RatelimitNamespacePrefix) - namespaceName := uid.New("test") err := db.Query.InsertRatelimitNamespace(ctx, h.DB.RW(), db.InsertRatelimitNamespaceParams{ ID: namespaceID, WorkspaceID: h.Resources().UserWorkspace.ID, // Use the default workspace - Name: namespaceName, + Name: uid.New("test"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) @@ -63,8 +62,8 @@ func TestWorkspacePermissions(t *testing.T) { // Try to access the override with a key from a different workspace req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: identifier, + Namespace: namespaceID, + Identifier: identifier, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_get_override/404_test.go b/go/apps/api/routes/v2_ratelimit_get_override/404_test.go index faf0c50227..5734e1d15a 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/404_test.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/404_test.go @@ -21,11 +21,10 @@ func TestOverrideNotFound(t *testing.T) { // Create a namespace but no override namespaceID := uid.New("test_ns") - namespaceName := uid.New("test") err := db.Query.InsertRatelimitNamespace(ctx, h.DB.RW(), db.InsertRatelimitNamespaceParams{ ID: namespaceID, WorkspaceID: h.Resources().UserWorkspace.ID, - Name: namespaceName, + Name: uid.New("test"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) @@ -48,8 +47,8 @@ func TestOverrideNotFound(t *testing.T) { // Test with non-existent identifier t.Run("override not found", func(t *testing.T) { req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: "non_existent_identifier", + Namespace: namespaceID, + Identifier: "non_existent_identifier", } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) @@ -61,10 +60,9 @@ func TestOverrideNotFound(t *testing.T) { // Test with non-existent namespace t.Run("namespace not found", func(t *testing.T) { - nonExistentNamespaceId := "ns_nonexistent" req := handler.Request{ - NamespaceId: &nonExistentNamespaceId, - Identifier: "some_identifier", + Namespace: "ns_nonexistent", + Identifier: "some_identifier", } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) @@ -75,10 +73,9 @@ func TestOverrideNotFound(t *testing.T) { // Test with non-existent namespace name t.Run("namespace name not found", func(t *testing.T) { - nonExistentNamespaceName := "nonexistent_namespace" req := handler.Request{ - NamespaceName: &nonExistentNamespaceName, - Identifier: "some_identifier", + Namespace: "nonexistent_namespace", + Identifier: "some_identifier", } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) @@ -102,8 +99,8 @@ func TestOverrideNotFound(t *testing.T) { nonExistentIdentifier := "nonexistent_identifier" req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: nonExistentIdentifier, + Namespace: namespaceID, + Identifier: nonExistentIdentifier, } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, http.Header{ diff --git a/go/apps/api/routes/v2_ratelimit_get_override/handler.go b/go/apps/api/routes/v2_ratelimit_get_override/handler.go index b4ae042245..ed6c45ee95 100644 --- a/go/apps/api/routes/v2_ratelimit_get_override/handler.go +++ b/go/apps/api/routes/v2_ratelimit_get_override/handler.go @@ -2,12 +2,12 @@ package handler import ( "context" - "database/sql" "encoding/json" "net/http" "strings" "github.com/unkeyed/unkey/go/apps/api/openapi" + "github.com/unkeyed/unkey/go/internal/services/caches" "github.com/unkeyed/unkey/go/internal/services/keys" "github.com/unkeyed/unkey/go/pkg/cache" "github.com/unkeyed/unkey/go/pkg/codes" @@ -15,7 +15,6 @@ import ( "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/match" "github.com/unkeyed/unkey/go/pkg/otel/logging" - "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/rbac" "github.com/unkeyed/unkey/go/pkg/zen" ) @@ -54,50 +53,67 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { return err } - response, err := db.Query.FindRatelimitNamespace(ctx, h.DB.RO(), db.FindRatelimitNamespaceParams{ - WorkspaceID: auth.AuthorizedWorkspaceID, - Name: sql.NullString{String: ptr.SafeDeref(req.NamespaceName), Valid: req.NamespaceName != nil}, - ID: sql.NullString{String: ptr.SafeDeref(req.NamespaceId), Valid: req.NamespaceId != nil}, - }) + namespace, hit, err := h.RatelimitNamespaceCache.SWR(ctx, + cache.ScopedKey{WorkspaceID: auth.AuthorizedWorkspaceID, Key: req.Namespace}, + func(ctx context.Context) (db.FindRatelimitNamespace, error) { + response, err := db.Query.FindRatelimitNamespace(ctx, h.DB.RO(), db.FindRatelimitNamespaceParams{ + WorkspaceID: auth.AuthorizedWorkspaceID, + Namespace: req.Namespace, + }) + if err != nil { + return db.FindRatelimitNamespace{}, err + } + + result := db.FindRatelimitNamespace{ + ID: response.ID, + WorkspaceID: response.WorkspaceID, + Name: response.Name, + CreatedAtM: response.CreatedAtM, + UpdatedAtM: response.UpdatedAtM, + DeletedAtM: response.DeletedAtM, + DirectOverrides: make(map[string]db.FindRatelimitNamespaceLimitOverride), + WildcardOverrides: make([]db.FindRatelimitNamespaceLimitOverride, 0), + } + + overrides := make([]db.FindRatelimitNamespaceLimitOverride, 0) + err = json.Unmarshal(response.Overrides.([]byte), &overrides) + if err != nil { + return result, err + } + + for _, override := range overrides { + result.DirectOverrides[override.Identifier] = override + if strings.Contains(override.Identifier, "*") { + result.WildcardOverrides = append(result.WildcardOverrides, override) + } + } + + return result, nil + }, caches.DefaultFindFirstOp) + if err != nil { if db.IsNotFound(err) { - return fault.New("namespace not found", + return fault.New("namespace was deleted", fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), - fault.Internal("namespace not found"), fault.Public("The namespace was not found."), + fault.Public("This namespace does not exist."), ) } - return fault.Wrap(err, - fault.Code(codes.App.Internal.ServiceUnavailable.URN()), - fault.Internal("database failed to find the namespace"), fault.Public("Error finding the ratelimit namespace."), - ) - } - - namespace := db.FindRatelimitNamespace{ - ID: response.ID, - WorkspaceID: response.WorkspaceID, - Name: response.Name, - CreatedAtM: response.CreatedAtM, - UpdatedAtM: response.UpdatedAtM, - DeletedAtM: response.DeletedAtM, - DirectOverrides: make(map[string]db.FindRatelimitNamespaceLimitOverride), - WildcardOverrides: make([]db.FindRatelimitNamespaceLimitOverride, 0), + return err } - overrides := make([]db.FindRatelimitNamespaceLimitOverride, 0) - err = json.Unmarshal(response.Overrides.([]byte), &overrides) - if err != nil { - return fault.Wrap(err, - fault.Internal("unable to unmarshal ratelimit overrides"), - fault.Public("We're unable to parse the ratelimits overrides."), + if hit == cache.Null { + return fault.New("namespace cache null", + fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.Public("This namespace does not exist."), ) } - for _, override := range overrides { - namespace.DirectOverrides[override.Identifier] = override - if strings.Contains(override.Identifier, "*") { - namespace.WildcardOverrides = append(namespace.WildcardOverrides, override) - } + if namespace.DeletedAtM.Valid { + return fault.New("namespace was deleted", + fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.Public("This namespace does not exist."), + ) } err = auth.Verify(ctx, keys.WithPermissions(rbac.Or( diff --git a/go/apps/api/routes/v2_ratelimit_limit/handler.go b/go/apps/api/routes/v2_ratelimit_limit/handler.go index 0a15254a00..c0d6e242bd 100644 --- a/go/apps/api/routes/v2_ratelimit_limit/handler.go +++ b/go/apps/api/routes/v2_ratelimit_limit/handler.go @@ -2,7 +2,6 @@ package v2RatelimitLimit import ( "context" - "database/sql" "encoding/json" "net/http" "strconv" @@ -21,7 +20,6 @@ import ( "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/match" "github.com/unkeyed/unkey/go/pkg/otel/logging" - "github.com/unkeyed/unkey/go/pkg/otel/tracing" "github.com/unkeyed/unkey/go/pkg/rbac" "github.com/unkeyed/unkey/go/pkg/zen" ) @@ -69,15 +67,15 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { cost = *req.Cost } - ctx, span := tracing.Start(ctx, "FindRatelimitNamespace") - namespace, hit, err := h.RatelimitNamespaceCache.SWR( - ctx, - cache.ScopedKey{WorkspaceID: auth.AuthorizedWorkspaceID, Key: req.Namespace}, + // Use the namespace field directly - it can be either name or ID + namespaceKey := req.Namespace + + namespace, hit, err := h.RatelimitNamespaceCache.SWR(ctx, + cache.ScopedKey{WorkspaceID: auth.AuthorizedWorkspaceID, Key: namespaceKey}, func(ctx context.Context) (db.FindRatelimitNamespace, error) { response, err := db.Query.FindRatelimitNamespace(ctx, h.DB.RO(), db.FindRatelimitNamespaceParams{ WorkspaceID: auth.AuthorizedWorkspaceID, - Name: sql.NullString{String: req.Namespace, Valid: true}, - ID: sql.NullString{String: "", Valid: false}, + Namespace: namespaceKey, }) result := db.FindRatelimitNamespace{} // nolint:exhaustruct if err != nil { @@ -109,10 +107,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } return result, nil - }, - caches.DefaultFindFirstOp, - ) - span.End() + }, caches.DefaultFindFirstOp) if err != nil { if db.IsNotFound(err) { @@ -122,7 +117,10 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { ) } - return err + return fault.Wrap(err, + fault.Code(codes.App.Internal.UnexpectedError.URN()), + fault.Public("An unexpected error occurred while fetching the namespace."), + ) } if hit == cache.Null { diff --git a/go/apps/api/routes/v2_ratelimit_list_overrides/200_test.go b/go/apps/api/routes/v2_ratelimit_list_overrides/200_test.go index 6546aa2d09..ad897413f0 100644 --- a/go/apps/api/routes/v2_ratelimit_list_overrides/200_test.go +++ b/go/apps/api/routes/v2_ratelimit_list_overrides/200_test.go @@ -64,7 +64,7 @@ func TestListOverridesSuccessfully(t *testing.T) { // Test getting by namespace name t.Run("get by namespace name", func(t *testing.T) { req := handler.Request{ - NamespaceName: &namespaceName, + Namespace: namespaceName, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -81,7 +81,7 @@ func TestListOverridesSuccessfully(t *testing.T) { // Test getting by namespace ID t.Run("get by namespace ID", func(t *testing.T) { req := handler.Request{ - NamespaceId: &namespaceID, + Namespace: namespaceID, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -109,7 +109,7 @@ func TestListOverridesSuccessfully(t *testing.T) { require.NoError(t, err) req := handler.Request{ - NamespaceId: &emptyNamespaceID, + Namespace: emptyNamespaceID, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_list_overrides/400_test.go b/go/apps/api/routes/v2_ratelimit_list_overrides/400_test.go index 0f1dd90df2..a715e619b8 100644 --- a/go/apps/api/routes/v2_ratelimit_list_overrides/400_test.go +++ b/go/apps/api/routes/v2_ratelimit_list_overrides/400_test.go @@ -38,7 +38,7 @@ func TestBadRequests(t *testing.T) { require.NotNil(t, res.Body) require.Equal(t, "https://unkey.com/docs/errors/unkey/application/invalid_input", res.Body.Error.Type) - require.Equal(t, "You must provide either a namespace ID or name.", res.Body.Error.Detail) + require.Equal(t, "POST request body for '/v2/ratelimit.listOverrides' failed to validate schema", res.Body.Error.Detail) require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) require.NotEmpty(t, res.Body.Meta.RequestId) @@ -46,8 +46,7 @@ func TestBadRequests(t *testing.T) { t.Run("neither namespace ID nor name provided", func(t *testing.T) { req := openapi.V2RatelimitListOverridesRequestBody{ - NamespaceId: nil, - NamespaceName: nil, + Namespace: "", } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -56,11 +55,11 @@ func TestBadRequests(t *testing.T) { require.NotNil(t, res.Body) require.Equal(t, "https://unkey.com/docs/errors/unkey/application/invalid_input", res.Body.Error.Type) - require.Equal(t, "You must provide either a namespace ID or name.", res.Body.Error.Detail) + require.Equal(t, "POST request body for '/v2/ratelimit.listOverrides' failed to validate schema", res.Body.Error.Detail) require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) require.NotEmpty(t, res.Body.Meta.RequestId) - require.Equal(t, len(res.Body.Error.Errors), 0) + require.Greater(t, len(res.Body.Error.Errors), 0) }) t.Run("malformed authorization header", func(t *testing.T) { @@ -69,9 +68,8 @@ func TestBadRequests(t *testing.T) { "Authorization": {"malformed_header"}, } - namespaceName := "test_namespace" req := handler.Request{ - NamespaceName: &namespaceName, + Namespace: "test_namespace", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -85,9 +83,8 @@ func TestBadRequests(t *testing.T) { // No Authorization header } - namespaceName := "test_namespace" req := handler.Request{ - NamespaceName: &namespaceName, + Namespace: "test_namespace", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_list_overrides/401_test.go b/go/apps/api/routes/v2_ratelimit_list_overrides/401_test.go index 9e4720c312..be024c5d7c 100644 --- a/go/apps/api/routes/v2_ratelimit_list_overrides/401_test.go +++ b/go/apps/api/routes/v2_ratelimit_list_overrides/401_test.go @@ -26,9 +26,8 @@ func TestUnauthorizedAccess(t *testing.T) { "Authorization": {"Bearer invalid_token"}, } - namespaceName := "test_namespace" req := handler.Request{ - NamespaceName: &namespaceName, + Namespace: "test_namespace", } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_list_overrides/403_test.go b/go/apps/api/routes/v2_ratelimit_list_overrides/403_test.go index 17577434da..a18f9eae0a 100644 --- a/go/apps/api/routes/v2_ratelimit_list_overrides/403_test.go +++ b/go/apps/api/routes/v2_ratelimit_list_overrides/403_test.go @@ -63,7 +63,7 @@ func TestWorkspacePermissions(t *testing.T) { // Try to access the override with a key from a different workspace req := handler.Request{ - NamespaceId: &namespaceID, + Namespace: namespaceID, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_list_overrides/404_test.go b/go/apps/api/routes/v2_ratelimit_list_overrides/404_test.go index d6c697a37c..f20e1584b3 100644 --- a/go/apps/api/routes/v2_ratelimit_list_overrides/404_test.go +++ b/go/apps/api/routes/v2_ratelimit_list_overrides/404_test.go @@ -48,7 +48,7 @@ func TestOverrideNotFound(t *testing.T) { t.Run("namespace id not found", func(t *testing.T) { nonExistentNamespaceId := "ns_nonexistent" req := handler.Request{ - NamespaceId: &nonExistentNamespaceId, + Namespace: nonExistentNamespaceId, } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) @@ -61,7 +61,7 @@ func TestOverrideNotFound(t *testing.T) { t.Run("namespace name not found", func(t *testing.T) { nonExistentNamespaceName := "nonexistent_namespace" req := handler.Request{ - NamespaceName: &nonExistentNamespaceName, + Namespace: nonExistentNamespaceName, } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_list_overrides/handler.go b/go/apps/api/routes/v2_ratelimit_list_overrides/handler.go index 75b3019e19..6c925bed16 100644 --- a/go/apps/api/routes/v2_ratelimit_list_overrides/handler.go +++ b/go/apps/api/routes/v2_ratelimit_list_overrides/handler.go @@ -47,16 +47,31 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { return err } - namespace, err := getNamespace(ctx, h, auth.AuthorizedWorkspaceID, req) - - if db.IsNotFound(err) { - return fault.New("namespace not found", - fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), - fault.Internal("namespace not found"), fault.Public("This namespace does not exist."), + // Use the namespace field directly - it can be either name or ID + response, err := db.Query.FindRatelimitNamespace(ctx, h.DB.RO(), db.FindRatelimitNamespaceParams{ + WorkspaceID: auth.AuthorizedWorkspaceID, + Namespace: req.Namespace, + }) + if err != nil { + if db.IsNotFound(err) { + return fault.New("namespace not found", + fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.Internal("namespace not found"), fault.Public("This namespace does not exist."), + ) + } + return fault.Wrap(err, + fault.Code(codes.App.Internal.UnexpectedError.URN()), + fault.Public("An unexpected error occurred while loading your namespace."), ) } - if err != nil { - return err + + namespace := db.RatelimitNamespace{ + ID: response.ID, + WorkspaceID: response.WorkspaceID, + Name: response.Name, + CreatedAtM: response.CreatedAtM, + UpdatedAtM: response.UpdatedAtM, + DeletedAtM: response.DeletedAtM, } if namespace.WorkspaceID != auth.AuthorizedWorkspaceID { @@ -90,7 +105,8 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { if err != nil { return err } - response := Response{ + + responseBody := Response{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, @@ -102,7 +118,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } for i, override := range overrides { - response.Data[i] = openapi.RatelimitOverride{ + responseBody.Data[i] = openapi.RatelimitOverride{ OverrideId: override.ID, Duration: int64(override.Duration), Identifier: override.Identifier, @@ -111,29 +127,5 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } } - return s.JSON(http.StatusOK, response) -} - -func getNamespace(ctx context.Context, h *Handler, workspaceID string, req Request) (db.RatelimitNamespace, error) { - - switch { - case req.NamespaceId != nil: - { - return db.Query.FindRatelimitNamespaceByID(ctx, h.DB.RO(), *req.NamespaceId) - - } - case req.NamespaceName != nil: - { - return db.Query.FindRatelimitNamespaceByName(ctx, h.DB.RO(), db.FindRatelimitNamespaceByNameParams{ - WorkspaceID: workspaceID, - Name: *req.NamespaceName, - }) - } - } - - return db.RatelimitNamespace{}, fault.New("missing namespace id or name", - fault.Code(codes.App.Validation.InvalidInput.URN()), - fault.Internal("missing namespace id or name"), fault.Public("You must provide either a namespace ID or name."), - ) - + return s.JSON(http.StatusOK, responseBody) } diff --git a/go/apps/api/routes/v2_ratelimit_set_override/200_test.go b/go/apps/api/routes/v2_ratelimit_set_override/200_test.go index 3b06ec5b59..aa5451a8da 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/200_test.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/200_test.go @@ -49,10 +49,10 @@ func TestSetOverrideSuccessfully(t *testing.T) { // Create a new override by namespace name t.Run("create override using namespace name", func(t *testing.T) { req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "user_123", - Limit: 10, - Duration: 1000, + Namespace: namespaceName, + Identifier: "user_123", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -75,10 +75,10 @@ func TestSetOverrideSuccessfully(t *testing.T) { // Create a new override by namespace ID t.Run("create override using namespace ID", func(t *testing.T) { req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: "user_456", - Limit: 20, - Duration: 2000, + Namespace: namespaceID, + Identifier: "user_456", + Limit: 20, + Duration: 2000, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -101,10 +101,10 @@ func TestSetOverrideSuccessfully(t *testing.T) { // Create an override with a wildcard identifier t.Run("create override with wildcard identifier", func(t *testing.T) { req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: "*", // Wildcard - Limit: 5, - Duration: 2000, + Namespace: namespaceID, + Identifier: "*", // Wildcard + Limit: 5, + Duration: 2000, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -127,10 +127,10 @@ func TestSetOverrideSuccessfully(t *testing.T) { t.Run("create same override twice should update existing record", func(t *testing.T) { req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: "*", // Wildcard - Limit: 5, - Duration: 2000, + Namespace: namespaceID, + Identifier: "*", // Wildcard + Limit: 5, + Duration: 2000, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) @@ -139,10 +139,10 @@ func TestSetOverrideSuccessfully(t *testing.T) { require.NotEmpty(t, res.Body.Data.OverrideId, "Override ID should not be empty") req2 := handler.Request{ - NamespaceId: &namespaceID, - Identifier: "*", // Wildcard - Limit: 100, - Duration: 60000, + Namespace: namespaceID, + Identifier: "*", // Wildcard + Limit: 100, + Duration: 60000, } res2 := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req2) diff --git a/go/apps/api/routes/v2_ratelimit_set_override/400_test.go b/go/apps/api/routes/v2_ratelimit_set_override/400_test.go index da76f834f6..f1121fe069 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/400_test.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/400_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/unkeyed/unkey/go/apps/api/openapi" handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_ratelimit_set_override" - "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/testutil" "github.com/unkeyed/unkey/go/pkg/uid" ) @@ -49,9 +48,9 @@ func TestBadRequests(t *testing.T) { t.Run("missing identifier", func(t *testing.T) { req := openapi.V2RatelimitSetOverrideRequestBody{ - NamespaceId: ptr.P("test_namespace_id"), - Limit: 10, - Duration: 1000, + Namespace: "test_namespace_id", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -69,10 +68,10 @@ func TestBadRequests(t *testing.T) { t.Run("empty identifier", func(t *testing.T) { req := openapi.V2RatelimitSetOverrideRequestBody{ - NamespaceId: ptr.P("test_namespace_id"), - Identifier: "", - Limit: 10, - Duration: 1000, + Namespace: "test_namespace_id", + Identifier: "", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -91,9 +90,9 @@ func TestBadRequests(t *testing.T) { t.Run("missing duration", func(t *testing.T) { req := openapi.V2RatelimitSetOverrideRequestBody{ - NamespaceId: ptr.P("test_namespace_id"), - Identifier: "user_123", - Limit: 10, + Namespace: "test_namespace_id", + Identifier: "user_123", + Limit: 10, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -111,10 +110,10 @@ func TestBadRequests(t *testing.T) { t.Run("invalid limit (negative)", func(t *testing.T) { req := openapi.V2RatelimitSetOverrideRequestBody{ - NamespaceId: ptr.P("test_namespace_id"), - Identifier: "user_123", - Limit: -10, - Duration: 1000, + Namespace: "test_namespace_id", + Identifier: "user_123", + Limit: -10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -132,10 +131,10 @@ func TestBadRequests(t *testing.T) { t.Run("invalid duration (negative)", func(t *testing.T) { req := openapi.V2RatelimitSetOverrideRequestBody{ - NamespaceId: ptr.P("test_namespace_id"), - Identifier: "user_123", - Limit: 10, - Duration: -1000, + Namespace: "test_namespace_id", + Identifier: "user_123", + Limit: 10, + Duration: -1000, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -153,11 +152,10 @@ func TestBadRequests(t *testing.T) { t.Run("neither namespace ID nor name provided", func(t *testing.T) { req := openapi.V2RatelimitSetOverrideRequestBody{ - NamespaceId: nil, - NamespaceName: nil, - Identifier: "user_123", - Limit: 10, - Duration: 1000, + Namespace: "", + Identifier: "user_123", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) @@ -166,11 +164,11 @@ func TestBadRequests(t *testing.T) { require.NotNil(t, res.Body) require.Equal(t, "https://unkey.com/docs/errors/unkey/application/invalid_input", res.Body.Error.Type) - require.Equal(t, "You must provide either a namespace ID or name.", res.Body.Error.Detail) + require.Equal(t, "POST request body for '/v2/ratelimit.setOverride' failed to validate schema", res.Body.Error.Detail) require.Equal(t, http.StatusBadRequest, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) require.NotEmpty(t, res.Body.Meta.RequestId) - require.Equal(t, len(res.Body.Error.Errors), 0) + require.Greater(t, len(res.Body.Error.Errors), 0) }) t.Run("malformed authorization header", func(t *testing.T) { @@ -179,12 +177,11 @@ func TestBadRequests(t *testing.T) { "Authorization": {"malformed_header"}, } - namespaceName := uid.New("test") req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", - Limit: 10, - Duration: 1000, + Namespace: uid.New("test"), + Identifier: "test_identifier", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_set_override/401_test.go b/go/apps/api/routes/v2_ratelimit_set_override/401_test.go index ab6e131c8c..dcbd204fe6 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/401_test.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/401_test.go @@ -29,12 +29,11 @@ func TestUnauthorizedAccess(t *testing.T) { "Authorization": {"Bearer invalid_token"}, } - namespaceName := uid.New("test") req := handler.Request{ - NamespaceName: &namespaceName, - Identifier: "test_identifier", - Limit: 10, - Duration: 1000, + Namespace: uid.New("test"), + Identifier: "test_identifier", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_set_override/403_test.go b/go/apps/api/routes/v2_ratelimit_set_override/403_test.go index 9c02ccc4b3..fc49558b45 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/403_test.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/403_test.go @@ -52,10 +52,10 @@ func TestWorkspacePermissions(t *testing.T) { // Try to create an override using a namespace from the default workspace // but with a key from a different workspace req := handler.Request{ - NamespaceId: &namespaceID, - Identifier: "test_identifier", - Limit: 10, - Duration: 1000, + Namespace: namespaceID, + Identifier: "test_identifier", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_set_override/404_test.go b/go/apps/api/routes/v2_ratelimit_set_override/404_test.go index 3118ca4a29..06bbc5f7c6 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/404_test.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/404_test.go @@ -33,12 +33,11 @@ func TestNamespaceNotFound(t *testing.T) { // Test with non-existent namespace ID t.Run("namespace id not found", func(t *testing.T) { - nonExistentNamespaceId := "ns_nonexistent" req := handler.Request{ - NamespaceId: &nonExistentNamespaceId, - Identifier: "some_identifier", - Limit: 10, - Duration: 1000, + Namespace: "ns_nonexistent", + Identifier: "some_identifier", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) @@ -49,12 +48,11 @@ func TestNamespaceNotFound(t *testing.T) { // Test with non-existent namespace name t.Run("namespace name not found", func(t *testing.T) { - nonExistentNamespaceName := "nonexistent_namespace" req := handler.Request{ - NamespaceName: &nonExistentNamespaceName, - Identifier: "some_identifier", - Limit: 10, - Duration: 1000, + Namespace: "nonexistent_namespace", + Identifier: "some_identifier", + Limit: 10, + Duration: 1000, } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) diff --git a/go/apps/api/routes/v2_ratelimit_set_override/handler.go b/go/apps/api/routes/v2_ratelimit_set_override/handler.go index 66ba925f7e..133432ad08 100644 --- a/go/apps/api/routes/v2_ratelimit_set_override/handler.go +++ b/go/apps/api/routes/v2_ratelimit_set_override/handler.go @@ -56,47 +56,43 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { return err } - namespace, err := getNamespace(ctx, h, auth.AuthorizedWorkspaceID, req) - if err != nil { - if db.IsNotFound(err) { - return fault.Wrap(err, + overrideID, err := db.TxWithResult(ctx, h.DB.RW(), func(ctx context.Context, tx db.DBTX) (string, error) { + namespace, err := db.Query.FindRatelimitNamespace(ctx, tx, db.FindRatelimitNamespaceParams{ + WorkspaceID: auth.AuthorizedWorkspaceID, + Namespace: req.Namespace, + }) + if err != nil { + if db.IsNotFound(err) { + return "", fault.New("namespace not found", + fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), + fault.Public("This namespace does not exist."), + ) + } + return "", err + } + + if namespace.DeletedAtM.Valid { + return "", fault.New("namespace was deleted", fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), - fault.Internal("namespace not found"), fault.Public("This namespace does not exist."), ) } - return err - } - - if namespace.WorkspaceID != auth.AuthorizedWorkspaceID { - return fault.New("namespace not found", - fault.Code(codes.Data.RatelimitNamespace.NotFound.URN()), - fault.Internal("wrong workspace, masking as 404"), - fault.Public("This namespace does not exist."), - ) - } - - err = auth.Verify(ctx, keys.WithPermissions(rbac.Or( - rbac.T(rbac.Tuple{ - ResourceType: rbac.Ratelimit, - ResourceID: namespace.ID, - Action: rbac.SetOverride, - }), - rbac.T(rbac.Tuple{ - ResourceType: rbac.Ratelimit, - ResourceID: "*", - Action: rbac.SetOverride, - }), - ))) - if err != nil { - return fault.Wrap(err, - fault.Internal("unable to check permissions"), - fault.Public("We're unable to check the permissions of your key."), - ) - } - - overrideID, err := db.TxWithResult(ctx, h.DB.RW(), func(ctx context.Context, tx db.DBTX) (string, error) { + err = auth.Verify(ctx, keys.WithPermissions(rbac.Or( + rbac.T(rbac.Tuple{ + ResourceType: rbac.Ratelimit, + ResourceID: namespace.ID, + Action: rbac.SetOverride, + }), + rbac.T(rbac.Tuple{ + ResourceType: rbac.Ratelimit, + ResourceID: "*", + Action: rbac.SetOverride, + }), + ))) + if err != nil { + return "", err + } override, err := db.Query.FindRatelimitOverrideByIdentifier(ctx, tx, db.FindRatelimitOverrideByIdentifierParams{ WorkspaceID: auth.AuthorizedWorkspaceID, NamespaceID: namespace.ID, @@ -188,26 +184,3 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { }, }) } - -func getNamespace(ctx context.Context, h *Handler, workspaceID string, req Request) (db.RatelimitNamespace, error) { - - switch { - case req.NamespaceId != nil: - { - return db.Query.FindRatelimitNamespaceByID(ctx, h.DB.RO(), *req.NamespaceId) - } - case req.NamespaceName != nil: - { - return db.Query.FindRatelimitNamespaceByName(ctx, h.DB.RO(), db.FindRatelimitNamespaceByNameParams{ - WorkspaceID: workspaceID, - Name: *req.NamespaceName, - }) - } - } - - return db.RatelimitNamespace{}, fault.New("missing namespace id or name", - fault.Code(codes.App.Validation.InvalidInput.URN()), - fault.Internal("missing namespace id or name"), fault.Public("You must provide either a namespace ID or name."), - ) - -} diff --git a/go/pkg/db/querier_generated.go b/go/pkg/db/querier_generated.go index 64e6dcdbf7..fee284154f 100644 --- a/go/pkg/db/querier_generated.go +++ b/go/pkg/db/querier_generated.go @@ -381,9 +381,7 @@ type Querier interface { // ) as overrides // FROM `ratelimit_namespaces` ns // WHERE ns.workspace_id = ? - // AND CASE WHEN ? IS NOT NULL THEN ns.name = ? - // WHEN ? IS NOT NULL THEN ns.id = ? - // ELSE false END + // AND (ns.id = ? OR ns.name = ?) FindRatelimitNamespace(ctx context.Context, db DBTX, arg FindRatelimitNamespaceParams) (FindRatelimitNamespaceRow, error) //FindRatelimitNamespaceByID // diff --git a/go/pkg/db/queries/ratelimit_namespace_find.sql b/go/pkg/db/queries/ratelimit_namespace_find.sql index dbd9e8394e..6e9220f749 100644 --- a/go/pkg/db/queries/ratelimit_namespace_find.sql +++ b/go/pkg/db/queries/ratelimit_namespace_find.sql @@ -13,7 +13,5 @@ SELECT *, json_array() ) as overrides FROM `ratelimit_namespaces` ns -WHERE ns.workspace_id = ? -AND CASE WHEN sqlc.narg('name') IS NOT NULL THEN ns.name = sqlc.narg('name') -WHEN sqlc.narg('id') IS NOT NULL THEN ns.id = sqlc.narg('id') -ELSE false END; +WHERE ns.workspace_id = sqlc.arg(workspace_id) +AND (ns.id = sqlc.arg(namespace) OR ns.name = sqlc.arg(namespace)); diff --git a/go/pkg/db/ratelimit_namespace_find.sql_generated.go b/go/pkg/db/ratelimit_namespace_find.sql_generated.go index e054ec25af..041c0d8be0 100644 --- a/go/pkg/db/ratelimit_namespace_find.sql_generated.go +++ b/go/pkg/db/ratelimit_namespace_find.sql_generated.go @@ -25,16 +25,13 @@ SELECT id, workspace_id, name, created_at_m, updated_at_m, deleted_at_m, json_array() ) as overrides FROM ` + "`" + `ratelimit_namespaces` + "`" + ` ns -WHERE ns.workspace_id = ? -AND CASE WHEN ? IS NOT NULL THEN ns.name = ? -WHEN ? IS NOT NULL THEN ns.id = ? -ELSE false END +WHERE ns.workspace_id = ? +AND (ns.id = ? OR ns.name = ?) ` type FindRatelimitNamespaceParams struct { - WorkspaceID string `db:"workspace_id"` - Name sql.NullString `db:"name"` - ID sql.NullString `db:"id"` + WorkspaceID string `db:"workspace_id"` + Namespace string `db:"namespace"` } type FindRatelimitNamespaceRow struct { @@ -64,17 +61,9 @@ type FindRatelimitNamespaceRow struct { // ) as overrides // FROM `ratelimit_namespaces` ns // WHERE ns.workspace_id = ? -// AND CASE WHEN ? IS NOT NULL THEN ns.name = ? -// WHEN ? IS NOT NULL THEN ns.id = ? -// ELSE false END +// AND (ns.id = ? OR ns.name = ?) func (q *Queries) FindRatelimitNamespace(ctx context.Context, db DBTX, arg FindRatelimitNamespaceParams) (FindRatelimitNamespaceRow, error) { - row := db.QueryRowContext(ctx, findRatelimitNamespace, - arg.WorkspaceID, - arg.Name, - arg.Name, - arg.ID, - arg.ID, - ) + row := db.QueryRowContext(ctx, findRatelimitNamespace, arg.WorkspaceID, arg.Namespace, arg.Namespace) var i FindRatelimitNamespaceRow err := row.Scan( &i.ID,