Search/Build a Local Cloud Emulator That Works with Official SDKs and CLIs

Build a Local Cloud Emulator That Works with Official SDKs and CLIs

Use when adding services or operations to an emulator that imitates a cloud platform on your machine. It checks the real wire protocol first, keeps requests and responses like the real cloud's, updates storage and configuration together, and tests with official SDKs before release.

To use this AI rule
## Match the Real Cloud's Request and Response Formats Before Taking Shortcuts Prioritize: 1. compatibility with the upstream cloud protocol; 2. behavior expected by official SDK and CLI clients; 3. reuse of existing emulator patterns; 4. correctness over convenience; 5. narrow, testable changes. Do not add custom endpoint shapes or simplify request and response formats for implementation convenience. Do not perform a broad refactor unless the requested behavior requires it. Before editing, identify the service, control-plane or data-plane role, protocol family, existing implementation to mirror, configuration impact, storage impact, documentation impact, and minimal useful test plan. When behavior is unclear, prefer the upstream platform contract, then established emulator behavior, then compatibility-suite expectations. If the task would require a cross-service architectural change, surface the tradeoffs instead of refactoring blindly. ## Keep Protocol Handlers Thin and Business Logic in Services Use a layered service design: - controllers or handlers parse protocol input and produce protocol-compatible responses; - services contain business logic and throw the shared cloud-domain exception; - models represent domain data; - registries, configuration, storage factories, lifecycle services, parsers, builders, and exception mappers provide shared infrastructure. Keep controllers thin. Reuse shared utilities and copy the nearest established service structure before introducing a new pattern. Route domain failures through the shared exception type. Query and XML flows should use the standard exception mapper; JSON protocol flows should return the required structured error body. Controller return types must remain compatible with ahead-of-time reflection and serialization constraints. Use constructor injection, braces for conditionals, and self-explanatory code. Never leave a catch block empty; if an exception is intentionally tolerated, log enough context to diagnose it. ## Find Each Service's Real Protocol Before Adding an Operation Determine the real wire protocol before adding an operation: - query protocols use form-encoded actions and XML responses; - JSON RPC-style protocols use a target header and structured JSON; - REST JSON and REST XML use resource paths with their native body format; - data-plane services may require native TCP proxies rather than HTTP management APIs. Use the shared XML builder, parser, and namespace constants. Never parse XML with regular expressions. Keep JSON errors aligned with the target protocol’s error structure. Some services expose more than one compatibility path. Keep alternate handlers aligned and test them together. Treat identity-discovery endpoints as their actual open protocol rather than forcing them through the management API model. Validate management APIs through official SDK clients, not only handcrafted HTTP requests. A manually accepted payload is not proof that client serialization, headers, errors, pagination, or URL generation are compatible. ## Always Update Storage Modes and Configuration Files Together Support storage through the shared factory and declared modes; never instantiate a storage implementation directly inside a service. Respect lifecycle hooks for loading, flushing, checkpoints, or write-ahead behavior. Treat the repository’s runtime configuration file as the effective source of truth when interface defaults and deployed values differ. For storage-related behavior: 1. update the canonical configuration interface; 2. update main runtime configuration; 3. update test configuration; 4. wire the mode through the storage factory; 5. verify lifecycle integration. For any user-facing configuration, also update documentation and follow the established environment-variable naming convention. Review base URL, hostname, region and account defaults, port ranges, persistence roots, and container networking when they affect generated identifiers or endpoints. Add new fields compatibly and keep shared configuration, storage, and service registration synchronized. ## Test Compatibility with Official SDKs, Not Manual Requests Use unit tests for isolated service behavior and integration tests for protocol handling, persistence, and client-visible state. Ordered integration execution is acceptable only when the behavior is intentionally stateful. Any change to parsing, response shape, errors, persistence semantics, URL generation, service enablement, or alternate protocol paths requires automated coverage. Run a focused test while iterating, then the directly relevant suite. Use the compatibility project when a change can affect real SDK behavior. Prefer SDK-based assertions over raw HTTP and document any intentional upstream deviation. When alternate protocol paths expose the same operation, test them for equivalent semantics. When state persists, verify load and flush behavior across the relevant storage modes. Do not claim compatibility from manual requests alone. If behavior changed without automated coverage, explain the missing test and why it could not be added. ## Finish New Services Fully Without Changing Release Workflows Along the Way When adding a service, create its controller, service, and models; register it; add canonical and test configuration; connect storage through the factory; add tests; and update documentation. Before finishing: - run relevant tests and protocol compatibility checks; - confirm no custom endpoints or convenience wire formats were introduced; - verify configuration, storage, lifecycle, and documentation updates; - check generated URLs and resource identifiers for consistency; - keep logging structured and quiet in hot paths. Keep pull requests focused, preserve existing behavior unless change is explicit, and separate unrelated refactors. Use conventional commit types and human-only attribution. Treat release branches, tags, and publishing workflows as critical infrastructure. A merge to the development branch does not by itself prove a stable release. Do not alter release behavior incidentally while implementing service functionality.