Skip to content
This repository was archived by the owner on Mar 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions localstack-core/localstack/services/apigateway/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from typing import Any

from requests.structures import CaseInsensitiveDict

from localstack.aws.api.apigateway import (
Account,
Authorizer,
Expand Down Expand Up @@ -109,8 +107,7 @@ def __init__(

class ApiGatewayStore(BaseStore):
# maps (API id) -> RestApiContainer
# TODO: remove CaseInsensitiveDict, and lower the value of the ID when getting it from the tags
rest_apis: dict[str, RestApiContainer] = LocalAttribute(default=CaseInsensitiveDict)
rest_apis: dict[str, RestApiContainer] = LocalAttribute(default=dict)

# account details
_account: Account = LocalAttribute(default=dict)
Expand Down
18 changes: 9 additions & 9 deletions localstack-core/localstack/services/apigateway/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from moto.apigateway import models as apigateway_models
from moto.apigateway.exceptions import (
BadRequestException,
DeploymentNotFoundException,
RestAPINotFound,
StageStillActive,
)
from moto.apigateway.responses import APIGatewayResponse
Expand Down Expand Up @@ -169,19 +169,19 @@ def create_rest_api(fn, self, *args, tags=None, **kwargs):
"""
tags = tags or {}
result = fn(self, *args, tags=tags, **kwargs)
# TODO: lower the custom_id when getting it from the tags, as AWS is case insensitive

if custom_id := tags.get(TAG_KEY_CUSTOM_ID):
self.apis.pop(result.id)
result.id = custom_id
self.apis[custom_id] = result
return result

@patch(apigateway_models.APIGatewayBackend.get_rest_api, pass_target=False)
def get_rest_api(self, function_id):
for key in self.apis.keys():
if key.lower() == function_id.lower():
return self.apis[key]
raise RestAPINotFound()
if not (result.id.islower() or result.id.isnumeric()):
self.apis.pop(result.id)
raise BadRequestException(
f"The RestApiId '{result.id}' cannot contain uppercase characters"
)

return result

@patch(apigateway_models.RestAPI.delete_deployment, pass_target=False)
def patch_delete_deployment(self, deployment_id: str) -> apigateway_models.Deployment:
Expand Down
21 changes: 18 additions & 3 deletions tests/aws/services/apigateway/test_apigateway_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def test_create_rest_api_with_custom_id(self, create_rest_apigw, url_function, a
if not is_next_gen_api() and url_function == localstack_path_based_url:
pytest.skip("This URL type is not supported in the legacy implementation")
apigw_name = f"gw-{short_uid()}"
test_id = "testId123"
test_id = "test-id123"
api_id, name, _ = create_rest_apigw(name=apigw_name, tags={TAG_KEY_CUSTOM_ID: test_id})
assert test_id == api_id
assert apigw_name == name
Expand All @@ -160,6 +160,20 @@ def test_create_rest_api_with_custom_id(self, create_rest_apigw, url_function, a
assert response.ok
assert response._content == b'{"echo": "foobar", "response": "mocked"}'

@markers.aws.only_localstack
# This is not a possible feature on aws.
def test_create_rest_api_with_invalid_custom_id(self, create_rest_apigw, aws_client):
apigw_name = f"gw-{short_uid()}"
test_id = "testId123"
with pytest.raises(ClientError) as exc:
create_rest_apigw(name=apigw_name, tags={TAG_KEY_CUSTOM_ID: test_id})

assert exc.value.response["Error"]["Code"] == "BadRequestException"
assert (
exc.value.response["Error"]["Message"]
== f"The RestApiId '{test_id}' cannot contain uppercase characters"
)

@markers.aws.validated
def test_update_rest_api_deployment(self, create_rest_apigw, aws_client, snapshot):
snapshot.add_transformer(snapshot.transform.key_value("id"))
Expand Down Expand Up @@ -1505,10 +1519,11 @@ class TestTagging:
def test_tag_api(self, create_rest_apigw, aws_client, account_id, region_name):
api_name = f"api-{short_uid()}"
tags = {"foo": "bar"}
custom_id = "c0stiom1d"

# add resource tags
api_id, _, _ = create_rest_apigw(name=api_name, tags={TAG_KEY_CUSTOM_ID: "c0stIOm1d"})
assert api_id == "c0stIOm1d"
api_id, _, _ = create_rest_apigw(name=api_name, tags={TAG_KEY_CUSTOM_ID: custom_id})
assert api_id == custom_id

api_arn = arns.apigateway_restapi_arn(api_id, account_id, region_name)
aws_client.apigateway.tag_resource(resourceArn=api_arn, tags=tags)
Expand Down
24 changes: 23 additions & 1 deletion tests/aws/services/apigateway/test_apigateway_custom_ids.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest
from botocore.exceptions import ClientError
from moto.apigateway.utils import (
ApigwApiKeyIdentifier,
ApigwResourceIdentifier,
Expand All @@ -7,7 +9,7 @@
from localstack.testing.pytest import markers
from localstack.utils.strings import long_uid, short_uid

API_ID = "ApiId"
API_ID = "api-id"
ROOT_RESOURCE_ID = "RootId"
PET_1_RESOURCE_ID = "Pet1Id"
PET_2_RESOURCE_ID = "Pet2Id"
Expand Down Expand Up @@ -60,3 +62,23 @@ def test_apigateway_custom_ids(
assert pet_resource_1["id"] == PET_1_RESOURCE_ID
assert pet_resource_2["id"] == PET_2_RESOURCE_ID
assert api_key["id"] == API_KEY_ID


@markers.aws.only_localstack
@markers.requires_in_process
def test_apigateway_invalid_rest_api_custom_id(
aws_client, set_resource_custom_id, create_rest_apigw, account_id, region_name, cleanups
):
rest_api_name = f"apigw-{short_uid()}"
bad_api_id = "UpperCaseApi"

set_resource_custom_id(
ApigwRestApiIdentifier(account_id, region_name, rest_api_name), bad_api_id
)
with pytest.raises(ClientError) as exc:
create_rest_apigw(name=rest_api_name)
assert exc.value.response["Error"]["Code"] == "BadRequestException"
assert (
exc.value.response["Error"]["Message"]
== f"The RestApiId '{bad_api_id}' cannot contain uppercase characters"
)
Loading