diff --git a/.gitattributes b/.gitattributes index 641e8a3b..a4e1b5c5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ src/Resources/*.php linguist-generated src/Routes/*.php linguist-generated -src/SeamClient.php linguist-generated +src/Seam.php linguist-generated # Keep development files out of the published package. # GitHub builds the archives Composer downloads as dist with git archive, diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 95ca05d1..54db6700 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -20,7 +20,9 @@ jobs: os: - ubuntu-latest php: - - '8.0' + - '8.2' + - '8.3' + - '8.4' - '8.5' include: - os: ubuntu-latest @@ -44,7 +46,7 @@ jobs: fail-fast: false matrix: php: - - '8.0' + - '8.2' - '8.5' steps: - name: Checkout @@ -73,7 +75,9 @@ jobs: os: - ubuntu-latest php: - - '8.0' + - '8.2' + - '8.3' + - '8.4' - '8.5' include: - os: ubuntu-latest @@ -117,7 +121,7 @@ jobs: contents: | client` is the Guzzle client](#seam-client-is-the-guzzle-client) | You use `$seam->client`, `$seam->request()`, or the removed public properties | +| [Requests are retried and time out sooner](#requests-are-retried-and-time-out-sooner) | You depend on requests never being retried, or on the 60-second timeout | +| [`poll_until_ready` is replaced](#poll_until_ready-is-replaced-by-wait_for_action_attempt) | You call `$seam->action_attempts->poll_until_ready()` or rely on its 20 s/0.4 s timing | +| [Nested resource classes are namespaced](#nested-resource-classes-are-namespaced) | You type-hint nested classes such as `Seam\Resources\DeviceProperties` | +| [Discriminated resources use specific subclasses](#discriminated-resources-use-specific-subclasses) | You inspect exact classes or construct events, action attempts, errors, or warnings | +| [Resource constructors take required properties first](#resource-constructors-take-required-properties-first) | You construct a resource positionally rather than with named arguments | +| [Missing required parameters fail locally](#missing-required-parameters-fail-locally) | You call endpoints with missing parameters and rely on the server's 400 response | +| [Preferred HTTP methods and URL search params](#endpoints-use-preferred-http-methods) | You inspect traffic in a proxy, mock server, or firewall rules | +| [Error handling refinements](#error-handling-refinements) | You compare `getRequestId()` to `""`, or depend on 3xx responses passing through | +| [Pagination metadata is a `Seam\Pagination`](#pagination-metadata-is-a-typed-object) | You treat the paginator's metadata as a `stdClass` | +| [`Seam\Version` replaces `Seam\Utils\PackageVersion`](#seamversion-replaces-packageversion) | You read the package version programmatically | + +## PHP 8.2 or later is required + +Version 3 declared support for PHP 8.0. Version 4 requires PHP >= 8.2, since PHP 8.1 reached end of life in December 2025. The SDK also gains two dependencies: `caseyamcl/guzzle_retry_middleware` and `svix/svix`. + +## `Seam\Seam` replaces `Seam\SeamClient` + +The client class is renamed from `Seam\SeamClient` to `Seam\Seam`; there is no compatibility alias. The constructor takes named options, so `$endpoint` is no longer the second positional argument, and `$throw_http_errors` is removed — an API error always raises a Seam exception: + +```php +// v3 +$seam = new Seam\SeamClient("your-api-key", "https://example.com"); + +// v4 +$seam = new Seam\Seam(api_key: "your-api-key", endpoint: "https://example.com"); +``` + +Static factories are available as an alternative to the constructor: `Seam::from_api_key()`, `Seam::from_personal_access_token()`, and `Seam::from_client()`. + +In v3, `$throw_http_errors = true` made Guzzle throw its own `RequestException` before the SDK could map the error. If you passed it, catch the Seam error classes instead — they are unchanged (see [Error handling refinements](#error-handling-refinements)). + +## `$seam->client` is the Guzzle client + +In v3, `$seam->client` was a bare `GuzzleHttp\Client`, and the SDK's error mapping lived in the separate `$seam->request()` helper. In v4, `$seam->client` is the fully configured Guzzle client the SDK itself uses — error mapping and retries are Guzzle middleware, and query params and JSON bodies are serialized per the Seam standards on the way out. Anything Guzzle can do is available on it directly, and it throws the same Seam exceptions the endpoint methods do. + +`Seam::request()` is removed. Call the client instead and decode the PSR-7 response yourself: + +```php +// v3: returns decoded JSON +$res = $seam->request("POST", "/devices/list", json: (object) []); + +// v4: returns a PSR-7 response +$res = json_decode( + $seam->client->request("GET", "/devices/list")->getBody() +); +``` + +The property is typed `GuzzleHttp\ClientInterface` rather than `GuzzleHttp\Client`, so update any type hints. Two other public members are removed with no replacement on the instance: + +- `$seam->api_key` is gone. +- `$seam->ltsVersion`, the global `LTS_VERSION` constant, and the `seam-lts-version` request header are gone, with no replacement. + +To configure the underlying client, pass `guzzle_options` (merged into the Guzzle client's config), or pass a preconfigured client via the `client` option. A preconfigured client carries its own endpoint and authorization, so combining it with `api_key`, `endpoint`, or any other option that would configure one raises `Seam\InvalidOptionsError` instead of being silently ignored: + +```php +$seam = new Seam\Seam( + api_key: "your-api-key", + guzzle_options: ["proxy" => "http://localhost:8125"], +); +``` + +## Requests are retried and time out sooner + +Version 3 never retried a request and timed out after 60 seconds. Version 4 makes up to three attempts by default: the initial request and two retries. Retries are limited to `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE` requests that fail because of a transport error, timeout, HTTP 429 response, or HTTP 5xx response. `POST` and `PATCH` requests are never retried. Retries use exponential backoff with jitter, and a `Retry-After` header is honored when it is longer than the calculated backoff. + +The timeout drops from 60 to 30 seconds, covers connecting as well as reading, and applies to each attempt rather than the whole sequence. Both behaviors are options: + +```php +$seam = new Seam\Seam( + retries: 0, // Disable retries. + timeout: 60.0, // Restore the v3 timeout, in seconds. +); +``` + +Note the interaction with the new HTTP methods: because reads are now `GET`, they are retried by default, which they were not in v3 (as `POST`). + +## `poll_until_ready` is replaced by `wait_for_action_attempt` + +`$seam->action_attempts->poll_until_ready()` is removed. Endpoints that return an [action attempt](https://docs.seam.co/latest/core-concepts/action-attempts) still wait for it by default, but the waiting is configured through `wait_for_action_attempt`, which now also accepts a `timeout` and `polling_interval` (in seconds), per request or as a client-wide default: + +```php +// v3 +$seam->locks->unlock_door(device_id: $device_id, wait_for_action_attempt: true); +$seam->action_attempts->poll_until_ready($action_attempt_id, timeout: 30.0); + +// v4 +$seam->locks->unlock_door( + device_id: $device_id, + wait_for_action_attempt: ["timeout" => 30.0, "polling_interval" => 2.0], +); + +$seam = new Seam\Seam(wait_for_action_attempt: false); // Client-wide default. +``` + +The default timing changes from a 20-second timeout with a 0.4-second polling interval to a 10-second timeout with a 1-second polling interval. Pass an explicit `timeout` if 10 seconds is too short for your devices. `Seam\ActionAttemptFailedError` and `Seam\ActionAttemptTimeoutError` are raised exactly as in v3. + +## Nested resource classes are namespaced + +Top-level resource classes are unchanged: `$seam->devices->get()` still returns a `Seam\Resources\Device`. The generated classes that type _nested_ properties move from resource-prefixed names in `Seam\Resources` to sub-namespaces mirroring the property path: + +```php +// v3 +use Seam\Resources\DeviceProperties; +use Seam\Resources\DeviceBattery; + +// v4 +use Seam\Resources\Device\Properties; +use Seam\Resources\Device\Properties\Battery; +``` + +This rename also fixes a class of bugs where two nested shapes competed for one name and the loser was silently dropped: for example, `$device->properties->battery->status` did not exist in v3 because the keypad's battery class won the name `DeviceBattery`. In v4 every nested shape has its own class, so fields that were missing on `device.properties.battery`, the climate preset ecobee metadata, and the phone session credential and entrance metadata are now present. + +Property reads are unaffected — only explicit references to the nested class names need updating. + +## Discriminated resources use specific subclasses + +In v3, events and action attempts were returned as base resource classes that +combined the properties of every possible variant. Errors and warnings were +similarly represented by one class per containing resource. In v4, known +discriminator values return subclasses containing only that variant's +properties: + +```php +use Seam\Resources\Event; +use Seam\Resources\Event\AccessCodeCreated; + +$event = $seam->events->get(event_id: $event_id); + +if ($event instanceof AccessCodeCreated) { + print $event->access_code_id; +} +``` + +The same pattern applies to action attempts and nested errors and warnings, for +example `Seam\Resources\ActionAttempt\UnlockDoor` and +`Seam\Resources\Device\Errors\DeviceOffline`. Base-class type hints and +`instanceof` checks continue to work because every variant extends its base. +Code that checks an exact class with `$resource::class`, or constructs these +resources directly, must use the appropriate variant class instead. + +If the API returns an unknown discriminator, the SDK returns the concrete base +class and preserves the raw value. Discriminants and other enum-valued response +properties remain strings, so existing string comparisons continue to work. +Generated backed enums are available as optional companions: + +```php +use Seam\Resources\Event\EventType; + +$event->event_type === "access_code.created"; +$event->event_type === EventType::ACCESS_CODE_CREATED->value; +$event_type = EventType::tryFrom($event->event_type); +``` + +## Resource constructors take required properties first + +In v3, resource constructor parameters were ordered by property name alone. In v4 required properties come first and optional ones follow, alphabetical within each group. + +```php +// v3 +new Seam\Resources\AcsUser($access_schedule, $acs_system_id, ...); + +// v4 +new Seam\Resources\AcsUser($acs_system_id, $acs_user_id, ..., access_schedule: $schedule); +``` + +This affects only positional construction. Reading properties and `from_json` are unaffected. Construct resources with [named arguments](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments). + +## Missing required parameters fail locally + +In v3, every endpoint parameter defaulted to `null`, so a call missing a required parameter was sent to the server and failed with `Seam\HttpInvalidInputError` after a round trip. In v4, required parameters have no default, so PHP itself rejects the call with an `ArgumentCountError` (or an `Error` for a missing named argument). Endpoints that require _at least one_ of their parameters throw `InvalidArgumentException` when called with none: + +```php +// v3: raises Seam\HttpInvalidInputError after a round trip to the server +// v4: raises ArgumentCountError locally +$seam->devices->get(); + +// v4: raises InvalidArgumentException("At least one parameter is required for /locks/get") +$seam->locks->get(); +``` + +If you catch `HttpInvalidInputError` around calls that could be sent incomplete, also handle these local errors (or fix the call site). + +Relatedly, call endpoint methods with [named arguments](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments). Parameter order is derived from the API definition and can change as endpoints gain parameters, so a positional call can silently start binding a value to the wrong parameter after an upgrade. Named arguments are stable. + +## Endpoints use preferred HTTP methods + +In v3, every endpoint was called with `POST` and a JSON body. In v4, endpoints use the HTTP method the Seam API prefers: + +- Read endpoints (`get`, `list`, and friends) use `GET`, with parameters sent as URL search params serialized per [Seam's URL search params standard](https://github.com/seamapi/url-search-params-serializer) (with `_strict=true` appended). +- Update endpoints use `PATCH` or `PUT`. +- Delete endpoints use `DELETE`. +- Create and action endpoints (`create`, `lock_door`, etc.) remain `POST`. + +Method signatures, arguments, and return values are unchanged — this only matters if something outside your code observes the HTTP traffic: proxy or firewall rules that allowlist methods, request logging, or test mocks registered against `POST` routes. The SDK also no longer sets a `User-Agent` of its own (v3 sent `Seam PHP Client `): it identifies itself with the `seam-sdk-name` and `seam-sdk-version` headers, and a `User-Agent` you set through `guzzle_options` is sent unchanged. + +If you call the Seam API with your own HTTP client, the serializer is available as `Seam\UrlSearchParamsSerializer::serialize()`. + +## Error handling refinements + +The exception classes keep their v3 names and stay in the `Seam` namespace, so existing catch blocks keep working: `Seam\HttpApiError`, `Seam\HttpUnauthorizedError`, `Seam\HttpInvalidInputError`, `Seam\ActionAttemptError`, `Seam\ActionAttemptFailedError`, and `Seam\ActionAttemptTimeoutError`. What changes: + +- Every SDK exception now implements the `Seam\SeamException` interface, so they can be caught as a group. +- `getRequestId()` returns `null` instead of `""` when the response carries no request id, and is typed `?string`. +- A response in the 3xx range is no longer treated as successful: redirects are followed by Guzzle, and an unfollowed redirect is an error instead of an empty result. +- An error response not shaped like a Seam error — a gateway returning HTML, for example — raises the underlying Guzzle exception with the real request attached, instead of a fabricated one. +- Malformed JSON in a response raises instead of silently decoding to `null`. + +## Pagination metadata is a typed object + +`firstPage()` and `nextPage()` still return a `[$items, $pagination]` pair, but the metadata is a readonly `Seam\Pagination` object rather than a raw `stdClass`. Property reads (`has_next_page`, `next_page_cursor`, `next_page_url`) are unchanged, so typical pagination loops, `flatten()`, and `flattenToArray()` work as before. What breaks is treating it as a `stdClass`: casting, mutation, or `json_encode` round-trips of the raw envelope. + +The paginator also validates its input now: using it with an endpoint that returns no pagination metadata throws `InvalidArgumentException`, and an `on_response` callback you pass in the params is chained rather than silently replaced. + +## `Seam\Version` replaces `PackageVersion` + +`Seam\Utils\PackageVersion::get()` is renamed to `Seam\Version::get()`, and the version string is also available as the `Seam\Version::VERSION` constant. + +## New in v4 + +These are additions, not breaking changes, but they are worth adopting while you migrate. + +### Personal access tokens and `SeamWithoutWorkspace` + +The client can now authenticate with a personal access token scoped to a workspace, and the new `Seam\SeamWithoutWorkspace` client reaches the endpoints that take no workspace in scope, such as listing your workspaces: + +```php +$seam = Seam\Seam::from_personal_access_token( + "your-personal-access-token", + "your-workspace-id", +); + +$seam = new Seam\SeamWithoutWorkspace( + personal_access_token: "your-personal-access-token", +); +$workspaces = $seam->workspaces->list(); +``` + +Tokens are validated on construction: a client session token, JWT, or publishable key passed as an API key raises `Seam\InvalidTokenError` with a message naming the mistake. + +### Authentication from the environment + +`SEAM_API_KEY` was already read in v3. Version 4 also reads `SEAM_PERSONAL_ACCESS_TOKEN` and `SEAM_WORKSPACE_ID` when no explicit credentials are passed, so `new Seam\Seam()` works under either authentication method. Setting both `SEAM_API_KEY` and `SEAM_PERSONAL_ACCESS_TOKEN` is an error. The endpoint may be set with `SEAM_ENDPOINT`. + +### Webhook verification + +`Seam\SeamWebhook` verifies incoming webhooks and returns a typed `Seam\Resources\Event`: + +```php +$webhook = new Seam\SeamWebhook($_ENV["SEAM_WEBHOOK_SECRET"]); +$event = $webhook->verify($payload, $headers); +``` + +### Explicit null with `NullValue::NULL` + +The Seam API distinguishes an omitted parameter from one explicitly set to null: in an update request, an omitted parameter leaves the current value unchanged, while a null parameter unsets it. PHP's `null` keeps the safe v3 meaning of "omit"; to send an explicit null, pass the `Seam\NullValue::NULL` sentinel: + +```php +use Seam\NullValue; + +// Leaves the name unchanged (same as v3). +$seam->devices->update(device_id: $device_id, name: null); + +// Unsets the name (new in v4). +$seam->devices->update(device_id: $device_id, name: NullValue::NULL); +``` + +The other Seam SDKs spell the sentinel `NULL` and its type `Null`, but both names are reserved in PHP, so the type and the value live on one enum. Only parameters the Seam API documents as nullable are typed to accept the sentinel (e.g. `string|NullValue|null`), so passing it anywhere else fails with a `TypeError`. + +## Migration checklist + +1. Upgrade your runtime to PHP 8.2 or later. +2. Update the dependency: `composer require "seamapi/seam:^4"`. +3. Rename `Seam\SeamClient` to `Seam\Seam` and pass constructor options by name; drop `$throw_http_errors`. +4. Replace `$seam->request()` with `$seam->client->request()`. Remove any use of `$seam->api_key`, `LTS_VERSION`, `$seam->ltsVersion`, or the `seam-lts-version` header — there is no replacement. +5. Replace `poll_until_ready()` with `wait_for_action_attempt`, and review the new 10 s/1 s defaults. +6. Review the new retry policy and 30-second timeout; pass `retries: 0` or `timeout: 60.0` to keep v3 behavior. +7. Update type hints on nested resource classes (`Seam\Resources\DeviceProperties` → `Seam\Resources\Device\Properties`) and on `$seam->client` (`ClientInterface`). +8. Replace exact base-class checks and direct construction of events, action attempts, errors, and warnings with their discriminated variant classes. +9. Switch endpoint calls to named arguments, and handle `ArgumentCountError`/`InvalidArgumentException` where calls might be missing parameters. +10. If proxies, firewalls, or test mocks assume all requests are `POST`, update them for `GET`/`PATCH`/`PUT`/`DELETE`. +11. Rename `Seam\Utils\PackageVersion` to `Seam\Version`. +12. Optionally, adopt personal access tokens, `SeamWebhook`, and `NullValue::NULL`. + +# Migrating from seamapi/seam v2 to v3 + +This guide covers upgrading from `seamapi/seam` v2.x to v3 of the [Seam PHP SDK](https://github.com/seamapi/php). + +Version 3 regenerates the SDK from Seam's API blueprint, the same source of truth used by the SDKs for other languages. It is a much smaller upgrade than v4: client construction, authentication, endpoint methods, error handling, and pagination are all unchanged. The breaking changes are in the generated classes — their namespaces, constructors, and two method signatures. + +## Installation + +```sh +composer require "seamapi/seam:^3" +``` + +## Summary of breaking changes + +| Change | Affects you if... | +| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| [Two `get` methods reorder their parameters](#two-get-methods-reorder-their-parameters) | You call `$seam->events->get()` or `$seam->acs->users->get()` positionally | +| [Resource classes move to `Seam\Resources`](#resource-classes-move-to-seamresources) | You type-hint or import `Seam\Objects\...` classes | +| [Route clients move to `Seam\Routes`](#route-clients-move-to-seamroutes) | You type-hint route clients such as `Seam\DevicesClient` | +| [`ActionAttempt->result` is typed](#actionattempt-result-is-typed) | You read fields off an action attempt's raw `result` | +| [Resource constructors are alphabetical and nullable](#resource-constructors-are-alphabetical-and-nullable) | You construct resource objects yourself, or rely on non-nullable property types | +| [Undocumented resource classes are removed](#undocumented-resource-classes-are-removed) | You reference classes such as `Seam\Objects\PhoneSession` | + +## Two `get` methods reorder their parameters + +The primary resource ID is now the first parameter of every `get` method. Two endpoints change as a result, and a positional call silently sends the old first argument as the wrong parameter: + +- `$seam->events->get()`: `$event_id` is now first (was `$device_id`). +- `$seam->acs->users->get()`: `$acs_user_id` is now first (was `$acs_system_id`). + +```php +// v2 +$event = $seam->events->get($device_id); + +// v3 +$event = $seam->events->get(device_id: $device_id); +``` + +Since parameter order follows the API definition and can change again, prefer named arguments for every endpoint call, not just these two. + +## Resource classes move to `Seam\Resources` + +As of v3.3.0, the generated resource classes live in the `Seam\Resources` namespace instead of `Seam\Objects`. The class names and shapes are unchanged: + +```php +// v2 +use Seam\Objects\Device; + +// v3 +use Seam\Resources\Device; +``` + +## Route clients move to `Seam\Routes` + +As of v3.5.0, the route clients live in the `Seam\Routes` namespace instead of `Seam`: + +```php +// v2 +function listLocks(Seam\LocksClient $locks): array { ... } + +// v3 +function listLocks(Seam\Routes\LocksClient $locks): array { ... } +``` + +`$seam->devices`, `$seam->locks`, and the rest are unaffected — only explicit references to the client class names need updating. `Seam\SeamClient` itself, the exception classes, and `Seam\Paginator` stay where they were. + +## `ActionAttempt->result` is typed + +`ActionAttempt->result` is now a typed `ActionAttemptResult|null` instead of raw decoded JSON. Fields the API spec types — for example `acs_credential_on_encoder` — are still available as properties. Fields the spec defines no type for, such as `result->access_code` and `result->noise_threshold`, are no longer present; fetch the resource from its own endpoint instead. + +## Resource constructors are alphabetical and nullable + +Resource object constructor parameters are now ordered alphabetically and uniformly nullable, and the matching properties are uniformly nullable too. Objects returned by the SDK are unaffected. What breaks: + +- Code constructing resource objects positionally binds values to the wrong parameters. Construct with named arguments or `from_json()`. +- Static analysis that relied on non-nullable property types (for example `$device->device_id` being `string`) now sees `string|null`. + +## Undocumented resource classes are removed + +Classes generated for resources absent from Seam's public API documentation — for example `Seam\Objects\PhoneSession`, `Seam\Objects\Customer`, and the `Seam\Objects\UnmanagedAcs...` nested classes — are no longer generated. The endpoint methods themselves are unchanged. If you referenced one of these classes, the data is still in the API response; the SDK just no longer ships a class for it. + +## Migration checklist + +1. Update the dependency: `composer require "seamapi/seam:^3"`. +2. Switch positional endpoint calls to named arguments — at minimum, fix `$seam->events->get()` and `$seam->acs->users->get()`. +3. Rename `Seam\Objects\...` imports and type hints to `Seam\Resources\...`. +4. Rename route client type hints from `Seam\...Client` to `Seam\Routes\...Client`. +5. Update code reading untyped `ActionAttempt->result` fields. +6. Construct resource objects with named arguments, and treat resource properties as nullable. diff --git a/README.md b/README.md index 567a864c..6b9816ff 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,212 @@ # Seam PHP SDK -Control locks, lights and other internet of things devices with Seam's simple API. +[![Packagist](https://img.shields.io/packagist/v/seamapi/seam.svg)](https://packagist.org/packages/seamapi/seam) +[![GitHub Actions](https://github.com/seamapi/php/actions/workflows/check.yml/badge.svg)](https://github.com/seamapi/php/actions/workflows/check.yml) -Check out [the documentation](https://docs.seam.co) or the usage below. +PHP SDK for the Seam API. + +## Description + +[Seam] makes it easy to integrate IoT devices with your applications. +This is an official SDK for the Seam API. +Please refer to the official [Seam Docs] to get started. + +Parts of this SDK are generated from always up-to-date type information +provided by [@seamapi/types]. +This ensures all API methods, request shapes, and response shapes are +accurate and fully typed. + +The underlying HTTP client is [Guzzle]. + +[Seam]: https://www.seam.co/ +[Seam Docs]: https://docs.seam.co/latest/ +[@seamapi/types]: https://github.com/seamapi/types/ +[Guzzle]: https://docs.guzzlephp.org/ + +## Installation + +Add this as a dependency to your project using [Composer] with + +``` +$ composer require seamapi/seam +``` + +[Composer]: https://getcomposer.org/ ## Usage +> [!NOTE] +> These examples assume `SEAM_API_KEY` is set in your environment. + +Endpoint methods take [named arguments], which is the supported way to call +them. PHP allows the parameters to be passed positionally too, but their order +is derived from the API definition and can change as endpoints gain parameters, +so a positional call can start binding a value to the wrong parameter after an +upgrade. Passing them by name is stable. + +[named arguments]: https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments + +### Examples + +#### List devices + ```php -$seam = new Seam\SeamClient("YOUR_API_KEY"); +$seam = new Seam\Seam(); -# Create a Connect Webview to login to a provider -$connect_webview = $seam->connect_webviews->create( - accepted_providers: ["august"] -); +$devices = $seam->devices->list(); +``` -print "Please Login at this url: " . $connect_webview->url; +#### Unlock a door -# Poll until connect webview is completed -while (true) { - $connect_webview = $seam->connect_webviews->get( - $connect_webview->connect_webview_id - ); - if ($connect_webview->status == "authorized") { - break; - } else { - sleep(1); - } -} +```php +$seam = new Seam\Seam(); -$connected_account = $seam->connected_accounts->get( - $connect_webview->connected_account_id -); +$lock = $seam->locks->get(name: "Front Door"); +$seam->locks->unlock_door(device_id: $lock->device_id); +``` + +### Authentication Method -print "Looks like you connected with " . - json_encode($connected_account->user_identifier); +The SDK supports two authentication mechanisms. +Configure either by passing the corresponding options to the `Seam` +constructor, or with the more ergonomic static factory methods. -$devices = $seam->devices->list( - connected_account_id: $connected_account->connected_account_id +#### API Key + +An API key is scoped to a single workspace and should only be used on the +server. Obtain one from the Seam Console. + +```php +// Set the SEAM_API_KEY environment variable +$seam = new Seam\Seam(); + +// Pass as an option to the constructor +$seam = new Seam\Seam(api_key: "your-api-key"); + +// Use the factory method +$seam = Seam\Seam::from_api_key("your-api-key"); +``` + +#### Personal Access Token + +A Personal Access Token is scoped to a Seam Console user. +It must be used with a workspace id. + +```php +// Set the SEAM_PERSONAL_ACCESS_TOKEN and SEAM_WORKSPACE_ID environment variables +$seam = new Seam\Seam(); + +// Pass as options to the constructor +$seam = new Seam\Seam( + personal_access_token: "your-personal-access-token", + workspace_id: "your-workspace-id" +); + +// Use the factory method +$seam = Seam\Seam::from_personal_access_token( + "your-personal-access-token", + "your-workspace-id" ); +``` + +### Action Attempts + +Some operations tell a device to do something, and the device may take time +to report back. Those endpoints return an [action attempt]. + +By default the SDK waits for the action attempt to finish: + +- It polls up to a timeout at a polling interval. +- It returns a fresh copy of the successful action attempt. +- It throws `Seam\ActionAttemptFailedError` if the action failed. +- It throws `Seam\ActionAttemptTimeoutError` if the timeout + elapses first. + +Both errors extend `Seam\ActionAttemptError` and expose the action +attempt with `getActionAttempt()`. + +[action attempt]: https://docs.seam.co/latest/core-concepts/action-attempts + +```php +use Seam\ActionAttemptFailedError; +use Seam\ActionAttemptTimeoutError; + +try { + $seam->locks->unlock_door(device_id: $device_id); +} catch (ActionAttemptFailedError $error) { + print "Could not unlock the door: " . $error->getMessage(); + print "Error code: " . $error->getErrorCode(); +} catch (ActionAttemptTimeoutError $error) { + print "The door did not unlock in time"; + print "Action attempt: " . $error->getActionAttempt()->action_attempt_id; +} +``` + +Waiting may be disabled for the whole client: -print "You have " . count($devices) . " devices"; +```php +$seam = new Seam\Seam(wait_for_action_attempt: false); -$device_id = $devices[0]->device_id; +$action_attempt = $seam->locks->unlock_door(device_id: $device_id); +$action_attempt->status; // "pending" +``` -# Lock a Door -$seam->locks->lock_door($device_id); +or for a single request: -$updated_device = $seam->devices->get($device_id); -$updated_device->properties->locked; // true +```php +$action_attempt = $seam->locks->unlock_door( + device_id: $device_id, + wait_for_action_attempt: false +); +``` -# Unlock a Door -$seam->locks->unlock_door($device_id); -$updated_device->properties->locked; // false +The timeout and polling interval, both in seconds, may be configured either +on the client or per request: -# Create an access code on a device -$access_code = $seam->access_codes->create( +```php +$seam = new Seam\Seam( + wait_for_action_attempt: ["timeout" => 30.0, "polling_interval" => 2.0] +); + +$seam->locks->unlock_door( device_id: $device_id, - code: "1234", - name: "Test Code" + wait_for_action_attempt: ["timeout" => 5.0] ); +``` -# Check the status of an access code -$access_code->status; // 'setting' (it will go to 'set' when active on the device) +### Setting a param to null -$seam->access_codes->delete($access_code->access_code_id); +The Seam API distinguishes three states for an updatable param: +omitted (leave the stored value unchanged), null (unset the stored value), +and a value (set it). + +PHP's `null` means omitted. +The SDK removes `null` params from the request entirely, +so passing `null` never unsets a value. +To unset a value, pass the `Seam\NullValue::NULL` sentinel, +which the SDK sends as JSON `null` in request bodies +and as an empty value in query strings: + +```php +use Seam\NullValue; + +// Leaves the name unchanged. +$seam->devices->update(device_id: $device_id, name: null); + +// Unsets the name. +$seam->devices->update(device_id: $device_id, name: NullValue::NULL); ``` +The other Seam SDKs spell the sentinel `NULL` and its type `Null`, +but those names are reserved in PHP, so both live on one enum: +`Seam\NullValue` is the type, and its single case `NullValue::NULL` +is the value to pass. + +Only pass `NullValue::NULL` for params the API documents as nullable. +Generated methods type nullable params as a union with the sentinel, +e.g. `string|NullValue|null`, so passing it anywhere else fails with a +`TypeError`. + ### Pagination Some Seam API endpoints that return lists of resources support pagination. @@ -115,7 +256,7 @@ $stored_data = json_decode( false ); -$params = $stored_data[0] ?? []; +$params = (array) ($stored_data[0] ?? []); $pagination = $stored_data[1] ?? (object) ["has_next_page" => false, "next_page_cursor" => null]; @@ -153,21 +294,302 @@ $pages = $seam->createPaginator( $connectedAccounts = $pages->flattenToArray(); ``` -## Installation +### Requests without a Workspace in scope + +Some endpoints are not scoped to a workspace. Use `SeamWithoutWorkspace` with a +personal access token to reach them. + +```php +// Set the SEAM_PERSONAL_ACCESS_TOKEN environment variable +$seam = new Seam\SeamWithoutWorkspace(); + +// Use the factory method +$seam = Seam\SeamWithoutWorkspace::from_personal_access_token( + "your-personal-access-token" +); + +// List workspaces authorized for this Personal Access Token +$workspaces = $seam->workspaces->list(); + +$workspace = $seam->workspaces->create( + name: "New Workspace", + connect_partner_name: "Your Company" +); +``` + +### Webhooks + +Seam delivers webhooks with [Svix]. Verify and parse an incoming request with +`SeamWebhook`, which returns the typed event. -To install the latest version of the automatically generated SDK, run: +[Svix]: https://www.svix.com/ -`composer require seamapi/seam` +```php +$webhook = new Seam\SeamWebhook($webhook_secret); + +try { + $event = $webhook->verify($request_body, $request_headers); + + print match (true) { + $event instanceof Seam\Resources\Event\AccessCodeCreated + => "Created access code {$event->access_code_id}", + $event::class === Seam\Resources\Event::class + => "Unknown event type {$event->event_type}", + default => "Received {$event->event_type}", + }; +} catch (Svix\Exception\WebhookVerificationException $error) { + http_response_code(401); +} catch (Seam\InvalidWebhookPayloadError $error) { + http_response_code(204); +} +``` -If you want to install our previous handwritten version, run: +### Advanced Usage + +#### Enum values + +Enum-valued response properties are strings, so they work with ordinary string +comparisons and remain forward-compatible when the API adds a value: + +```php +if ($action_attempt->status === "pending") { + // The action is still running. +} +``` -`composer require seamapi/seam:1.1` +The SDK also generates backed enums for autocomplete, discovery of known +values, and optional validation. Use the enum's `value` when comparing, or +`tryFrom()` to convert a response value: + +```php +use Seam\Resources\ActionAttempt\Status; +use Seam\Resources\Event\EventType; + +if ($action_attempt->status === Status::PENDING->value) { + // The action is still running. +} + +$status = Status::tryFrom($action_attempt->status); +$event_type = EventType::tryFrom($event->event_type); +``` + +`tryFrom()` returns `null` for a value introduced after the installed SDK was +released; the original response property still contains the raw string. Enum +properties also reference their companion enum in PHPDoc for IDE and static +analysis hints. + +#### Setting the endpoint + +The endpoint may be set with the `SEAM_ENDPOINT` environment variable, or +passed directly. + +```php +$seam = new Seam\Seam(endpoint: "https://example.com"); +``` + +#### Configuring the Guzzle client + +Pass any [Guzzle request option] with `guzzle_options`. They are merged into +the client the SDK builds, so the authorization and SDK headers are kept. + +[Guzzle request option]: https://docs.guzzlephp.org/en/stable/request-options.html + +```php +$seam = new Seam\Seam( + guzzle_options: [ + "headers" => ["X-Custom-Header" => "value"], + "proxy" => "http://localhost:8125", + ] +); +``` + +#### Setting the timeout + +Requests time out after 30 seconds by default, covering both connecting and +reading. Pass `timeout` in seconds to change it, or `0` to disable it. + +```php +$seam = new Seam\Seam(timeout: 60.0); +``` + +#### Retries + +By default, the SDK makes up to three attempts: the initial request and two +retries. Retries are limited to `GET`, `HEAD`, `OPTIONS`, `PUT`, and `DELETE` +requests that fail because of a transport error, timeout, HTTP 429 response, or +HTTP 5xx response. `POST` and `PATCH` requests are not retried. + +Retries use exponential backoff with jitter: approximately 200–240 ms before +the first retry and 400–480 ms before the second. A longer `Retry-After` header +is honored. The request timeout is reset for each attempt. + +```php +// Retry more times +$seam = new Seam\Seam(retries: 5); + +// Turn retries off +$seam = new Seam\Seam(retries: 0); +``` + +#### Using the underlying client + +`$seam->client` already carries the endpoint, authorization, error mapping, +and retries, so it can be used to reach an endpoint the SDK does not expose. +It wraps the [Guzzle] client and implements Guzzle's `ClientInterface`. + +[Guzzle]: https://docs.guzzlephp.org/ + +```php +$response = $seam->client->request("POST", "/devices/list", [ + "json" => (object) ["limit" => 10], +]); + +$devices = Seam\Http\Body::decode($response)->devices; +``` + +#### Overriding the client + +Pass an already configured Guzzle client. It carries its own endpoint and +authorization, so it cannot be combined with any option other than +`wait_for_action_attempt`. + +```php +$client = new GuzzleHttp\Client([ + "base_uri" => "https://connect.getseam.com", + "headers" => ["authorization" => "Bearer " . $api_key], +]); + +$seam = Seam\Seam::from_client($client); +``` + +The client is used exactly as given. It does not gain the SDK's error mapping +or retries, so an API error raises Guzzle's exception rather than +`Seam\HttpApiError`. To opt in, add the middleware yourself. + +#### Adding the Seam middleware to your own client + +`Seam\Http\ClientFactory::add_middleware` puts the error mapping and retry +middleware on a handler stack. Build the client with that stack, and with +`http_errors` disabled so the error middleware raises instead of Guzzle. + +```php +$handler = GuzzleHttp\HandlerStack::create(); + +Seam\Http\ClientFactory::add_middleware($handler); + +$client = new GuzzleHttp\Client([ + "base_uri" => "https://connect.getseam.com", + "headers" => ["authorization" => "Bearer " . $api_key], + "handler" => $handler, + "http_errors" => false, +]); + +$seam = Seam\Seam::from_client($client); +``` + +Pass `retries` to change how many times a failed request is retried, or `0` +to disable them: + +```php +Seam\Http\ClientFactory::add_middleware($handler, retries: 0); +``` + +Add it once per stack: applying it twice stacks two sets of retries. + +#### Serializing URL search params + +The Seam API parses URL search params as complex types. +If you call it with your own HTTP client, +`Seam\StrictUrlSearchParamsSerializer` is exported for that purpose. +The `_strict=true` param is added to any non-empty query +so the Seam API uses strict, schema-aware parsing. +A query with no serializable params remains empty. + +```php +use Seam\StrictUrlSearchParamsSerializer; + +$query = StrictUrlSearchParamsSerializer::serialize([ + "device_ids" => ["device1", "device2"], +]); + +$response = file_get_contents( + "https://connect.getseam.com/devices/list?{$query}", + context: stream_context_create([ + "http" => ["header" => "Authorization: Bearer your-api-key"], + ]), +); +``` + +The serialization defines the name and value of each search param, +where every value is a string. +`Seam\UrlSearchParams` holds those pairs and renders the query string, +as [URLSearchParams] does for the [reference implementation]: + +```php +use Seam\StrictUrlSearchParamsSerializer; +use Seam\UrlSearchParams; + +$search_params = new UrlSearchParams(); + +StrictUrlSearchParamsSerializer::update($search_params, [ + "device_ids" => ["device1", "device2"], +]); + +iterator_to_array($search_params); +// => [["device_ids", "device1"], ["device_ids", "device2"], ["_strict", "true"]] + +(string) $search_params; +// => 'device_ids=device1&device_ids=device2&_strict=true' +``` + +Pass either the query string or the pairs to your HTTP client. +A client may percent-encode a few characters differently than +`URLSearchParams` does, e.g. Guzzle escapes `*` and leaves `~` unescaped, +which the Seam API reads as the same params either way. + +A param set to `null` is omitted, +while a param set to `NullValue::NULL` is serialized to an empty value, +which the Seam API reads as null, +as described in [Setting a param to null](#setting-a-param-to-null). +A param that cannot be represented raises a `Seam\UnserializableParamError`. + +The Seam API parses these params with the corresponding [parser]. + +[URLSearchParams]: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams +[reference implementation]: https://github.com/seamapi/url-search-params-serializer +[parser]: https://github.com/seamapi/url-search-params-parser + +#### Errors + +Every exception the SDK raises implements `Seam\SeamException`, so it can be +caught as a group. An API error is a `Seam\HttpApiError` carrying +`getErrorCode()`, `getStatusCode()` and `getRequestId()`, with +`Seam\HttpUnauthorizedError` and `Seam\HttpInvalidInputError` as the two +specific cases worth catching on their own. + +```php +use Seam\HttpApiError; +use Seam\HttpInvalidInputError; + +try { + $seam->devices->get(device_id: $device_id); +} catch (HttpInvalidInputError $error) { + print_r($error->getValidationErrorMessages("device_id")); +} catch (HttpApiError $error) { + print $error->getErrorCode(); +} +``` + +An error response that is not shaped like a Seam error, such as a gateway +returning HTML, raises the underlying Guzzle exception instead. A successful +response that does not carry the resource the endpoint returns raises +`Seam\InvalidResponseError`, with `getPath()` and `getKey()`. ## Development and Testing ### Quickstart -Install [PHP](https://www.php.net/) 8.0 or later, +Install [PHP](https://www.php.net/) 8.2 or later, [Composer](https://getcomposer.org/) and [Node.js](https://nodejs.org/), then run @@ -186,19 +608,30 @@ View them with $ composer run-script --list ``` -| Task | Command | -| ----------------- | ------------------ | -| Run the tests | `composer test` | -| Lint | `composer lint` | -| Format | `npm run format` | -| Build the package | `composer build` | -| Generate the SDK | `npm run generate` | +| Task | Command | +| ---------------------- | ------------------ | +| Run the tests | `composer test` | +| Lint and analyze types | `composer lint` | +| Format | `npm run format` | +| Build the package | `composer build` | +| Generate the SDK | `npm run generate` | Formatting is handled by [Prettier](https://prettier.io/) via [@prettier/plugin-php](https://github.com/prettier/plugin-php), so PHP, TypeScript, JSON, YAML and Markdown are all formatted by `npm run format`. +### Source code + +The [source code] is hosted on GitHub. +Clone the project with + +``` +$ git clone git@github.com:seamapi/php.git +``` + +[source code]: https://github.com/seamapi/php + ### Running Tests Run the full suite with @@ -215,10 +648,20 @@ $ composer test -- tests/MyTest.php PHPUnit is configured in `phpunit.xml.dist`. +Static analysis is handled by [Psalm](https://psalm.dev/), configured in +`psalm.xml` and run as part of `composer lint`. The generated sources under +`src/Resources` and `src/Routes` are excluded, since analyzing them would only +create pressure to change the generator. + ### Requirements -This package supports PHP 8.0 and later. -Continuous integration exercises both ends of that range, PHP 8.0 and 8.5. +This package supports PHP 8.2 and later. +Continuous integration exercises every supported version, PHP 8.2 through 8.5. + +The test suite runs against [@seamapi/fake-seam-connect], which is started +automatically for each test, so `npm install` must have been run first. + +[@seamapi/fake-seam-connect]: https://github.com/seamapi/fake-seam-connect ### Publishing @@ -234,7 +677,7 @@ and dispatches the [Version](.github/workflows/version.yml) workflow. Run the [Version](.github/workflows/version.yml) workflow with the version to cut. It runs `npm version`, which bumps the `version` field in `package.json`, -injects that version into `Seam\Utils\PackageVersion`, creates a signed `v*` +injects that version into `Seam\Version`, creates a signed `v*` git tag and pushes it. Pushing the tag triggers the [Publish](.github/workflows/publish.yml) workflow, and [Packagist](https://packagist.org/packages/seamapi/seam) @@ -245,7 +688,7 @@ picks up the new tag from its GitHub webhook. > step in between. > This repository therefore keeps the version in `package.json`, which is a > development manifest that is not published, and injects it into the -> `Seam\Utils\PackageVersion::VERSION` constant used for the +> `Seam\Version::VERSION` constant used for the > `seam-sdk-version` header. > > The injection runs from `version.ts`, wired to the `version` lifecycle @@ -256,3 +699,56 @@ picks up the new tag from its GitHub webhook. Development files are kept out of the published package with `export-ignore` rules in `.gitattributes`, which `git archive` honours when GitHub builds the archives Composer downloads as `dist`. + +## GitHub Actions + +_GitHub Actions should already be configured: this section is for reference only._ + +Publishing is handled by [Packagist], which reads new versions from the git +tags this repository pushes, so no registry token is needed. + +[Packagist]: https://packagist.org/packages/seamapi/seam + +### Secrets for Optional GitHub Actions + +The version, format, generate, and semantic-release GitHub actions +require a user with write access to the repository. +Set these additional secrets to enable the action: + +- `GH_TOKEN`: A personal access token for the user. +- `GIT_USER_NAME`: The GitHub user's real name. +- `GIT_USER_EMAIL`: The GitHub user's email. +- `GPG_PRIVATE_KEY`: The GitHub user's [GPG private key]. +- `GPG_PASSPHRASE`: The GitHub user's GPG passphrase. + +[GPG private key]: https://github.com/marketplace/actions/import-gpg#prerequisites + +## Contributing + +Please submit and comment on bug reports and feature requests. + +To submit a patch: + +1. Fork it (https://github.com/seamapi/php/fork). +2. Create your feature branch (`git checkout -b my-new-feature`). +3. Make changes. +4. Commit your changes (`git commit -am 'Add some feature'`). +5. Push to the branch (`git push origin my-new-feature`). +6. Create a new Pull Request. + +## License + +This PHP package is licensed under the MIT license. + +## Warranty + +This software is provided by the copyright holders and contributors "as is" and +any express or implied warranties, including, but not limited to, the implied +warranties of merchantability and fitness for a particular purpose are +disclaimed. In no event shall the copyright holder or contributors be liable for +any direct, indirect, incidental, special, exemplary, or consequential damages +(including, but not limited to, procurement of substitute goods or services; +loss of use, data, or profits; or business interruption) however caused and on +any theory of liability, whether in contract, strict liability, or tort +(including negligence or otherwise) arising in any way out of the use of this +software, even if advised of the possibility of such damage. diff --git a/codegen/layouts/partials/client-class.hbs b/codegen/layouts/partials/client-class.hbs index a205fc3f..8a36f141 100644 --- a/codegen/layouts/partials/client-class.hbs +++ b/codegen/layouts/partials/client-class.hbs @@ -1,6 +1,11 @@ class {{clientName}}Client { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; {{#if hasChildClients}} {{#each childClients}} public {{clientName}}Client ${{namespace}}; @@ -8,18 +13,19 @@ class {{clientName}}Client {{else}} {{/if}} - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; {{#each childClients}} - $this->{{namespace}} = new {{clientName}}Client($seam); + $this->{{namespace}} = new {{clientName}}Client($client, $defaults); {{/each}} } {{#each methods}} {{> route-method}} {{/each}} -{{#if isActionAttempts}} -{{> poll-until-ready}} -{{/if}} } diff --git a/codegen/layouts/partials/poll-until-ready.hbs b/codegen/layouts/partials/poll-until-ready.hbs deleted file mode 100644 index 87ce726d..00000000 --- a/codegen/layouts/partials/poll-until-ready.hbs +++ /dev/null @@ -1,24 +0,0 @@ - public function poll_until_ready(string $action_attempt_id, float $timeout = 20.0): ActionAttempt - { - $seam = $this->seam; - $time_waiting = 0.0; - $polling_interval = 0.4; - $action_attempt = $seam->action_attempts->get($action_attempt_id); - - while ($action_attempt->status == 'pending') { - $action_attempt = $seam->action_attempts->get( - $action_attempt->action_attempt_id - ); - if ($time_waiting > $timeout) { - throw new ActionAttemptTimeoutError($action_attempt, $timeout); - } - $time_waiting += $polling_interval; - usleep($polling_interval * 1000000); - } - - if ($action_attempt->status == 'error') { - throw new ActionAttemptFailedError($action_attempt); - } - - return $action_attempt; - } diff --git a/codegen/layouts/partials/route-method.hbs b/codegen/layouts/partials/route-method.hbs index 9c73dfd9..0552974d 100644 --- a/codegen/layouts/partials/route-method.hbs +++ b/codegen/layouts/partials/route-method.hbs @@ -1,31 +1,38 @@ {{{methodPhpDoc this}}} public function {{methodName}}({{{signatureParams}}}): {{returnType}} { +{{#if requiresAtLeastOneParameter}} + if ({{#each atLeastOneParameterNames}}{{#unless @first}} && {{/unless}}${{this}} === null{{/each}}) { + throw new \InvalidArgumentException("At least one parameter is required for {{path}}"); + } +{{/if}} {{#if hasParams}} $request_payload = []; -{{#each paramNames}} - if (${{this}} !== null) { - $request_payload["{{this}}"] = ${{this}}; +{{#each parameters}} +{{#if required}} + $request_payload["{{name}}"] = ${{name}}; +{{else}} + if (${{name}} !== null) { + $request_payload["{{name}}"] = ${{name}}; } +{{/if}} {{/each}} {{/if}} - {{#unless returnsVoid}}$res = {{/unless}}$this->seam->request( - "POST", + {{#unless returnsVoid}}$res = Body::decode({{/unless}}$this->client->request( + "{{httpMethod}}", "{{path}}", {{#if hasParams}} - json: (object) $request_payload, + ["{{#if usesQueryParams}}query{{else}}json{{/if}}" => {{#unless usesQueryParams}}(object) {{/unless}}$request_payload], {{/if}} - ); + ){{#unless returnsVoid}}){{/unless}}; {{#if usesActionAttempt}} - if (!$wait_for_action_attempt) { - return {{returnResource}}::from_json($res->{{returnPath}}); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready($res->action_attempt->action_attempt_id); - - return $action_attempt; + return ResolveActionAttempt::resolve_action_attempt( + {{returnResource}}::from_json(Body::read($res, "{{returnPath}}", "{{path}}")), + $this->client, + $wait_for_action_attempt ?? $this->defaults["wait_for_action_attempt"] + ); {{else}} {{#if usesOnResponse}} @@ -36,9 +43,9 @@ {{#unless returnsVoid}} {{#if isArrayResponse}} - return array_map(fn ($r) => {{returnResource}}::from_json($r), $res->{{returnPath}}); + return array_map(fn ($r) => {{returnResource}}::from_json($r), Body::read_list($res, "{{returnPath}}", "{{path}}")); {{else}} - return {{returnResource}}::from_json($res->{{returnPath}}); + return {{returnResource}}::from_json(Body::read($res, "{{returnPath}}", "{{path}}")); {{/if}} {{/unless}} {{/if}} diff --git a/codegen/layouts/resource.hbs b/codegen/layouts/resource.hbs index a7d7f4ed..f7f3a95a 100644 --- a/codegen/layouts/resource.hbs +++ b/codegen/layouts/resource.hbs @@ -1,34 +1,75 @@ {{factory.discriminant}} ?? null) + ? {{factory.enumType}}::tryFrom($json->{{factory.discriminant}}) + : null; + + return match ($discriminant) { +{{#each factory.variants}} + {{enumCase}} => {{className}}::from_json($json), +{{/each}} + default => new self( {{#each fromJsonProps}} - {{{this}}} + {{{this}}} {{/each}} - ); - } + ), + }; +{{else}} + return new self( +{{#each fromJsonProps}} + {{{this}}} +{{/each}} + ); +{{/if}} + } - public function __construct( + public function __construct( {{#each constructorParams}} {{#if (hasPhpDoc this)}} {{{propertyPhpDoc this}}} {{/if}} - {{{declaration}}} + {{{declaration}}} +{{/each}} + ) { +{{#if parentArgs}} + parent::__construct( +{{#each parentArgs}} + {{{this}}} +{{/each}} + ); +{{/if}} + } + } + +{{/each}} +{{#each enums}} + enum {{enumName}}: string + { +{{#each cases}} +{{#if description}} + /** + * {{description}} + */ +{{/if}} + case {{name}} = {{{value}}}; {{/each}} - ) { } + +{{/each}} } {{/each}} diff --git a/codegen/layouts/seam-client.hbs b/codegen/layouts/seam-client.hbs index d39a7c01..4e26a88f 100644 --- a/codegen/layouts/seam-client.hbs +++ b/codegen/layouts/seam-client.hbs @@ -5,96 +5,158 @@ namespace Seam; {{#each useStatements}} use {{this}}; {{/each}} -use Seam\Utils\PackageVersion; -use GuzzleHttp\Client as HTTPClient; -use \Exception as Exception; -use Seam\HttpApiError; -use Seam\HttpUnauthorizedError; -use Seam\HttpInvalidInputError; - -define('LTS_VERSION', '1.0.0'); - -class SeamClient +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; +use Seam\Http\ClientFactory; +use Seam\Http\SerializingClient; + +/** + * Client for the Seam API. + * + * Authenticate with an API key, which is scoped to a single workspace, or with + * a personal access token together with the id of the workspace to act on. + * When neither is given, the SEAM_API_KEY or SEAM_PERSONAL_ACCESS_TOKEN and + * SEAM_WORKSPACE_ID environment variables are used. + * + * @see https://docs.seam.co/ + */ +class Seam { {{#each parentClients}} public {{clientName}}Client ${{namespace}}; {{/each}} - public string $api_key; - public HTTPClient $client; - public string $ltsVersion = LTS_VERSION; - + /** + * The client this instance makes its requests with. + * + * Query params given as a map and NullValue::NULL sentinels in JSON + * bodies are serialized with the Seam standard before the request goes + * out; see Seam\Http\SerializingClient. + */ + public ClientInterface $client; + + /** + * Default request options applied to every call, currently just + * wait_for_action_attempt. + * + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + public array $defaults; + + /** + * @param bool|array{timeout?: float, polling_interval?: float}|null $wait_for_action_attempt Whether to wait for action attempts to finish, optionally with timeout and polling_interval in seconds. Defaults to true. + * @param array $guzzle_options Options merged into the underlying Guzzle client, e.g. headers or proxy. + * @param int|null $retries How many times to retry a failed request. Defaults to 2; pass 0 to disable. + * @param float|null $timeout Request timeout in seconds, covering connecting and reading. Defaults to 30; pass 0 to disable. + * @param ClientInterface|null $client A preconfigured Guzzle client, used as is. It carries its own endpoint and authorization, so it cannot be combined with any option other than wait_for_action_attempt. + */ public function __construct( - $api_key = null, - $endpoint = "https://connect.getseam.com", - $throw_http_errors = false - ) { - $this->api_key = $api_key ?: (getenv('SEAM_API_KEY') ?: null); - $seam_sdk_version = PackageVersion::get(); - $this->client = new HTTPClient([ - "base_uri" => $endpoint, - "timeout" => 60.0, - "headers" => [ - "Authorization" => "Bearer " . $this->api_key, - "User-Agent" => "Seam PHP Client ". $seam_sdk_version, - "seam-sdk-name" => "seamapi/php", - "seam-sdk-version" => $seam_sdk_version, - "seam-lts-version" => $this->ltsVersion - ], - "http_errors" => $throw_http_errors, - ]); -{{#each parentClients}} - $this->{{namespace}} = new {{clientName}}Client($this); -{{/each}} - } - - public function request( - $method, - $path, - $json = null, - $query = null, + ?string $api_key = null, + ?string $personal_access_token = null, + ?string $workspace_id = null, + ?string $endpoint = null, + bool|array|null $wait_for_action_attempt = null, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null, + ?ClientInterface $client = null ) { - $options = [ - "json" => $json, - "query" => $query, + $this->defaults = [ + "wait_for_action_attempt" => $wait_for_action_attempt ?? true, ]; - $options = array_filter($options, fn($option) => $option !== null); - - $response = $this->client->request($method, $path, $options); - $status_code = $response->getStatusCode(); - $request_id = $response->getHeaderLine("seam-request-id"); - $res_json = null; - try { - $res_json = json_decode($response->getBody()); - } catch (Exception $ignoreError) { - } + // A client carries its own endpoint and authorization, so no option + // that would configure one can be combined with it. + Options::check_client_options($client, [ + "api_key" => $api_key, + "personal_access_token" => $personal_access_token, + "workspace_id" => $workspace_id, + "endpoint" => $endpoint, + "guzzle_options" => $guzzle_options, + "retries" => $retries, + "timeout" => $timeout, + ]); - if ($status_code >= 400) { - if ($status_code === 401) { - throw new HttpUnauthorizedError($request_id); - } + $this->client = SerializingClient::wrap($client ?? ClientFactory::create( + Options::get_endpoint($endpoint), + Auth::get_auth_headers($api_key, $personal_access_token, $workspace_id), + $guzzle_options, + $retries, + $timeout + )); - if (($res_json->error ?? null) != null) { - if ($res_json->error->type === 'invalid_input') { - throw new HttpInvalidInputError($res_json->error, $status_code, $request_id); - } +{{#each parentClients}} + $this->{{namespace}} = new {{clientName}}Client($this->client, $this->defaults); +{{/each}} + } - throw new HttpApiError($res_json->error, $status_code, $request_id); - } + /** + * Creates a client authorized with an API key. + */ + public static function from_api_key( + string $api_key, + ?string $endpoint = null, + bool|array|null $wait_for_action_attempt = null, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null + ): static { + return new static( + api_key: $api_key, + endpoint: $endpoint, + wait_for_action_attempt: $wait_for_action_attempt, + guzzle_options: $guzzle_options, + retries: $retries, + timeout: $timeout + ); + } - throw \GuzzleHttp\Exception\RequestException::create( - new \GuzzleHttp\Psr7\Request($method, $path), - $response - ); - } + /** + * Creates a client authorized with a personal access token, scoped to the + * given workspace. + */ + public static function from_personal_access_token( + string $personal_access_token, + string $workspace_id, + ?string $endpoint = null, + bool|array|null $wait_for_action_attempt = null, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null + ): static { + return new static( + personal_access_token: $personal_access_token, + workspace_id: $workspace_id, + endpoint: $endpoint, + wait_for_action_attempt: $wait_for_action_attempt, + retries: $retries, + timeout: $timeout, + guzzle_options: $guzzle_options + ); + } - return $res_json; + /** + * Creates a client from a preconfigured Guzzle client. + */ + public static function from_client( + ClientInterface $client, + bool|array|null $wait_for_action_attempt = null + ): static { + return new static( + client: $client, + wait_for_action_attempt: $wait_for_action_attempt + ); } - public function createPaginator($request, $params = []) + /** + * Creates a paginator for a list endpoint. + * + * @param callable $request Invokes the list method with a params array, e.g. fn($params) => $seam->devices->list(...$params) + * @param array $params + */ + public function createPaginator(callable $request, array $params = []): Paginator { - return new Paginator($request, $params); + return new Paginator($request, $params); } } diff --git a/codegen/lib/class-model.ts b/codegen/lib/class-model.ts index 93356d5c..187754c6 100644 --- a/codegen/lib/class-model.ts +++ b/codegen/lib/class-model.ts @@ -4,18 +4,22 @@ export interface PhpClientMethodParameter { name: string type: string + phpDocType: string description: string - required?: boolean | undefined + isOptional: boolean + isNullable: boolean position?: number | undefined } export interface PhpClientMethod { methodName: string + httpMethod: string path: string description: string responseDescription: string isDeprecated: boolean deprecationMessage: string + requiresAtLeastOneParameter: boolean parameters: PhpClientMethodParameter[] returnResource: string returnPath: string @@ -43,4 +47,4 @@ export const sortPhpClientMethodParameters = ( [...parameters].sort((a, b) => getParameterRank(a) - getParameterRank(b)) const getParameterRank = (parameter: PhpClientMethodParameter): number => - parameter.position ?? ((parameter.required ?? false) ? 1000 : 9999) + parameter.position ?? (parameter.isOptional ? 9999 : 1000) diff --git a/codegen/lib/handlebars-helpers.ts b/codegen/lib/handlebars-helpers.ts index 263be9ce..316620f6 100644 --- a/codegen/lib/handlebars-helpers.ts +++ b/codegen/lib/handlebars-helpers.ts @@ -4,28 +4,48 @@ export interface DeprecatedPhpDocContext { description: string isDeprecated: boolean deprecationMessage: string + phpDocType?: string } export interface MethodPhpDocContext extends DeprecatedPhpDocContext { returnType: string responseDescription: string - parameters: Array<{ name: string; type: string; description: string }> + // The endpoint parameters plus the SDK level ones, e.g. + // wait_for_action_attempt, so editors surface all of them. + documentedParameters: Array<{ + name: string + type: string + description: string + }> } +// Resource classes and their properties are emitted inside a braced namespace +// block, so both sit one level deeper than a docblock at file scope would. export const resourcePhpDoc = (context: DeprecatedPhpDocContext): string => - createPhpDoc(context.description, deprecatedTag(context)) + createPhpDoc(context.description, deprecatedTag(context), ' ') export const hasPhpDoc = (context: DeprecatedPhpDocContext): boolean => - context.description.trim() !== '' || context.isDeprecated + context.description.trim() !== '' || + context.isDeprecated || + (context.phpDocType != null && context.phpDocType !== '') export const propertyPhpDoc = (context: DeprecatedPhpDocContext): string => - createPhpDoc(context.description, deprecatedTag(context), ' ') + createPhpDoc( + context.description, + [ + ...(context.phpDocType == null || context.phpDocType === '' + ? [] + : [`@var ${context.phpDocType}`]), + ...deprecatedTag(context), + ], + ' ', + ) export const methodPhpDoc = (context: MethodPhpDocContext): string => createPhpDoc( context.description, [ - ...context.parameters.map( + ...context.documentedParameters.map( (parameter) => `@param ${parameter.type} $${parameter.name}${parameter.description === '' ? '' : ` ${parameter.description}`}`, ), diff --git a/codegen/lib/layouts/resource.ts b/codegen/lib/layouts/resource.ts index d0400061..ee9d6284 100644 --- a/codegen/lib/layouts/resource.ts +++ b/codegen/lib/layouts/resource.ts @@ -1,15 +1,9 @@ -// Builds the template context for resource files (src/Resources/{Name}.php): -// the resource class followed by the local classes for its object properties. -// Each class contributes its from_json body lines and constructor parameter -// lines. -// -// The blueprint does not track which resource properties are required, so -// every property is optional: from_json falls back to null for missing values -// and the constructor parameters are nullable. +// Builds the template context for generated resource files. import type { ResourceClassProperty, ResourceClassSchema, + ResourceEnumSchema, ResourceSchema, } from '../resource-model.js' @@ -18,19 +12,41 @@ export interface ClassLayoutContext { description: string isDeprecated: boolean deprecationMessage: string + isFinal: boolean + extendsName: string + factory?: FactoryLayoutContext fromJsonProps: string[] constructorParams: ConstructorParamLayoutContext[] + parentArgs: string[] +} + +export interface FactoryLayoutContext { + discriminant: string + enumType: string + variants: Array<{ enumCase: string; className: string }> } export interface ConstructorParamLayoutContext { declaration: string + phpDocType: string description: string isDeprecated: boolean deprecationMessage: string } -export interface ResourceLayoutContext { +export interface EnumLayoutContext { + enumName: string + cases: Array<{ name: string; value: string; description: string }> +} + +export interface NamespaceLayoutContext { + namespace: string classes: ClassLayoutContext[] + enums: EnumLayoutContext[] +} + +export interface ResourceLayoutContext { + namespaces: NamespaceLayoutContext[] } const generateFromJsonProp = (property: ResourceClassProperty): string => { @@ -43,6 +59,9 @@ const generateFromJsonProp = (property: ResourceClassProperty): string => { case 'listReference': return `${name}: array_map(fn ($${name[0]}) => ${property.referenceName}::from_json($${name[0]}), $json->${name} ?? []),` + case 'record': + return `${name}: $json->${name} ?? null,` + case 'value': return `${name}: $json->${name} ?? null,` } @@ -50,27 +69,41 @@ const generateFromJsonProp = (property: ResourceClassProperty): string => { const generateConstructorParam = ( property: ResourceClassProperty, + promote: boolean, ): ConstructorParamLayoutContext => { - let declaration: string + let type: string + let phpDocType = '' + const defaultValue = property.isOptional ? ' = null' : '' + switch (property.kind) { case 'objectReference': - declaration = `public ${property.referenceName}|null $${property.name},` + type = `${property.referenceName}|null` break case 'listReference': - declaration = `public array $${property.name},` + type = `array${property.isOptional ? '|null' : ''}` + phpDocType = `list<${property.referenceName}>${property.isOptional ? '|null' : ''}` + break + + case 'record': + type = `${property.phpType}|null` + phpDocType = `${property.phpDocType}|null` break case 'value': { - const { phpType } = property - const nullSuffix = phpType === 'mixed' ? '' : '|null' - declaration = `public ${phpType}${nullSuffix} $${property.name},` + const nullSuffix = property.phpType === 'mixed' ? '' : '|null' + type = `${property.phpType}${nullSuffix}` + phpDocType = + property.phpDocType === '' || property.phpDocType === property.phpType + ? '' + : `${property.phpDocType}|null` break } } return { - declaration, + declaration: `${promote ? 'public ' : ''}${type} $${property.name}${defaultValue},`, + phpDocType, description: property.description, isDeprecated: property.isDeprecated, deprecationMessage: property.deprecationMessage, @@ -80,24 +113,69 @@ const generateConstructorParam = ( const getClassLayoutContext = ( schema: ResourceClassSchema, ): ClassLayoutContext => { - const sorted = [...schema.properties].sort((a, b) => - a.name.localeCompare(b.name), + const inheritedNames = new Set( + schema.inheritedProperties.map(({ name }) => name), ) + const properties = sortRequiredFirst([ + ...schema.inheritedProperties, + ...schema.properties, + ]) return { className: schema.name, description: schema.description, isDeprecated: schema.isDeprecated, deprecationMessage: schema.deprecationMessage, - fromJsonProps: sorted.map(generateFromJsonProp), - constructorParams: sorted.map(generateConstructorParam), + isFinal: schema.isFinal, + extendsName: schema.extendsName, + ...(schema.factory == null ? {} : { factory: schema.factory }), + fromJsonProps: properties.map(generateFromJsonProp), + constructorParams: properties.map((property) => + generateConstructorParam(property, !inheritedNames.has(property.name)), + ), + parentArgs: schema.inheritedProperties.map( + ({ name }) => `${name}: $${name},`, + ), } } +const getEnumLayoutContext = ( + schema: ResourceEnumSchema, +): EnumLayoutContext => ({ + enumName: schema.name, + cases: schema.cases.map((enumCase) => ({ + ...enumCase, + value: JSON.stringify(enumCase.value), + })), +}) + +const sortRequiredFirst = ( + properties: ResourceClassProperty[], +): ResourceClassProperty[] => + [...properties].sort( + (a, b) => + Number(a.isOptional) - Number(b.isOptional) || + a.name.localeCompare(b.name), + ) + export const setResourceLayoutContext = ( resource: ResourceSchema, -): ResourceLayoutContext => ({ - classes: [resource.resourceClass, ...resource.localClasses].map( - getClassLayoutContext, - ), -}) +): ResourceLayoutContext => { + const namespaces = new Map() + + for (const declaration of resource.declarations) { + let context = namespaces.get(declaration.namespace) + if (context == null) { + context = { namespace: declaration.namespace, classes: [], enums: [] } + namespaces.set(declaration.namespace, context) + } + + if (declaration.kind === 'class') { + context.classes.push(getClassLayoutContext(declaration)) + } else { + context.enums.push(getEnumLayoutContext(declaration)) + } + } + + return { namespaces: [...namespaces.values()] } +} diff --git a/codegen/lib/layouts/route.ts b/codegen/lib/layouts/route.ts index eb32944a..f58c5ef9 100644 --- a/codegen/lib/layouts/route.ts +++ b/codegen/lib/layouts/route.ts @@ -8,25 +8,40 @@ import { sortPhpClientMethodParameters, } from '../class-model.js' -const seamClientClass = 'Seam\\SeamClient' +const clientInterfaceClass = 'GuzzleHttp\\ClientInterface' +const bodyClass = 'Seam\\Http\\Body' +const nullValueClass = 'Seam\\NullValue' +const resolveActionAttemptClass = 'Seam\\Http\\ResolveActionAttempt' const resourcesNamespace = 'Seam\\Resources' -const actionAttemptErrorClasses = [ - 'Seam\\ActionAttemptFailedError', - 'Seam\\ActionAttemptTimeoutError', -] export interface MethodLayoutContext { methodName: string + httpMethod: string + usesQueryParams: boolean description: string responseDescription: string isDeprecated: boolean deprecationMessage: string - parameters: Array<{ name: string; type: string; description: string }> + parameters: Array<{ + name: string + type: string + phpDocType: string + description: string + required: boolean + isOptional: boolean + isNullable: boolean + }> + documentedParameters: Array<{ + name: string + type: string + description: string + }> path: string returnType: string hasParams: boolean + requiresAtLeastOneParameter: boolean + atLeastOneParameterNames: string[] signatureParams: string - paramNames: string[] usesActionAttempt: boolean usesOnResponse: boolean returnsVoid: boolean @@ -40,21 +55,55 @@ export interface ClientLayoutContext { hasChildClients: boolean childClients: Array<{ clientName: string; namespace: string }> methods: MethodLayoutContext[] - isActionAttempts: boolean } export interface RouteLayoutContext extends ClientLayoutContext { useStatements: string[] } +const paginationParameters = new Set(['limit', 'page_cursor']) + +const waitForActionAttemptParameter = { + name: 'wait_for_action_attempt', + type: 'bool|array|null', + description: + 'Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client.', + required: false, +} + +const onResponseParameter = { + name: 'on_response', + type: 'callable|null', + description: + 'Called with the raw response envelope, used by the paginator to read the pagination metadata.', + required: false, +} + +// A nullable param accepts the NullValue::NULL sentinel, which sends an +// explicit null to unset a value. A merely optional param does not: optional +// means omit by passing null, and sending null there would unset a value +// instead. Optionality composes with nullability rather than replacing it. +const getParameterPhpType = (parameter: { + type: string + isOptional: boolean + isNullable: boolean +}): string => { + const { type, isOptional, isNullable } = parameter + if (type === 'mixed') return type + if (isNullable) return `${type}|NullValue${isOptional ? '|null' : ''}` + if (!isOptional) return type + return type.includes('|') ? `${type}|null` : `?${type}` +} + const getMethodLayoutContext = ( method: PhpClientMethod, - clientName: string, ): MethodLayoutContext => { const { methodName, path, parameters, returnResource, returnPath } = method + // A method returning a list of action attempts is an ordinary list + // endpoint: only a single returned attempt is resolved. const usesActionAttempt = - returnResource === 'ActionAttempt' && clientName !== 'ActionAttempts' + returnResource === 'ActionAttempt' && !method.isArrayResponse const usesOnResponse = parameters.some((p) => p.name === 'page_cursor') && methodName === 'list' const returnsVoid = returnResource === '' @@ -65,32 +114,67 @@ const getMethodLayoutContext = ( : 'void' const sortedParameters = sortPhpClientMethodParameters(parameters) - const signatureParams = sortedParameters .map( (p) => - `${!(p.required ?? false) && p.type !== 'mixed' ? '?' : ''}${p.type} $${p.name}${(p.required ?? false) ? '' : ' = null'}`, + `${getParameterPhpType(p)} $${p.name}${p.isOptional ? ' = null' : ''}`, + ) + .concat( + usesActionAttempt + ? ['bool|array|null $wait_for_action_attempt = null'] + : [], ) - .concat(usesActionAttempt ? ['bool $wait_for_action_attempt = true'] : []) .concat(usesOnResponse ? ['?callable $on_response = null'] : []) .join(', ') + const atLeastOneParameterNames = sortedParameters + .map(({ name }) => name) + .filter((name) => !paginationParameters.has(name)) + + const endpointParameters = sortedParameters.map( + ({ name, type, phpDocType, description, isOptional, isNullable }) => ({ + name, + type, + phpDocType, + description, + required: !isOptional, + isOptional, + isNullable, + }), + ) + const documentedEndpointParameters = endpointParameters.map( + ({ name, type, phpDocType, description, isNullable }) => ({ + name, + type: + isNullable && type !== 'mixed' ? `${phpDocType}|NullValue` : phpDocType, + description, + }), + ) + return { methodName, + httpMethod: method.httpMethod, + usesQueryParams: ['GET', 'DELETE'].includes(method.httpMethod), description: method.description, responseDescription: method.responseDescription, isDeprecated: method.isDeprecated, deprecationMessage: method.deprecationMessage, - parameters: sortedParameters.map(({ name, type, description }) => ({ - name, - type, - description, - })), + // The request payload is built from the endpoint parameters alone. + parameters: endpointParameters, + // The SDK level parameters are documented alongside them so editors + // surface all of them, but they never reach the payload. + documentedParameters: [ + ...documentedEndpointParameters, + ...(usesActionAttempt ? [waitForActionAttemptParameter] : []), + ...(usesOnResponse ? [onResponseParameter] : []), + ], path, returnType, hasParams: parameters.length > 0, + requiresAtLeastOneParameter: + method.requiresAtLeastOneParameter && atLeastOneParameterNames.length > 0, + atLeastOneParameterNames, signatureParams, - paramNames: sortedParameters.map((p) => p.name), usesActionAttempt, usesOnResponse, returnsVoid, @@ -100,44 +184,47 @@ const getMethodLayoutContext = ( } } -// Child clients live in the same namespace as their parent, so only the -// SeamClient, the resource classes returned by the methods, and the action -// attempt errors thrown by poll_until_ready need importing. -const getUseStatements = ( - client: PhpClient, - isActionAttempts: boolean, -): string[] => { +// Child clients live in the same namespace as their parent, so only the HTTP +// client, the action attempt resolver, and the resource classes returned by +// the methods need importing. +const getUseStatements = (client: PhpClient): string[] => { const resourceNames = new Set( client.methods .map((m) => m.returnResource) .filter((resourceName) => resourceName !== ''), ) - if (isActionAttempts) resourceNames.add('ActionAttempt') + const usesActionAttempt = client.methods.some( + (m) => m.returnResource === 'ActionAttempt' && !m.isArrayResponse, + ) + + // Void endpoints never read the response, so they do not decode it. + const readsBody = client.methods.some((m) => m.returnResource !== '') + + // Only nullable params reference the null sentinel type; importing it + // elsewhere would trip the unused-import lint. + const usesNullValue = client.methods.some((m) => + m.parameters.some((p) => p.isNullable && p.type !== 'mixed'), + ) return [ - seamClientClass, + clientInterfaceClass, + ...(readsBody ? [bodyClass] : []), + ...(usesNullValue ? [nullValueClass] : []), + ...(usesActionAttempt ? [resolveActionAttemptClass] : []), ...[...resourceNames].map((name) => `${resourcesNamespace}\\${name}`), - ...(isActionAttempts ? actionAttemptErrorClasses : []), ].sort((a, b) => a.localeCompare(b)) } export const setRouteLayoutContext = ( client: PhpClient, -): RouteLayoutContext => { - const isActionAttempts = client.clientName === 'ActionAttempts' - - return { - useStatements: getUseStatements(client, isActionAttempts), - clientName: client.clientName, - hasChildClients: client.childClientIdentifiers.length > 0, - childClients: client.childClientIdentifiers.map((i) => ({ - clientName: i.clientName, - namespace: i.namespace, - })), - methods: client.methods.map((m) => - getMethodLayoutContext(m, client.clientName), - ), - isActionAttempts, - } -} +): RouteLayoutContext => ({ + useStatements: getUseStatements(client), + clientName: client.clientName, + hasChildClients: client.childClientIdentifiers.length > 0, + childClients: client.childClientIdentifiers.map((i) => ({ + clientName: i.clientName, + namespace: i.namespace, + })), + methods: client.methods.map(getMethodLayoutContext), +}) diff --git a/codegen/lib/map-php-type.ts b/codegen/lib/map-php-type.ts index 7ca5e2c6..f7f206e0 100644 --- a/codegen/lib/map-php-type.ts +++ b/codegen/lib/map-php-type.ts @@ -3,7 +3,18 @@ import type { Parameter, Property } from '@seamapi/blueprint' -export const getPhpType = (schema: Parameter | Property): string => { +type RecordValueType = NonNullable< + Extract['valueTypes'] +>[number] + +export const getPhpType = ( + schema: Parameter | Property, + enumType = 'string', +): string => { + if (schema.format === 'enum') return enumType + if (schema.format === 'record' && !('resourceType' in schema)) { + return 'array|\\stdClass' + } if (schema.format === 'number' && schema.isInt) return 'int' switch (schema.jsonType) { @@ -13,8 +24,10 @@ export const getPhpType = (schema: Parameter | Property): string => { case 'number': return 'float' - case 'boolean': - return 'bool' + case 'boolean': { + const values = [...new Set(schema.values)] + return values.length === 1 ? String(values[0]) : 'bool' + } case 'array': return 'array' @@ -23,3 +36,55 @@ export const getPhpType = (schema: Parameter | Property): string => { return 'mixed' } } + +export const getPhpDocType = (schema: Parameter | Property): string => { + if (schema.format === 'list') { + return `list<${getListItemPhpType(schema)}>` + } + + if (schema.format !== 'record' || 'resourceType' in schema) { + return getPhpType(schema) + } + + const types = + ('valueTypes' in schema ? schema.valueTypes : undefined)?.map( + getRecordValuePhpType, + ) ?? [] + return `array|\\stdClass` +} + +const getListItemPhpType = ( + schema: Extract, +): string => { + switch (schema.itemFormat) { + case 'number': + return 'isItemInt' in schema && schema.isItemInt ? 'int' : 'float' + case 'boolean': + return 'bool' + case 'object': + case 'record': + case 'discriminated_object': + return 'array|\\stdClass' + default: + return 'string' + } +} + +const getRecordValuePhpType = (type: RecordValueType): string => { + switch (type) { + case 'string': + return 'string' + case 'number': + return 'float' + case 'integer': + return 'int' + case 'boolean': + return 'bool' + case 'object': + return 'array|\\stdClass' + case 'array': + return 'list' + default: + throw new Error(`Unsupported JSON Schema type: ${type}`) + } +} diff --git a/codegen/lib/merge-properties.ts b/codegen/lib/merge-properties.ts new file mode 100644 index 00000000..26caa63f --- /dev/null +++ b/codegen/lib/merge-properties.ts @@ -0,0 +1,153 @@ +import type { Property } from '@seamapi/blueprint' + +const formatKey = (property: Property): string => + property.format === 'list' ? `list<${property.itemFormat}>` : property.format + +const isScalar = (property: Property): boolean => + property.format !== 'list' && property.format !== 'object' + +interface MergedDocs { + description: string + isDeprecated: boolean + deprecationMessage: string +} + +// Each variant documents a property for its own case, which is accurate there +// but not for the single class the variants merge into. Some are merely narrow +// ("Previous code configuration" on a shape that also covers names); others +// contradict each other outright ("the error is not a device error" against +// "the error is a device error"). No description beats a wrong one, so keep one +// only when every variant that documents the property agrees. +const mergeDocs = (occurrences: Property[]): MergedDocs => { + const descriptions = [ + ...new Set( + occurrences + .map((occurrence) => occurrence.description.trim()) + .filter((description) => description !== ''), + ), + ] + const deprecated = occurrences.find(({ isDeprecated }) => isDeprecated) + + return { + description: descriptions.length === 1 ? (descriptions[0] ?? '') : '', + // Deprecating in any variant deprecates the merged property, so a warning + // is never dropped just because another variant omits it. + isDeprecated: deprecated != null, + deprecationMessage: deprecated?.deprecationMessage ?? '', + } +} + +// The variants of a discriminated union collapse into a single class, so a +// property carried by more than one variant has to end up with every field any +// variant gives it. Keeping only the first occurrence silently drops the rest, +// which loses data once the merged shape is a typed class rather than a hash. +const mergeOccurrences = (occurrences: Property[], path: string): Property => { + const [first, ...rest] = occurrences + if (first == null) throw new Error(`Nothing to merge at ${path}.`) + if (rest.length === 0) return first + + const docs = mergeDocs(occurrences) + + const formats = new Set(occurrences.map(formatKey)) + if (formats.size > 1) { + // Scalars all become a plain accessor, so any of them represents the rest. + if (occurrences.every(isScalar)) return { ...first, ...docs } + throw new Error( + `Cannot merge ${path}: variants disagree on its shape (${[...formats].join(', ')}).`, + ) + } + + if (first.format === 'boolean') { + const booleans = occurrences as Array< + Extract + > + const values = booleans.some(({ values }) => values == null) + ? undefined + : [...new Set(booleans.flatMap(({ values }) => values ?? []))] + const merged = { ...first, ...docs } + if (values == null) delete merged.values + else merged.values = values + return merged + } + + if (first.format === 'record' && 'valueTypes' in first) { + const valueTypes = occurrences.some( + (occurrence) => + !('valueTypes' in occurrence) || occurrence.valueTypes == null, + ) + ? undefined + : [ + ...new Set( + occurrences.flatMap((occurrence) => + 'valueTypes' in occurrence ? (occurrence.valueTypes ?? []) : [], + ), + ), + ] + const merged = { ...first, ...docs } + if (valueTypes == null) delete merged.valueTypes + else merged.valueTypes = valueTypes + return merged + } + + if (first.format === 'object') { + return { + ...first, + ...docs, + properties: mergeProperties( + occurrences.map( + (occurrence) => (occurrence as typeof first).properties, + ), + path, + ), + } + } + + if (first.format === 'list' && first.itemFormat === 'object') { + return { + ...first, + ...docs, + itemProperties: mergeProperties( + occurrences.map( + (occurrence) => (occurrence as typeof first).itemProperties, + ), + `${path}[]`, + ), + } + } + + if (first.format === 'list' && first.itemFormat === 'discriminated_object') { + // Keep every variant. Whoever consumes this list merges them in turn. + return { + ...first, + ...docs, + variants: occurrences.flatMap( + (occurrence) => (occurrence as typeof first).variants, + ), + } + } + + return { ...first, ...docs } +} + +export const mergeProperties = ( + propertyLists: Property[][], + path = '', +): Property[] => { + const occurrences = new Map() + for (const properties of propertyLists) { + for (const property of properties) { + const group = occurrences.get(property.name) + if (group == null) { + occurrences.set(property.name, [property]) + } else { + group.push(property) + } + } + } + + return [...occurrences.entries()] + .map(([name, group]) => + mergeOccurrences(group, path === '' ? name : `${path}.${name}`), + ) + .sort((a, b) => a.name.localeCompare(b.name)) +} diff --git a/codegen/lib/resource-model.ts b/codegen/lib/resource-model.ts index e29a192f..e2e49899 100644 --- a/codegen/lib/resource-model.ts +++ b/codegen/lib/resource-model.ts @@ -1,21 +1,30 @@ // Builds the resource class model for src/Resources from the blueprint. // -// Each blueprint resource becomes a PHP class in its own file. Nested object -// properties and lists of objects are split into their own classes, named -// after the base resource and the property, e.g. the device battery property -// becomes DeviceBattery. Those classes only exist to type a resource -// property, so they are emitted as local classes in the file of the resource -// that introduced them. Discriminated unions (events, action attempts, and -// discriminated object lists) are flattened into a single class with the -// union of the variant properties. +// Nested objects are emitted recursively in the namespace of their owner. +// Discriminated resources and object lists are emitted as an abstract base, +// one final class per variant, and an unknown-discriminant fallback. -import type { Blueprint, Property } from '@seamapi/blueprint' -import { pascalCase } from 'change-case' +import type { + Blueprint, + EnumProperty, + Property, + Resource, +} from '@seamapi/blueprint' +import { constantCase, pascalCase } from 'change-case' -import { getPhpType } from './map-php-type.js' +import { getPhpDocType, getPhpType } from './map-php-type.js' export type ResourceClassProperty = - | ({ kind: 'value'; phpType: string } & ResourceClassPropertyMetadata) + | ({ + kind: 'value' + phpType: string + phpDocType: string + } & ResourceClassPropertyMetadata) + | ({ + kind: 'record' + phpType: string + phpDocType: string + } & ResourceClassPropertyMetadata) | ({ kind: 'objectReference' referenceName: string @@ -28,22 +37,49 @@ export type ResourceClassProperty = interface ResourceClassPropertyMetadata { name: string description: string + isOptional: boolean + isNullable: boolean isDeprecated: boolean deprecationMessage: string } +export interface ResourceFactoryVariant { + enumCase: string + className: string +} + +export interface ResourceFactory { + discriminant: string + enumType: string + variants: ResourceFactoryVariant[] +} + export interface ResourceClassSchema { + kind: 'class' name: string + namespace: string description: string isDeprecated: boolean deprecationMessage: string + isFinal: boolean + extendsName: string properties: ResourceClassProperty[] + inheritedProperties: ResourceClassProperty[] + factory?: ResourceFactory +} + +export interface ResourceEnumSchema { + kind: 'enum' + name: string + namespace: string + cases: Array<{ name: string; value: string; description: string }> } +export type ResourceDeclaration = ResourceClassSchema | ResourceEnumSchema + export interface ResourceSchema { name: string - resourceClass: ResourceClassSchema - localClasses: ResourceClassSchema[] + declarations: ResourceDeclaration[] } export interface ResourceModel { @@ -51,188 +87,558 @@ export interface ResourceModel { resources: ResourceSchema[] } -export const createResourceModel = (blueprint: Blueprint): ResourceModel => { - const baseResources = new Map() +const rootNamespace = 'Seam\\Resources' +const maxDepth = 16 - for (const resource of blueprint.resources) { - baseResources.set(resource.resourceType, resource.properties) - } +const reservedClassNames = new Set([ + 'array', + 'bool', + 'callable', + 'enum', + 'false', + 'float', + 'int', + 'iterable', + 'mixed', + 'never', + 'null', + 'object', + 'parent', + 'self', + 'static', + 'string', + 'true', + 'void', + 'abstract', + 'and', + 'as', + 'break', + 'case', + 'catch', + 'class', + 'clone', + 'const', + 'continue', + 'declare', + 'default', + 'die', + 'do', + 'echo', + 'else', + 'elseif', + 'empty', + 'enddeclare', + 'endfor', + 'endforeach', + 'endif', + 'endswitch', + 'endwhile', + 'exit', + 'extends', + 'final', + 'finally', + 'fn', + 'for', + 'foreach', + 'function', + 'global', + 'goto', + 'if', + 'implements', + 'include', + 'include_once', + 'instanceof', + 'insteadof', + 'interface', + 'isset', + 'list', + 'match', + 'namespace', + 'new', + 'or', + 'print', + 'private', + 'protected', + 'public', + 'readonly', + 'require', + 'require_once', + 'return', + 'switch', + 'throw', + 'trait', + 'try', + 'unset', + 'use', + 'var', + 'while', + 'xor', + 'yield', +]) - // The blueprint models events and action attempts as one resource per - // variant. The PHP SDK has a single class for each, so the variants are - // merged into one schema. - const { events } = blueprint - if (events.length > 0) { - baseResources.set( - 'event', - mergeProperties(events.map((event) => event.properties)), - ) - } +interface ClassDocs { + description: string + isDeprecated: boolean + deprecationMessage: string +} - const { actionAttempts } = blueprint - if (actionAttempts.length > 0) { - baseResources.set( - 'action_attempt', - mergeProperties( - actionAttempts.map((actionAttempt) => actionAttempt.properties), - ), - ) - } +interface BuiltDeclaration { + declaration: ResourceDeclaration + nestedDeclarations: BuiltDeclaration[] +} - const classes = new Map() - const localClassNames = new Map() - - let currentResourceName = '' - - const addClass = ( - name: string, - properties: Property[], - baseName: string, - description = '', - isDeprecated = false, - deprecationMessage = '', - ): void => { - if (classes.has(name)) return - const schema: ResourceClassSchema = { - name, - description, - isDeprecated, - deprecationMessage, - properties: [], - } - classes.set(name, schema) - if (name !== currentResourceName) { - localClassNames.get(currentResourceName)?.push(name) +interface VariantInput { + properties: Property[] + description: string +} + +export const createResourceModel = (blueprint: Blueprint): ResourceModel => { + const discriminatedTypes = new Set( + [...blueprint.events, ...blueprint.actionAttempts].map( + ({ resourceType }) => resourceType, + ), + ) + const resources = new Map( + blueprint.resources + .filter(({ resourceType }) => !discriminatedTypes.has(resourceType)) + .map((resource) => [resource.resourceType, resource] as const), + ) + + const resourceTypes: string[] = [ + ...resources.keys(), + ...(blueprint.events.length > 0 ? ['event'] : []), + ...(blueprint.actionAttempts.length > 0 ? ['action_attempt'] : []), + ].sort() + + const schemas = resourceTypes.map((resourceType): ResourceSchema => { + const name = pascalCase(resourceType) + let built: BuiltDeclaration + + if (resourceType === 'event') { + built = buildDiscriminatedClass( + name, + rootNamespace, + blueprint.events, + 'event_type', + resourceType, + 0, + { + description: 'Base class for events returned by the Seam API.', + isDeprecated: false, + deprecationMessage: '', + }, + ) + } else if (resourceType === 'action_attempt') { + built = buildDiscriminatedClass( + name, + rootNamespace, + blueprint.actionAttempts, + 'action_type', + resourceType, + 0, + { + description: + 'Base class for actions whose completion is tracked asynchronously.', + isDeprecated: false, + deprecationMessage: '', + }, + true, + ) + } else { + const resource = resources.get(resourceType) + built = buildClass( + name, + rootNamespace, + resource?.properties ?? [], + resourceType, + 0, + docsFor(resource), + ) } - schema.properties = properties.map((property) => - createResourceClassProperty(property, baseName, addClass), - ) + + return { name, declarations: flattenDeclarations(built) } + }) + + return { + resourceNames: schemas.map(({ name }) => name), + resources: schemas, } +} - const baseResourceTypes = [...baseResources.keys()].sort() - const resources = baseResourceTypes.map((resourceType) => { - const name = pascalCase(resourceType) - currentResourceName = name - localClassNames.set(name, []) - const sourceResource = - blueprint.resources.find( - (resource) => resource.resourceType === resourceType, - ) ?? - (resourceType === 'event' - ? blueprint.events[0] - : blueprint.actionAttempts[0]) - addClass( - name, - baseResources.get(resourceType) ?? [], - resourceType, - sourceResource?.description, - sourceResource?.isDeprecated, - sourceResource?.deprecationMessage, - ) +const docsFor = (resource: Resource | undefined): ClassDocs => ({ + description: resource?.description ?? '', + isDeprecated: resource?.isDeprecated ?? false, + deprecationMessage: resource?.deprecationMessage ?? '', +}) - const resourceClass = classes.get(name) - if (resourceClass == null) { - throw new Error( - `Missing class for resource ${resourceType}: ${name} is already used by a property class of another resource`, +const buildClass = ( + className: string, + namespace: string, + classProperties: Property[], + path: string, + depth: number, + docs: ClassDocs, + options: { + isFinal?: boolean + extendsName?: string + inheritedProperties?: ResourceClassProperty[] + factory?: ResourceFactory + } = {}, +): BuiltDeclaration => { + assertDepth(path, depth) + + const nestedNamespace = `${namespace}\\${className}` + const nestedDeclarations: BuiltDeclaration[] = [] + const takenNames = new Set() + + const properties = classProperties.map((property): ResourceClassProperty => { + const metadata = propertyMetadata(property) + const nestedPath = `${path}.${property.name}` + const nestedClassName = pascalCase(property.name) + + if (property.format === 'enum') { + assertAvailableName( + nestedClassName, + nestedPath, + nestedNamespace, + takenNames, + ) + const enumType = `\\${nestedNamespace}\\${nestedClassName}` + nestedDeclarations.push( + buildEnum(nestedClassName, nestedNamespace, property), ) + return { + ...metadata, + kind: 'value', + phpType: 'string', + phpDocType: `value-of<${enumType}>|string`, + } + } + + if ( + property.format === 'list' && + property.itemFormat === 'discriminated_object' + ) { + assertAvailableName( + nestedClassName, + nestedPath, + nestedNamespace, + takenNames, + ) + nestedDeclarations.push( + buildDiscriminatedClass( + nestedClassName, + nestedNamespace, + property.variants, + property.discriminator, + nestedPath, + depth + 1, + propertyDocs(property), + ), + ) + return { + ...metadata, + kind: 'listReference', + referenceName: `\\${nestedNamespace}\\${nestedClassName}`, + } } - return { - name, - resourceClass, - localClasses: (localClassNames.get(name) ?? []) - .map((localClassName) => { - const localClass = classes.get(localClassName) - if (localClass == null) { - throw new Error(`Missing local class ${localClassName}`) - } - return localClass - }) - .sort((a, b) => a.name.localeCompare(b.name)), + const nestedProperties = getNestedProperties(property) + if (nestedProperties != null) { + assertAvailableName( + nestedClassName, + nestedPath, + nestedNamespace, + takenNames, + ) + nestedDeclarations.push( + buildClass( + nestedClassName, + nestedNamespace, + nestedProperties, + nestedPath, + depth + 1, + propertyDocs(property), + ), + ) + const referenceName = `\\${nestedNamespace}\\${nestedClassName}` + return { + ...metadata, + kind: property.format === 'list' ? 'listReference' : 'objectReference', + referenceName, + } } + + return property.format === 'record' && !('resourceType' in property) + ? { + ...metadata, + kind: 'record', + phpType: getPhpType(property), + phpDocType: getPhpDocType(property), + } + : { + ...metadata, + kind: 'value', + phpType: getPhpType(property), + phpDocType: getPhpDocType(property), + } }) return { - resourceNames: resources.map((resource) => resource.name), - resources, + declaration: { + kind: 'class', + name: className, + namespace, + ...docs, + isFinal: options.isFinal ?? false, + extendsName: options.extendsName ?? '', + properties, + inheritedProperties: options.inheritedProperties ?? [], + ...(options.factory == null ? {} : { factory: options.factory }), + }, + nestedDeclarations, } } -const createResourceClassProperty = ( - property: Property, - baseName: string, - addClass: ( - name: string, - properties: Property[], - baseName: string, - description?: string, - isDeprecated?: boolean, - deprecationMessage?: string, - ) => void, -): ResourceClassProperty => { - const referenceName = pascalCase(`${baseName}_${property.name}`) - const metadata = { - name: property.name, - description: property.description, - isDeprecated: property.isDeprecated, - deprecationMessage: property.deprecationMessage, +const buildDiscriminatedClass = ( + className: string, + namespace: string, + variants: VariantInput[], + discriminator: string, + path: string, + depth: number, + docs: ClassDocs, + actionAttempt = false, +): BuiltDeclaration => { + assertDepth(path, depth) + if (variants.length === 0) { + return buildClass(className, namespace, [], path, depth, docs) } - if (property.format === 'object') { - const { properties } = property - - if (properties.length > 0) { - addClass(referenceName, properties, baseName, property.description) - return { ...metadata, kind: 'objectReference', referenceName } + const variantInfo = variants.map((variant) => { + const property = variant.properties.find( + ({ name }) => name === discriminator, + ) + if (property?.format !== 'enum' || property.values.length !== 1) { + throw new Error( + `Cannot generate ${path}: ${discriminator} is not a single-value enum`, + ) } + return { variant, value: property.values[0]?.name ?? '' } + }) + + const commonNames = new Set( + variants[0]?.properties + .filter((property) => + variants.every((variant) => { + const candidate = variant.properties.find( + ({ name }) => name === property.name, + ) + return ( + candidate != null && + (property.name === discriminator || + (actionAttempt && property.name === 'error') || + propertyShape(candidate) === propertyShape(property)) + ) + }), + ) + .map(({ name }) => name) ?? [], + ) + + const commonProperties = (variants[0]?.properties ?? []) + .filter(({ name }) => commonNames.has(name)) + .map((property) => { + if (property.format !== 'enum') return property + const values = uniqueEnumValues( + variants.flatMap( + (variant) => + ( + variant.properties.find(({ name }) => name === property.name) as + EnumProperty | undefined + )?.values ?? [], + ), + ) + return { ...property, values } + }) + .map((property) => { + if (!actionAttempt || !['error', 'result'].includes(property.name)) { + return property + } + return { + ...property, + description: `${property.description}${property.description === '' ? '' : ' '}Null while the action attempt is pending or when this value does not apply.`, + } + }) + + const discriminantProperty = commonProperties.find( + ({ name }) => name === discriminator, + ) + if (discriminantProperty?.format !== 'enum') { + throw new Error(`Cannot generate ${path}: missing ${discriminator}`) } - if (property.format === 'list') { - const itemProperties = - property.itemFormat === 'object' - ? property.itemProperties - : property.itemFormat === 'discriminated_object' - ? mergeProperties( - property.variants.map((variant) => variant.properties), - ) - : [] - - if (itemProperties.length > 0) { - addClass(referenceName, itemProperties, baseName, property.description) - return { ...metadata, kind: 'listReference', referenceName } - } + const enumName = pascalCase(discriminator) + const enumType = `\\${namespace}\\${className}\\${enumName}` + const factory: ResourceFactory = { + discriminant: discriminator, + enumType, + variants: variantInfo.map(({ value }) => ({ + enumCase: `${enumType}::${enumCaseName(value)}`, + className: `\\${namespace}\\${className}\\${pascalCase(value)}`, + })), } - return { - ...metadata, - kind: 'value', - phpType: getPhpType(property), + const built = buildClass( + className, + namespace, + commonProperties, + path, + depth, + { + ...docs, + description: `${docs.description}${docs.description === '' ? '' : ' '}Known ${discriminator} values use subclasses; unknown values use this base class and retain their raw discriminator.`, + }, + { factory }, + ) + const base = built.declaration + if (base.kind !== 'class') throw new Error(`Cannot generate ${path}`) + const baseName = `\\${namespace}\\${className}` + for (const { variant, value } of variantInfo) { + const ownProperties = variant.properties + .filter(({ name }) => !commonNames.has(name)) + .map((property) => { + if (!actionAttempt || property.name !== 'result') return property + return { + ...property, + description: `${property.description}${property.description === '' ? '' : ' '}Null while the action attempt is pending or when this value does not apply.`, + } + }) + built.nestedDeclarations.push( + buildClass( + pascalCase(value), + `${namespace}\\${className}`, + ownProperties, + `${path}.${value}`, + depth + 1, + { + description: variant.description, + isDeprecated: false, + deprecationMessage: '', + }, + { + isFinal: true, + extendsName: baseName, + inheritedProperties: base.properties, + }, + ), + ) } + + return built +} + +const buildEnum = ( + name: string, + namespace: string, + property: EnumProperty, +): BuiltDeclaration => ({ + declaration: { + kind: 'enum', + name, + namespace, + cases: uniqueEnumValues(property.values).map((value) => ({ + name: enumCaseName(value.name), + value: value.name, + description: value.description, + })), + }, + nestedDeclarations: [], +}) + +const enumCaseName = (value: string): string => { + const name = constantCase(value) + return /^[A-Z_]/.test(name) && !reservedClassNames.has(name.toLowerCase()) + ? name + : `VALUE_${name}` } -const mergeProperties = (propertyLists: Property[][]): Property[] => { - const merged = new Map() +const uniqueEnumValues = (values: T[]): T[] => [ + ...new Map(values.map((value) => [value.name, value])).values(), +] - for (const properties of propertyLists) { - for (const property of properties) { - const existing = merged.get(property.name) +const propertyShape = (property: Property): string => + JSON.stringify(property, (key, value: unknown) => + [ + 'description', + 'isDeprecated', + 'deprecationMessage', + 'isUndocumented', + 'undocumentedMessage', + 'isDraft', + 'draftMessage', + 'propertyGroupKey', + ].includes(key) + ? undefined + : value, + ) - if (existing == null) { - merged.set(property.name, property) - continue - } +const propertyMetadata = ( + property: Property, +): ResourceClassPropertyMetadata => ({ + name: property.name, + description: property.description, + isOptional: property.isOptional, + isNullable: property.isNullable, + isDeprecated: property.isDeprecated, + deprecationMessage: property.deprecationMessage, +}) - if (existing.format === 'object' && property.format === 'object') { - merged.set(property.name, { - ...existing, - properties: mergeProperties([ - existing.properties, - property.properties, - ]), - }) - } - } +const propertyDocs = (property: Property): ClassDocs => ({ + description: property.description, + isDeprecated: property.isDeprecated, + deprecationMessage: property.deprecationMessage, +}) + +const getNestedProperties = (property: Property): Property[] | undefined => { + if (property.format === 'object') { + return property.properties.length > 0 ? property.properties : undefined + } + if (property.format === 'list' && property.itemFormat === 'object') { + return property.itemProperties.length > 0 + ? property.itemProperties + : undefined + } + return undefined +} + +const assertDepth = (path: string, depth: number): void => { + if (depth > maxDepth) { + throw new Error( + `Cannot generate ${path}: nesting exceeded a depth of ${maxDepth}, which means the schema is cyclic`, + ) } +} - return [...merged.values()] +const assertAvailableName = ( + name: string, + path: string, + namespace: string, + takenNames: Set, +): void => { + if (reservedClassNames.has(name.toLowerCase())) { + throw new Error(`Cannot generate ${path}: ${name} is reserved in PHP`) + } + if (takenNames.has(name.toLowerCase())) { + throw new Error( + `Cannot generate ${path}: ${name} is already used in ${namespace}`, + ) + } + takenNames.add(name.toLowerCase()) } + +const flattenDeclarations = ( + built: BuiltDeclaration, +): ResourceDeclaration[] => [ + built.declaration, + ...built.nestedDeclarations.flatMap(flattenDeclarations), +] diff --git a/codegen/lib/routes.ts b/codegen/lib/routes.ts index f18f155a..3c13cc7d 100644 --- a/codegen/lib/routes.ts +++ b/codegen/lib/routes.ts @@ -12,7 +12,7 @@ import type { PhpClient, PhpClientMethod } from './class-model.js' import { setResourceLayoutContext } from './layouts/resource.js' import { setRouteLayoutContext } from './layouts/route.js' import { setSeamClientLayoutContext } from './layouts/seam-client.js' -import { getPhpType } from './map-php-type.js' +import { getPhpDocType, getPhpType } from './map-php-type.js' import { createResourceModel } from './resource-model.js' interface Metadata { @@ -21,7 +21,7 @@ interface Metadata { const resourcesPath = 'src/Resources' const routesPath = 'src/Routes' -const seamClientPath = 'src/SeamClient.php' +const seamClientPath = 'src/Seam.php' export const routes = ( files: Metalsmith.Files, @@ -121,16 +121,24 @@ const createClientMethod = (endpoint: Endpoint): PhpClientMethod => { return { methodName: endpoint.name, + httpMethod: endpoint.request.preferredMethod, path: endpoint.path, description: endpoint.description, responseDescription: response.description, isDeprecated: endpoint.isDeprecated, deprecationMessage: endpoint.deprecationMessage, + // An endpoint that takes no individually required parameter may still + // require one of them, so PHP cannot enforce it through the signature. + requiresAtLeastOneParameter: + endpoint.request.hasRequiredParameters && + endpoint.request.parameters.every(({ isRequired }) => !isRequired), parameters: endpoint.request.parameters.map((parameter) => ({ name: parameter.name, type: getPhpType(parameter), + phpDocType: getPhpDocType(parameter), description: parameter.description, - required: parameter.isRequired, + isOptional: !parameter.isRequired, + isNullable: parameter.isNullable, // The primary identifier of a get endpoint always sorts first in the // method signature. position: diff --git a/codegen/smith.ts b/codegen/smith.ts index 744efd43..ba1384fa 100644 --- a/codegen/smith.ts +++ b/codegen/smith.ts @@ -12,7 +12,7 @@ import { helpers, routes } from './lib/index.js' const rootDir = dirname(fileURLToPath(import.meta.url)) await Promise.all([ - deleteAsync(['./src/Resources', './src/Routes', './src/SeamClient.php']), + deleteAsync(['./src/Resources', './src/Routes', './src/Seam.php']), ]) const partials = await getHandlebarsPartials(`${rootDir}/layouts/partials`) diff --git a/composer.json b/composer.json index 6827b5ce..fb1f683d 100644 --- a/composer.json +++ b/composer.json @@ -25,19 +25,18 @@ "source": "https://github.com/seamapi/php" }, "require": { - "php": "^8.0", - "guzzlehttp/guzzle": "^7.5" + "php": "^8.2", + "caseyamcl/guzzle_retry_middleware": "^2.13", + "guzzlehttp/guzzle": "^7.5", + "svix/svix": "^1.40" }, "require-dev": { - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.7" + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^6.0" }, "autoload": { "psr-4": { - "Seam\\": [ - "src/", - "src/Exceptions/" - ] + "Seam\\": "src/" }, "classmap": [ "src/Resources/" @@ -49,6 +48,9 @@ } }, "config": { + "platform": { + "php": "8.2.27" + }, "sort-packages": true }, "archive": { @@ -58,7 +60,7 @@ "/.env.example", "/.github", "/.npmrc", - "/.phpunit.result.cache", + "/.phpunit.cache", "/.prettierignore", "/.prettierrc.json", "/.releaserc.json", @@ -69,6 +71,7 @@ "/package.json", "/phpunit.xml.dist", "/pkg", + "/psalm.xml", "/tests", "/tmp", "/tsconfig.json", @@ -81,16 +84,19 @@ "test": "phpunit", "lint": [ "@lint:composer", - "@lint:syntax" + "@lint:syntax", + "@lint:types" ], "lint:composer": "@composer validate --strict", - "lint:syntax": "! find src tests -name '*.php' -exec php -l {} \\; | grep -v '^No syntax errors detected'" + "lint:syntax": "! find src tests -name '*.php' -exec php -l {} \\; | grep -v '^No syntax errors detected'", + "lint:types": "psalm --no-cache" }, "scripts-descriptions": { "build": "Build a distributable archive of this package into pkg/.", "test": "Run the test suite.", "lint": "Run all lint checks.", "lint:composer": "Validate composer.json and composer.lock.", - "lint:syntax": "Check every PHP source file for syntax errors." + "lint:syntax": "Check every PHP source file for syntax errors.", + "lint:types": "Run static analysis with Psalm." } } diff --git a/composer.lock b/composer.lock index bc6794ae..29d6834a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,38 +4,114 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "47b28c106be0ff4f75680c1fe23e931a", + "content-hash": "fcadd2bfc0a095fb22d744660cd07366", "packages": [ + { + "name": "caseyamcl/guzzle_retry_middleware", + "version": "v2.13.0", + "source": { + "type": "git", + "url": "https://github.com/caseyamcl/guzzle_retry_middleware.git", + "reference": "17c9299cde438b00bbeb099c6480319a81636a60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/caseyamcl/guzzle_retry_middleware/zipball/17c9299cde438b00bbeb099c6480319a81636a60", + "reference": "17c9299cde438b00bbeb099c6480319a81636a60", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.3|^7.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "jaschilz/php-coverage-badger": "^2.0", + "nesbot/carbon": "^2.0|^3.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.0", + "phpunit/phpunit": "^7.5|^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/var-dumper": "^5.0|^6.0|^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleRetry\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Casey McLaughlin", + "email": "caseyamcl@gmail.com", + "homepage": "https://caseymclaughlin.com", + "role": "Developer" + } + ], + "description": "Guzzle v6+ retry middleware that handles 429/503 status codes and connection timeouts", + "homepage": "https://github.com/caseyamcl/guzzle_retry_middleware", + "keywords": [ + "Guzzle", + "back-off", + "caseyamcl", + "guzzle_retry_middleware", + "middleware", + "retry", + "retry-after" + ], + "support": { + "issues": "https://github.com/caseyamcl/guzzle_retry_middleware/issues", + "source": "https://github.com/caseyamcl/guzzle_retry_middleware/tree/v2.13.0" + }, + "funding": [ + { + "url": "https://github.com/caseyamcl", + "type": "github" + } + ], + "time": "2025-07-11T12:33:22+00:00" + }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", + "version": "7.15.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "ae311b8f045ea93ce7b1c9cdb7cec06c53f944bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ae311b8f045ea93ce7b1c9cdb7cec06c53f944bc", + "reference": "ae311b8f045ea93ce7b1c9cdb7cec06c53f944bc", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/promises": "^2.5.2", + "guzzlehttp/psr7": "^2.13", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -48,9 +124,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" } }, "autoload": { @@ -116,7 +189,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/7.15.3" }, "funding": [ { @@ -132,38 +205,38 @@ "type": "tidelift" } ], - "time": "2022-08-28T15:39:27+00:00" + "time": "2026-08-05T19:48:21+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.2", + "version": "2.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "b94b2807d85443f9719887892882d0329d1e2598" + "reference": "2823687acff28b2dbe67b2508a6b300e2c3fa4ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", - "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "url": "https://api.github.com/repos/guzzle/promises/zipball/2823687acff28b2dbe67b2508a6b300e2c3fa4ce", + "reference": "2823687acff28b2dbe67b2508a6b300e2c3fa4ce", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.5-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -200,7 +273,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.2" + "source": "https://github.com/guzzle/promises/tree/2.5.2" }, "funding": [ { @@ -216,36 +289,39 @@ "type": "tidelift" } ], - "time": "2022-08-28T14:55:35+00:00" + "time": "2026-08-05T19:30:54+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.4.1", + "version": "2.13.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379" + "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379", - "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/dad89620b7a6edb60c15858442eb2e408b45d8f4", + "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "ralouphie/getallheaders": "^3.0" + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -255,9 +331,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "2.4-dev" } }, "autoload": { @@ -319,7 +392,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.1" + "source": "https://github.com/guzzle/psr7/tree/2.13.0" }, "funding": [ { @@ -335,25 +408,25 @@ "type": "tidelift" } ], - "time": "2022-08-28T14:45:39+00:00" + "time": "2026-07-16T22:23:49+00:00" }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -373,7 +446,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -385,27 +458,27 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0" + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -425,10 +498,10 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -440,31 +513,31 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -479,7 +552,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -493,9 +566,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "ralouphie/getallheaders", @@ -541,31 +614,84 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "svix/svix", + "version": "v1.99.1", + "source": { + "type": "git", + "url": "https://github.com/svix/svix-webhooks.git", + "reference": "6cd12c1d6d19c6d222bab889d08315f6469702c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/svix/svix-webhooks/zipball/6cd12c1d6d19c6d222bab889d08315f6469702c8", + "reference": "6cd12c1d6d19c6d222bab889d08315f6469702c8", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^7.0", + "php": ">=8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": ">=10.0 <=12.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svix\\": "php/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Svix", + "email": "support@svix.com", + "homepage": "http://www.svix.com" + } + ], + "description": "Svix PHP Library", + "homepage": "https://www.svix.com", + "keywords": [ + "api", + "webhooks" + ], + "support": { + "issues": "https://github.com/svix/svix-webhooks/issues", + "source": "https://github.com/svix/svix-webhooks/tree/v1.99.1" + }, + "time": "2026-07-23T16:05:36+00:00" + }, { "name": "symfony/deprecation-contracts", - "version": "v3.0.2", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.0-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" } }, "autoload": { @@ -590,7 +716,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -601,47 +727,51 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" - } - ], - "packages-dev": [ + "time": "2026-06-05T06:23:12+00:00" + }, { - "name": "doctrine/instantiator", - "version": "1.4.1", + "name": "symfony/polyfill-php80", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "php": ">=7.2" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -649,815 +779,3286 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "constructor", - "instantiate" + "compatibility", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", + "url": "https://symfony.com/sponsor", "type": "custom" }, { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" - }, + "time": "2026-04-10T16:19:22+00:00" + } + ], + "packages-dev": [ { - "name": "myclabs/deep-copy", - "version": "1.11.0", + "name": "amphp/amp", + "version": "v3.1.3", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "url": "https://github.com/amphp/amp.git", + "reference": "73c38b323ff8d790abf0f76c56fcc892bda4111a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/amphp/amp/zipball/73c38b323ff8d790abf0f76c56fcc892bda4111a", + "reference": "73c38b323ff8d790abf0f76c56fcc892bda4111a", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" }, "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.1" }, "type": "library", "autoload": { "files": [ - "src/DeepCopy/deep_copy.php" + "src/functions.php", + "src/Future/functions.php", + "src/Internal/functions.php" ], "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "Amp\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Create deep copies (clones) of your objects", + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "https://amphp.org/amp", "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" ], "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "issues": "https://github.com/amphp/amp/issues", + "source": "https://github.com/amphp/amp/tree/v3.1.3" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/amphp", + "type": "github" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2026-07-19T17:59:20+00:00" }, { - "name": "nikic/php-parser", - "version": "v4.15.1", + "name": "amphp/byte-stream", + "version": "v2.1.2", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + "url": "https://github.com/amphp/byte-stream.git", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=7.0" + "amphp/amp": "^3", + "amphp/parser": "^1.1", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2.3" }, "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.22.1" }, - "bin": [ - "bin/php-parse" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.9-dev" - } - }, "autoload": { + "files": [ + "src/functions.php", + "src/Internal/functions.php" + ], "psr-4": { - "PhpParser\\": "lib/PhpParser" + "Amp\\ByteStream\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "A PHP parser written in PHP", + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", "keywords": [ - "parser", - "php" + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" ], "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" }, - "time": "2022-09-04T07:30:47+00:00" + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-03-16T17:10:27+00:00" }, { - "name": "phar-io/manifest", - "version": "2.0.3", + "name": "amphp/cache", + "version": "v2.0.1", "source": { "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "url": "https://github.com/amphp/cache.git", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c", + "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" + "amphp/amp": "^3", + "amphp/serialization": "^1", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" }, + "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Amp\\Cache\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Niklas Keller", + "email": "me@kelunik.com" }, { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" } ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "description": "A fiber-aware cache API based on Amp and Revolt.", + "homepage": "https://amphp.org/cache", "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "issues": "https://github.com/amphp/cache/issues", + "source": "https://github.com/amphp/cache/tree/v2.0.1" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T03:38:06+00:00" }, { - "name": "phar-io/version", - "version": "3.2.1", + "name": "amphp/dns", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + "url": "https://github.com/amphp/dns.git", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "url": "https://api.github.com/repos/amphp/dns/zipball/78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", + "reference": "78eb3db5fc69bf2fc0cb503c4fcba667bc223c71", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/process": "^2", + "daverandom/libdns": "^2.0.2", + "ext-filter": "*", + "ext-json": "*", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.20" }, "type": "library", "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Dns\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Chris Wright", + "email": "addr@daverandom.com" }, { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" }, { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" } ], - "description": "Library for handling version information and constraints", + "description": "Async DNS resolution for Amp.", + "homepage": "https://github.com/amphp/dns", + "keywords": [ + "amp", + "amphp", + "async", + "client", + "dns", + "resolve" + ], "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" + "issues": "https://github.com/amphp/dns/issues", + "source": "https://github.com/amphp/dns/tree/v2.4.0" }, - "time": "2022-02-21T01:04:05+00:00" + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2025-01-19T15:43:40+00:00" }, { - "name": "phpunit/php-code-coverage", - "version": "9.2.17", + "name": "amphp/parallel", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" + "url": "https://github.com/amphp/parallel.git", + "reference": "37f5b2754fadc229c00f9416bd68fb8d04529a81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", + "url": "https://api.github.com/repos/amphp/parallel/zipball/37f5b2754fadc229c00f9416bd68fb8d04529a81", + "reference": "37f5b2754fadc229c00f9416bd68fb8d04529a81", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/cache": "^2", + "amphp/parser": "^1", + "amphp/pipeline": "^1", + "amphp/process": "^2", + "amphp/serialization": "^1", + "amphp/socket": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1" }, "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.2-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/Context/functions.php", + "src/Context/Internal/functions.php", + "src/Ipc/functions.php", + "src/Worker/functions.php" + ], + "psr-4": { + "Amp\\Parallel\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" } ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "description": "Parallel processing component for Amp.", + "homepage": "https://github.com/amphp/parallel", "keywords": [ - "coverage", - "testing", - "xunit" + "async", + "asynchronous", + "concurrent", + "multi-processing", + "multi-threading" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + "issues": "https://github.com/amphp/parallel/issues", + "source": "https://github.com/amphp/parallel/tree/v2.4.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/amphp", "type": "github" } ], - "time": "2022-08-30T12:24:04+00:00" + "time": "2026-05-16T16:54:01+00:00" }, { - "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "name": "amphp/parser", + "version": "v1.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "url": "https://github.com/amphp/parser.git", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7", + "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=7.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Amp\\Parser\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", "keywords": [ - "filesystem", - "iterator" + "async", + "non-blocking", + "parser", + "stream" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "issues": "https://github.com/amphp/parser/issues", + "source": "https://github.com/amphp/parser/tree/v1.1.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/amphp", "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2024-03-21T19:16:53+00:00" }, { - "name": "phpunit/php-invoker", - "version": "3.1.1", + "name": "amphp/pipeline", + "version": "v1.2.7", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "url": "https://github.com/amphp/pipeline.git", + "reference": "cf2d67696c2015ea7c7fd8f6ec5f8ed7c2d17c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/cf2d67696c2015ea7c7fd8f6ec5f8ed7c2d17c17", + "reference": "cf2d67696c2015ea7c7fd8f6ec5f8ed7c2d17c17", "shasum": "" }, "require": { - "php": ">=7.3" + "amphp/amp": "^3", + "php": ">=8.1", + "revolt/event-loop": "^1" }, "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-pcntl": "*" + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Amp\\Pipeline\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "description": "Asynchronous iterators and operators.", + "homepage": "https://amphp.org/pipeline", "keywords": [ - "process" + "amp", + "amphp", + "async", + "io", + "iterator", + "non-blocking" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "issues": "https://github.com/amphp/pipeline/issues", + "source": "https://github.com/amphp/pipeline/tree/v1.2.7" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/amphp", "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2026-07-26T14:50:43+00:00" }, { - "name": "phpunit/php-text-template", - "version": "2.0.4", + "name": "amphp/process", + "version": "v2.1.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "url": "https://github.com/amphp/process.git", + "reference": "583959df17d00304ad7b0b32285373f985935643" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/amphp/process/zipball/583959df17d00304ad7b0b32285373f985935643", + "reference": "583959df17d00304ad7b0b32285373f985935643", "shasum": "" }, "require": { - "php": ">=7.3" + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/sync": "^2", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Process\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], + "description": "A fiber-aware process manager based on Amp and Revolt.", + "homepage": "https://amphp.org/process", "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "issues": "https://github.com/amphp/process/issues", + "source": "https://github.com/amphp/process/tree/v2.1.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/amphp", "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2026-05-31T15:11:55+00:00" }, { - "name": "phpunit/php-timer", - "version": "5.0.3", + "name": "amphp/serialization", + "version": "v1.1.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "url": "https://github.com/amphp/serialization.git", + "reference": "fdf2834d78cebb0205fb2672676c1b1eb84371f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/amphp/serialization/zipball/fdf2834d78cebb0205fb2672676c1b1eb84371f0", + "reference": "fdf2834d78cebb0205fb2672676c1b1eb84371f0", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=7.4" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "amphp/php-cs-fixer-config": "^2", + "ext-json": "*", + "ext-zlib": "*", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Serialization\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", "keywords": [ - "timer" + "async", + "asynchronous", + "serialization", + "serialize" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "issues": "https://github.com/amphp/serialization/issues", + "source": "https://github.com/amphp/serialization/tree/v1.1.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/amphp", "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2026-04-05T15:59:53+00:00" }, { - "name": "phpunit/phpunit", - "version": "9.5.24", + "name": "amphp/socket", + "version": "v2.4.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5" + "url": "https://github.com/amphp/socket.git", + "reference": "dadb63c5d3179fd83803e29dfeac27350e619314" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", - "reference": "d0aa6097bef9fd42458a9b3c49da32c6ce6129c5", + "url": "https://api.github.com/repos/amphp/socket/zipball/dadb63c5d3179fd83803e29dfeac27350e619314", + "reference": "dadb63c5d3179fd83803e29dfeac27350e619314", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.1", - "sebastian/version": "^3.0.2" + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/dns": "^2", + "ext-openssl": "*", + "kelunik/certificate": "^1.1", + "league/uri": "^7", + "league/uri-interfaces": "^7", + "php": ">=8.1", + "revolt/event-loop": "^1" }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "amphp/process": "^2", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.1" }, - "bin": [ - "phpunit" - ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "9.5-dev" - } - }, "autoload": { "files": [ - "src/Framework/Assert/Functions.php" + "src/functions.php", + "src/Internal/functions.php", + "src/SocketAddress/functions.php" ], - "classmap": [ - "src/" - ] + "psr-4": { + "Amp\\Socket\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Daniel Lowrey", + "email": "rdlowrey@gmail.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", + "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.", + "homepage": "https://github.com/amphp/socket", "keywords": [ - "phpunit", - "testing", - "xunit" + "amp", + "async", + "encryption", + "non-blocking", + "sockets", + "tcp", + "tls" ], "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.24" + "issues": "https://github.com/amphp/socket/issues", + "source": "https://github.com/amphp/socket/tree/v2.4.0" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/amphp", "type": "github" } ], - "time": "2022-08-30T07:42:16+00:00" + "time": "2026-04-19T15:09:56+00:00" }, { - "name": "sebastian/cli-parser", - "version": "1.0.1", + "name": "amphp/sync", + "version": "v2.3.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "url": "https://github.com/amphp/sync.git", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", "shasum": "" }, "require": { - "php": ">=7.3" + "amphp/amp": "^3", + "amphp/pipeline": "^1", + "amphp/serialization": "^1", + "php": ">=8.1", + "revolt/event-loop": "^1 || ^0.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "amphp/php-cs-fixer-config": "^2", + "amphp/phpunit-util": "^3", + "phpunit/phpunit": "^9", + "psalm/phar": "5.23" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "src/functions.php" + ], + "psr-4": { + "Amp\\Sync\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" } ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", + "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "issues": "https://github.com/amphp/sync/issues", + "source": "https://github.com/amphp/sync/tree/v2.3.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/amphp", "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-08-03T19:31:26+00:00" }, { - "name": "sebastian/code-unit", - "version": "1.0.8", + "name": "composer/pcre", + "version": "3.4.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "url": "https://github.com/composer/pcre.git", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", "shasum": "" }, "require": { - "php": ">=7.3" + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<2.2.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" }, "type": "library", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Composer\\Pcre\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Collection of value objects that represent the PHP code units", - "homepage": "https://github.com/sebastianbergmann/code-unit", + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], "support": { - "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.4.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2026-06-07T11:47:49+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "danog/advanced-json-rpc", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/danog/php-advanced-json-rpc.git", + "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/danog/php-advanced-json-rpc/zipball/ae703ea7b4811797a10590b6078de05b3b33dd91", + "reference": "ae703ea7b4811797a10590b6078de05b3b33dd91", + "shasum": "" + }, + "require": { + "netresearch/jsonmapper": "^5", + "php": ">=8.1", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0 || ^6" + }, + "replace": { + "felixfbecker/php-advanced-json-rpc": "^3" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "AdvancedJsonRpc\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "description": "A more advanced JSONRPC implementation", + "support": { + "issues": "https://github.com/danog/php-advanced-json-rpc/issues", + "source": "https://github.com/danog/php-advanced-json-rpc/tree/v3.2.3" + }, + "time": "2026-01-12T21:07:10+00:00" + }, + { + "name": "daverandom/libdns", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/DaveRandom/LibDNS.git", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "Required for IDN support" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "LibDNS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "DNS protocol implementation written in pure PHP", + "keywords": [ + "dns" + ], + "support": { + "issues": "https://github.com/DaveRandom/LibDNS/issues", + "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0" + }, + "time": "2024-04-12T12:12:48+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, + { + "name": "felixfbecker/language-server-protocol", + "version": "v1.5.3", + "source": { + "type": "git", + "url": "https://github.com/felixfbecker/php-language-server-protocol.git", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "reference": "a9e113dbc7d849e35b8776da39edaf4313b7b6c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/phpstan": "*", + "squizlabs/php_codesniffer": "^3.1", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "LanguageServerProtocol\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Felix Becker", + "email": "felix.b@outlook.com" + } + ], + "description": "PHP classes for the Language Server Protocol", + "keywords": [ + "language", + "microsoft", + "php", + "server" + ], + "support": { + "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", + "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.3" + }, + "time": "2024-04-30T00:40:11+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, + { + "name": "kelunik/certificate", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/kelunik/certificate.git", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">=7.0" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2", + "phpunit/phpunit": "^6 | 7 | ^8 | ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kelunik\\Certificate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Access certificate details and transform between different formats.", + "keywords": [ + "DER", + "certificate", + "certificates", + "openssl", + "pem", + "x509" + ], + "support": { + "issues": "https://github.com/kelunik/certificate/issues", + "source": "https://github.com/kelunik/certificate/tree/v1.1.3" + }, + "time": "2023-02-03T21:26:53+00:00" + }, + { + "name": "league/uri", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-15T20:22:25+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-08T20:05:35+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.14.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2026-08-11T10:17:44+00:00" + }, + { + "name": "netresearch/jsonmapper", + "version": "v5.0.1", + "source": { + "type": "git", + "url": "https://github.com/cweiske/jsonmapper.git", + "reference": "980674efdda65913492d29a8fd51c82270dd37bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/980674efdda65913492d29a8fd51c82270dd37bb", + "reference": "980674efdda65913492d29a8fd51c82270dd37bb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", + "squizlabs/php_codesniffer": "~3.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JsonMapper": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0" + ], + "authors": [ + { + "name": "Christian Weiske", + "email": "cweiske@cweiske.de", + "homepage": "http://github.com/cweiske/jsonmapper/", + "role": "Developer" + } + ], + "description": "Map nested JSON structures onto PHP classes", + "support": { + "email": "cweiske@cweiske.de", + "issues": "https://github.com/cweiske/jsonmapper/issues", + "source": "https://github.com/cweiske/jsonmapper/tree/v5.0.1" + }, + "time": "2026-02-22T16:28:03+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" + }, + "time": "2026-03-18T20:49:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" + }, + "time": "2026-01-06T21:53:42+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.3" + }, + "time": "2026-07-08T07:01:06+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "10.1.16", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-08-22T04:31:57+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "4.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T06:24:48+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:56:09+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-31T14:07:24+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:57:52+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "10.5.64", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e8c1d19cea35ad97d4887f363d07c78e30fbf06", + "reference": "0e8c1d19cea35ad97d4887f363d07c78e30fbf06", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.5", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.4", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.1", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.64" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-07-06T14:50:35+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "revolt/event-loop", + "version": "v1.0.9", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "44061cf513e53c6200372fc935ac42271566295d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/44061cf513e53c6200372fc935ac42271566295d", + "reference": "44061cf513e53c6200372fc935ac42271566295d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "6.16.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.9" + }, + "time": "2026-05-16T17:55:38+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:25:16+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "0735b90f4da94969541dac1da743446e276defa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", + "reference": "0735b90f4da94969541dac1da743446e276defa6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:09:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "ext-dom": "*", + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1475,11 +4076,15 @@ "email": "sebastian@phpunit.de" } ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" }, "funding": [ { @@ -1487,34 +4092,202 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { - "name": "sebastian/comparator", - "version": "4.0.8", + "name": "sebastian/lines-of-code", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5d32fe257a9b39cb63146924d6b4e32a22d4502a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5d32fe257a9b39cb63146924d6b4e32a22d4502a", + "reference": "5d32fe257a9b39cb63146924d6b4e32a22d4502a", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1536,58 +4309,61 @@ "email": "whatthejeff@gmail.com" }, { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" + "name": "Adam Harvey", + "email": "aharvey@php.net" } ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2026-08-11T05:27:39+00:00" }, { - "name": "sebastian/complexity", - "version": "2.0.2", + "name": "sebastian/type", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1606,11 +4382,64 @@ "role": "lead" } ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -1618,644 +4447,858 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { - "name": "sebastian/diff", - "version": "4.0.4", + "name": "spatie/array-to-xml", + "version": "3.4.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "url": "https://github.com/spatie/array-to-xml.git", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", "shasum": "" }, "require": { - "php": ">=7.3" + "ext-dom": "*", + "php": "^8.0" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Spatie\\ArrayToXml\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://freek.dev", + "role": "Developer" } ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", + "description": "Convert an array to xml", + "homepage": "https://github.com/spatie/array-to-xml", "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" + "array", + "convert", + "xml" ], "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2025-12-15T09:00:41+00:00" }, { - "name": "sebastian/environment", - "version": "5.1.4", + "name": "symfony/console", + "version": "v7.4.16", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "url": "https://github.com/symfony/console.git", + "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/symfony/console/zipball/f4c69c9aed03abf933b294257d618bdd9b30a06d", + "reference": "f4c69c9aed03abf933b294257d618bdd9b30a06d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, - "suggest": { - "ext-posix": "*" + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", "keywords": [ - "Xdebug", - "environment", - "hhvm" + "cli", + "command-line", + "console", + "terminal" ], "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "source": "https://github.com/symfony/console/tree/v7.4.16" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2026-07-31T12:37:14+00:00" }, { - "name": "sebastian/exporter", - "version": "4.0.5", + "name": "symfony/filesystem", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "url": "https://github.com/symfony/filesystem.git", + "reference": "ff16a16bf87fdf264638b8f6995b3515975e3c79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/ff16a16bf87fdf264638b8f6995b3515975e3c79", + "reference": "ff16a16bf87fdf264638b8f6995b3515975e3c79", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "symfony/process": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/symfony/filesystem/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2026-07-22T07:36:05+00:00" }, { - "name": "sebastian/global-state", - "version": "5.0.5", + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=7.2" }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" + "provide": { + "ext-ctype": "*" }, "suggest": { - "ext-uopz": "*" + "ext-ctype": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", "keywords": [ - "global state" + "compatibility", + "ctype", + "polyfill", + "portable" ], "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { - "name": "sebastian/lines-of-code", - "version": "1.0.3", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.41.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { - "name": "sebastian/object-enumerator", - "version": "4.0.4", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.38.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { - "name": "sebastian/object-reflector", - "version": "2.0.4", + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { - "php": ">=7.3" + "ext-iconv": "*", + "php": ">=7.2" }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "classmap": [ - "src/" - ] + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { - "name": "sebastian/recursion-context", - "version": "4.0.4", + "name": "symfony/polyfill-php84", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" + "php": ">=7.2" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.0-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, "classmap": [ - "src/" + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Adam Harvey", - "email": "aharvey@php.net" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.3", + "name": "symfony/service-contracts", + "version": "v3.7.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, - "require-dev": { - "phpunit/phpunit": "^9.0" + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.7-dev" } }, "autoload": { - "classmap": [ - "src/" + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { - "name": "sebastian/type", - "version": "3.2.0", + "name": "symfony/string", + "version": "v7.4.15", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "url": "https://github.com/symfony/string.git", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, "autoload": { - "classmap": [ - "src/" + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { - "name": "sebastian/version", - "version": "3.0.2", + "name": "theseer/tokenizer", + "version": "1.3.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { - "php": ">=7.3" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, "autoload": { "classmap": [ "src/" @@ -2267,130 +5310,207 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/theseer", "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "3.7.1", + "name": "vimeo/psalm", + "version": "6.16.1", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + "url": "https://github.com/vimeo/psalm.git", + "reference": "f1f5de594dc76faf8784e02d3dc4716c91c6f6ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/f1f5de594dc76faf8784e02d3dc4716c91c6f6ac", + "reference": "f1f5de594dc76faf8784e02d3dc4716c91c6f6ac", "shasum": "" }, "require": { + "amphp/amp": "^3", + "amphp/byte-stream": "^2", + "amphp/parallel": "^2.3", + "composer-runtime-api": "^2", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "composer/xdebug-handler": "^2.0 || ^3.0", + "danog/advanced-json-rpc": "^3.1", + "dnoegel/php-xdg-base-dir": "^0.1.1", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", "ext-simplexml": "*", "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" + "felixfbecker/language-server-protocol": "^1.5.3", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", + "netresearch/jsonmapper": "^5.0", + "nikic/php-parser": "^5.0.0", + "php": "~8.1.31 || ~8.2.27 || ~8.3.16 || ~8.4.3 || ~8.5.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "spatie/array-to-xml": "^2.17.0 || ^3.0", + "symfony/console": "^6.0 || ^7.0 || ^8.0", + "symfony/filesystem": "~6.3.12 || ~6.4.3 || ^7.0.3 || ^8.0", + "symfony/polyfill-php84": "^1.31.0" + }, + "provide": { + "psalm/psalm": "self.version" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "amphp/phpunit-util": "^3", + "bamarni/composer-bin-plugin": "^1.4", + "brianium/paratest": "^6.9", + "danog/class-finder": "^0.4.8", + "dg/bypass-finals": "^1.5", + "ext-curl": "*", + "mockery/mockery": "^1.5", + "nunomaduro/mock-final-classes": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpdoc-parser": "^1.6", + "phpunit/phpunit": "^9.6", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.19", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.6", + "symfony/process": "^6.0 || ^7.0 || ^8.0" + }, + "suggest": { + "ext-curl": "In order to send data to shepherd", + "ext-igbinary": "^2.0.5 is required, used to serialize caching data" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "psalm", + "psalm-language-server", + "psalm-plugin", + "psalm-refactor", + "psalm-review", + "psalter" ], - "type": "library", + "type": "project", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev", + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-5.x": "5.x-dev", + "dev-6.x": "6.x-dev", + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psalm\\": "src/Psalm/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Greg Sherwood", - "role": "lead" + "name": "Matthew Brown" + }, + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "description": "A static analysis tool for finding errors in PHP applications", "keywords": [ - "phpcs", - "standards" + "code", + "inspection", + "php", + "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" }, - "time": "2022-06-18T07:21:10+00:00" + "time": "2026-03-19T10:56:09+00:00" }, { - "name": "theseer/tokenizer", - "version": "1.2.1", + "name": "webmozart/assert", + "version": "2.4.1", "source": { "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "url": "https://github.com/webmozarts/assert.git", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/2ccb7c2e821038c03a3e6e1700c570c158c55f70", + "reference": "2ccb7c2e821038c03a3e6e1700c570c158c55f70", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.2 || ^8.0" + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, "autoload": { - "classmap": [ - "src/" - ] + "psr-4": { + "Webmozart\\Assert\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.4.1" }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2026-06-15T15:31:57+00:00" } ], "aliases": [], @@ -2399,8 +5519,11 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.0" + "php": "^8.2" }, "platform-dev": {}, + "platform-overrides": { + "php": "8.2.27" + }, "plugin-api-version": "2.6.0" } diff --git a/package-lock.json b/package-lock.json index dc179e80..b35ab47b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "@seamapi/php", - "version": "3.5.1", + "version": "4.0.0-beta.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@seamapi/php", - "version": "3.5.1", + "version": "4.0.0-beta.19", "license": "MIT", "devDependencies": { "@prettier/plugin-php": "^0.25.0", - "@seamapi/blueprint": "^1.2.0", + "@seamapi/blueprint": "^1.8.0", + "@seamapi/fake-seam-connect": "2.0.4", "@seamapi/smith": "^1.1.0", - "@seamapi/types": "1.985.0", + "@seamapi/types": "1.1001.0", "change-case": "^5.4.4", "execa": "^10.0.1", "prettier": "^3.9.6" @@ -805,9 +806,9 @@ "license": "MIT" }, "node_modules/@seamapi/blueprint": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@seamapi/blueprint/-/blueprint-1.2.0.tgz", - "integrity": "sha512-+fomtVD/VGJavEpOtVZD+3ZxUYgTXAS4DreNqbsNl0R1qS0wiIdJ40AmE9dgLnY1cHCfPNBmf1z//FdvCqHhrw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@seamapi/blueprint/-/blueprint-1.8.0.tgz", + "integrity": "sha512-NUghBmYaKreBeBxwPIB2O9hjIFZtEjVj73tAsuKdJR8t4BxlKK1I0XDQXxo3ZsH2QezNlbdo9/0MoX/33VXuhQ==", "dev": true, "license": "MIT", "dependencies": { @@ -815,10 +816,28 @@ "zod": "^3.23.8" }, "engines": { - "node": ">=22.11.0", + "node": ">=22.12.0", "npm": ">=10.0.0" } }, + "node_modules/@seamapi/fake-seam-connect": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@seamapi/fake-seam-connect/-/fake-seam-connect-2.0.4.tgz", + "integrity": "sha512-pgPUhIMW462B3jIWTuhH/aK2wUxRuOLjhsjB7YxdXw2g+hTVRJ5w4t01spETu1g2bch3Rzffd0whUVLeC4vjwQ==", + "dev": true, + "license": "MIT", + "bin": { + "fake-seam-connect": "dist/server.js" + }, + "engines": { + "node": ">=22.12.0", + "npm": ">=10.0.0" + }, + "optionalDependencies": { + "zustand": "^4.3.7", + "zustand-hoist": "^2.0.0" + } + }, "node_modules/@seamapi/smith": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@seamapi/smith/-/smith-1.1.0.tgz", @@ -853,14 +872,14 @@ } }, "node_modules/@seamapi/types": { - "version": "1.985.0", - "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.985.0.tgz", - "integrity": "sha512-3+aFZXav6zQ9mPsl6FLR0TuMBbu3u3kC16OoEjNHZbTASj1eOYTAjb2lUR8VjFAaR1iZi/rNcIPBRfX/xaWbfg==", + "version": "1.1001.0", + "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.1001.0.tgz", + "integrity": "sha512-pwIEqMYCdOLlIHUzzLlMq/4K6QwAM3kWXooSxNWOlPyCeWk21SvrbTN/joXwZtky504g3unLPKMbFad1mlQyfQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=22.11.0", - "npm": ">=10.9.4" + "node": ">=22.12.0", + "npm": ">=10.0.0" }, "peerDependencies": { "zod": "^3.24.0" @@ -4881,6 +4900,18 @@ "license": "MIT", "peer": true }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5866,6 +5897,17 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "dev": true, + "license": "MIT", + "optional": true, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/ware": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", @@ -6065,6 +6107,51 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/zustand-hoist": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/zustand-hoist/-/zustand-hoist-2.0.1.tgz", + "integrity": "sha512-Lhvv3RlLQx1NSUtuhk8jegXe1Wyav9RAOnLd4CRs1SbB5qcFoarAGQTE43vIxXizrm1UQJl1q5uRbOZuXGXGpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18.12.0", + "npm": ">= 9.0.0" + }, + "peerDependencies": { + "zustand": ">=4.0.0" + } } } } diff --git a/package.json b/package.json index da30a5a8..03d0f79e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@seamapi/php", - "version": "3.5.1", + "version": "4.0.0-beta.19", "type": "module", "private": true, "license": "MIT", @@ -13,7 +13,8 @@ "lint": "eslint .", "postlint": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore .", "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore .", - "preformat": "eslint --fix ." + "preformat": "eslint --fix .", + "start": "fake-seam-connect --seed" }, "engines": { "node": ">=22.11.0", @@ -29,11 +30,13 @@ "version": "^11.0.0 || ^10.9.4" } }, + "packageManager": "npm@11.19.0", "devDependencies": { "@prettier/plugin-php": "^0.25.0", - "@seamapi/blueprint": "^1.2.0", + "@seamapi/blueprint": "^1.8.0", + "@seamapi/fake-seam-connect": "2.0.4", "@seamapi/smith": "^1.1.0", - "@seamapi/types": "1.985.0", + "@seamapi/types": "1.1001.0", "change-case": "^5.4.4", "execa": "^10.0.1", "prettier": "^3.9.6" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e953736f..d086a803 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,11 +1,12 @@ @@ -14,9 +15,16 @@ tests - + src - + + + src/Resources + src/Routes + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 00000000..f3cb128b --- /dev/null +++ b/psalm.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/src/Exceptions/ActionAttemptError.php b/src/ActionAttemptError.php similarity index 71% rename from src/Exceptions/ActionAttemptError.php rename to src/ActionAttemptError.php index 26530d06..776ee6fe 100644 --- a/src/Exceptions/ActionAttemptError.php +++ b/src/ActionAttemptError.php @@ -4,14 +4,16 @@ use Seam\Resources\ActionAttempt; -class ActionAttemptError extends \Exception +/** + * Base class for the errors raised while resolving an action attempt. + */ +class ActionAttemptError extends \RuntimeException implements SeamException { private ActionAttempt $actionAttempt; public function __construct(string $message, ActionAttempt $actionAttempt) { parent::__construct($message); - $this->name = get_class($this); $this->actionAttempt = $actionAttempt; } diff --git a/src/ActionAttemptFailedError.php b/src/ActionAttemptFailedError.php new file mode 100644 index 00000000..a69311f5 --- /dev/null +++ b/src/ActionAttemptFailedError.php @@ -0,0 +1,33 @@ +error->message ?? "Action attempt failed", + $actionAttempt, + ); + $this->errorCode = $actionAttempt->error->type ?? "unknown_error"; + } + + /** + * The action attempt error type. + * + * Named `getErrorCode` rather than `getCode` because `Exception::getCode` + * is final and returns an int. + */ + public function getErrorCode(): string + { + return $this->errorCode; + } +} diff --git a/src/Exceptions/ActionAttemptTimeoutError.php b/src/ActionAttemptTimeoutError.php similarity index 67% rename from src/Exceptions/ActionAttemptTimeoutError.php rename to src/ActionAttemptTimeoutError.php index d0b6a343..5d2f3d4d 100644 --- a/src/Exceptions/ActionAttemptTimeoutError.php +++ b/src/ActionAttemptTimeoutError.php @@ -4,6 +4,12 @@ use Seam\Resources\ActionAttempt; +/** + * Raised when an action attempt does not finish within the timeout. + * + * The action attempt it carries is the last one observed, which is still + * pending. + */ class ActionAttemptTimeoutError extends ActionAttemptError { public function __construct(ActionAttempt $actionAttempt, float $timeout) @@ -12,6 +18,5 @@ public function __construct(ActionAttempt $actionAttempt, float $timeout) "Timed out waiting for action attempt after {$timeout}s", $actionAttempt, ); - $this->name = get_class($this); } } diff --git a/src/Auth.php b/src/Auth.php new file mode 100644 index 00000000..55151b89 --- /dev/null +++ b/src/Auth.php @@ -0,0 +1,184 @@ + + */ + public static function get_auth_headers( + ?string $api_key = null, + ?string $personal_access_token = null, + ?string $workspace_id = null, + ): array { + // The environment is only consulted when no credential was passed at + // all, so an explicit personal access token is not second guessed by + // a stray SEAM_API_KEY. + if ($api_key === null && $personal_access_token === null) { + $api_key = Options::get_env("SEAM_API_KEY"); + $personal_access_token = Options::get_env( + "SEAM_PERSONAL_ACCESS_TOKEN", + ); + + if ($api_key !== null && $personal_access_token !== null) { + throw new InvalidOptionsError( + "Both SEAM_API_KEY and SEAM_PERSONAL_ACCESS_TOKEN environment variables are defined. " . + "Please use only one authentication method.", + ); + } + } + + $workspace_id ??= Options::get_env("SEAM_WORKSPACE_ID"); + + if ( + Options::is_seam_options_with_api_key( + $api_key, + $personal_access_token, + ) + ) { + return self::get_auth_headers_for_api_key($api_key); + } + + if ( + Options::is_seam_options_with_personal_access_token( + $personal_access_token, + $api_key, + $workspace_id, + ) + ) { + return self::get_auth_headers_for_personal_access_token( + $personal_access_token, + $workspace_id, + ); + } + + throw new InvalidOptionsError( + "Must specify an api_key or personal_access_token. " . + "Attempted reading configuration from the environment, but neither the " . + "SEAM_API_KEY nor the SEAM_PERSONAL_ACCESS_TOKEN environment variable is set.", + ); + } + + /** + * Builds the headers for a client that is not scoped to a workspace, + * falling back to the environment when no token is given. + * + * @return array + */ + public static function get_auth_headers_without_workspace( + ?string $personal_access_token = null, + ): array { + $personal_access_token ??= Options::get_env( + "SEAM_PERSONAL_ACCESS_TOKEN", + ); + + if ($personal_access_token === null) { + throw new InvalidOptionsError( + "Must specify a personal_access_token. " . + "Attempted reading configuration from the environment, " . + "but the environment variable SEAM_PERSONAL_ACCESS_TOKEN is not set.", + ); + } + + return self::get_auth_headers_for_personal_access_token_without_workspace( + $personal_access_token, + ); + } + + /** + * @return array + */ + public static function get_auth_headers_for_api_key(string $api_key): array + { + if (Token::is_client_session_token($api_key)) { + throw new InvalidTokenError( + "A Client Session Token cannot be used as an api_key", + ); + } + + if (Token::is_jwt($api_key)) { + throw new InvalidTokenError("A JWT cannot be used as an api_key"); + } + + if (Token::is_access_token($api_key)) { + throw new InvalidTokenError( + "An Access Token cannot be used as an api_key", + ); + } + + if (Token::is_publishable_key($api_key)) { + throw new InvalidTokenError( + "A Publishable Key cannot be used as an api_key", + ); + } + + if (!Token::is_seam_token($api_key)) { + throw new InvalidTokenError("Unknown or invalid api_key format"); + } + + return ["authorization" => "Bearer " . $api_key]; + } + + /** + * @return array + */ + public static function get_auth_headers_for_personal_access_token( + string $personal_access_token, + string $workspace_id, + ): array { + self::assert_personal_access_token($personal_access_token); + + return [ + "authorization" => "Bearer " . $personal_access_token, + "seam-workspace" => $workspace_id, + ]; + } + + /** + * Headers for a personal access token that is not scoped to a workspace. + * + * @return array + */ + public static function get_auth_headers_for_personal_access_token_without_workspace( + string $personal_access_token, + ): array { + self::assert_personal_access_token($personal_access_token); + + return ["authorization" => "Bearer " . $personal_access_token]; + } + + private static function assert_personal_access_token(string $token): void + { + if (Token::is_client_session_token($token)) { + throw new InvalidTokenError( + "A Client Session Token cannot be used as a personal_access_token", + ); + } + + if (Token::is_jwt($token)) { + throw new InvalidTokenError( + "A JWT cannot be used as a personal_access_token", + ); + } + + if (Token::is_publishable_key($token)) { + throw new InvalidTokenError( + "A Publishable Key cannot be used as a personal_access_token", + ); + } + + if (!Token::is_access_token($token)) { + throw new InvalidTokenError( + "Unknown or invalid personal_access_token format", + ); + } + } +} diff --git a/src/Exceptions/ActionAttemptFailedError.php b/src/Exceptions/ActionAttemptFailedError.php deleted file mode 100644 index 59e307fe..00000000 --- a/src/Exceptions/ActionAttemptFailedError.php +++ /dev/null @@ -1,22 +0,0 @@ -error->message, $actionAttempt); - $this->name = get_class($this); - $this->errorCode = $actionAttempt->error->type; - } - - public function getErrorCode(): string - { - return $this->errorCode; - } -} diff --git a/src/Exceptions/HttpUnauthorizedError.php b/src/Exceptions/HttpUnauthorizedError.php deleted file mode 100644 index 921ee2eb..00000000 --- a/src/Exceptions/HttpUnauthorizedError.php +++ /dev/null @@ -1,15 +0,0 @@ - "unauthorized", - "message" => "Unauthorized", - ]; - parent::__construct($error, 401, $requestId); - } -} diff --git a/src/Http/Body.php b/src/Http/Body.php new file mode 100644 index 00000000..47ccc2d6 --- /dev/null +++ b/src/Http/Body.php @@ -0,0 +1,93 @@ +getBody(); + + if ($body->isSeekable()) { + $body->rewind(); + } + + $contents = $body->getContents(); + + if ($contents === "") { + return null; + } + + try { + return Utils::jsonDecode($contents); + } catch (\InvalidArgumentException) { + return null; + } + } + + /** + * Reads the resource an endpoint returns out of its response envelope. + * + * @throws InvalidResponseError If the envelope does not carry the key + */ + public static function read(mixed $res, string $key, string $path): mixed + { + if (!is_object($res)) { + throw new InvalidResponseError( + $path, + $key, + "got " . get_debug_type($res) . " instead of a response object", + ); + } + + if (!property_exists($res, $key)) { + throw new InvalidResponseError( + $path, + $key, + "which the response does not contain", + ); + } + + return $res->$key; + } + + /** + * Reads a list of resources out of a response envelope. + * + * @return array + * + * @throws InvalidResponseError If the envelope does not carry a list + */ + public static function read_list( + mixed $res, + string $key, + string $path, + ): array { + $value = self::read($res, $key, $path); + + if (!is_array($value)) { + throw new InvalidResponseError( + $path, + $key, + "got " . get_debug_type($value) . " instead of a list", + ); + } + + return $value; + } +} diff --git a/src/Http/ClientFactory.php b/src/Http/ClientFactory.php new file mode 100644 index 00000000..77bffdb2 --- /dev/null +++ b/src/Http/ClientFactory.php @@ -0,0 +1,113 @@ + $auth_headers + * @param array $guzzle_options + */ + public static function create( + string $endpoint, + array $auth_headers, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null, + ): Client { + $retries ??= self::DEFAULT_RETRIES; + $timeout ??= self::DEFAULT_TIMEOUT; + + // Middleware has to be on the handler stack the client is built with: + // a stack pushed onto after construction does not apply. + $handler = $guzzle_options["handler"] ?? HandlerStack::create(); + + if ($handler instanceof HandlerStack) { + // Cloned so that building a client does not mutate a stack the + // caller may reuse, which would stack this middleware twice. + $handler = clone $handler; + } else { + // A bare handler, such as a MockHandler, is wrapped so the + // middleware below still applies to it. + $handler = HandlerStack::create($handler); + } + + self::add_middleware($handler, $retries); + + $headers = array_merge( + $auth_headers, + $guzzle_options["headers"] ?? [], + self::sdk_headers(), + ); + + return new Client( + array_merge( + [ + "base_uri" => $endpoint, + "timeout" => $timeout, + "connect_timeout" => $timeout, + ], + $guzzle_options, + [ + "handler" => $handler, + "headers" => $headers, + // ErrorMiddleware raises instead, so that a Seam error + // response becomes a Seam exception. + "http_errors" => false, + ], + ), + ); + } + + /** + * Adds the Seam error mapping and retry middleware to a handler stack. + * + * Build the client with the stack, and with `http_errors` disabled so + * the error middleware raises instead of Guzzle. + * + * @param int|null $retries Defaults to self::DEFAULT_RETRIES. + */ + public static function add_middleware( + HandlerStack $handler, + ?int $retries = null, + ): void { + // Unshifted so it sits outside every other middleware and only sees + // a response none of them could act on: a redirect is followed + // rather than raised, and a retried request is judged by the + // response it finally settled on. + $handler->unshift(ErrorMiddleware::create(), "seam_error"); + RetryMiddleware::add($handler, $retries ?? self::DEFAULT_RETRIES); + } + + /** + * @return array + */ + private static function sdk_headers(): array + { + $version = Version::get(); + + return [ + "seam-sdk-name" => "seamapi/php", + "seam-sdk-version" => $version, + ]; + } +} diff --git a/src/Http/ErrorMiddleware.php b/src/Http/ErrorMiddleware.php new file mode 100644 index 00000000..d4c4c506 --- /dev/null +++ b/src/Http/ErrorMiddleware.php @@ -0,0 +1,111 @@ + static fn( + RequestInterface $request, + array $options, + ): PromiseInterface => $handler($request, $options)->then( + static function (ResponseInterface $response) use ( + $request, + ): ResponseInterface { + $status_code = $response->getStatusCode(); + + if ($status_code >= 200 && $status_code < 300) { + return $response; + } + + throw self::to_exception($request, $response); + }, + ); + } + + private static function to_exception( + RequestInterface $request, + ResponseInterface $response, + ): \Throwable { + $status_code = $response->getStatusCode(); + $request_id = self::get_request_id($response); + + if ($status_code === 401) { + return new HttpUnauthorizedError($request_id); + } + + $error = self::get_error($response); + + if ($error === null) { + return BadResponseException::create($request, $response); + } + + if (($error->type ?? null) === "invalid_input") { + return new HttpInvalidInputError($error, $status_code, $request_id); + } + + return new HttpApiError($error, $status_code, $request_id); + } + + /** + * The error from a Seam error envelope, i.e. JSON holding an `error` + * object with a string `type` and `message`, or null when the response is + * not one. + */ + private static function get_error(ResponseInterface $response): ?object + { + if ( + !str_starts_with( + $response->getHeaderLine("content-type"), + "application/json", + ) + ) { + return null; + } + + $body = Body::decode($response); + + if (!is_object($body)) { + return null; + } + + $error = $body->error ?? null; + + if (!is_object($error)) { + return null; + } + + return is_string($error->type ?? null) && + is_string($error->message ?? null) + ? $error + : null; + } + + private static function get_request_id(ResponseInterface $response): ?string + { + return $response->hasHeader("seam-request-id") + ? $response->getHeaderLine("seam-request-id") + : null; + } +} diff --git a/src/Http/ResolveActionAttempt.php b/src/Http/ResolveActionAttempt.php new file mode 100644 index 00000000..90c3db54 --- /dev/null +++ b/src/Http/ResolveActionAttempt.php @@ -0,0 +1,127 @@ +status === "success") { + return $action_attempt; + } + + if ($action_attempt->status === "error") { + throw new ActionAttemptFailedError($action_attempt); + } + + $remaining = $deadline - self::now(); + + if ($remaining <= 0.0) { + throw new ActionAttemptTimeoutError($action_attempt, $timeout); + } + + usleep((int) (min($polling_interval, $remaining) * 1000000.0)); + + $action_attempt = self::get_action_attempt( + $client, + $action_attempt->action_attempt_id, + ); + } + } + + private static function get_action_attempt( + ClientInterface $client, + string $action_attempt_id, + ): ActionAttempt { + $res = Body::decode( + $client->request("GET", "/action_attempts/get", [ + "query" => ["action_attempt_id" => $action_attempt_id], + ]), + ); + + $action_attempt = ActionAttempt::from_json( + Body::read($res, "action_attempt", "/action_attempts/get"), + ); + + if ($action_attempt === null) { + throw new InvalidResponseError( + "/action_attempts/get", + "action_attempt", + "which was empty for {$action_attempt_id}", + ); + } + + return $action_attempt; + } + + private static function now(): float + { + return microtime(true); + } +} diff --git a/src/Http/RetryMiddleware.php b/src/Http/RetryMiddleware.php new file mode 100644 index 00000000..7d32f4b7 --- /dev/null +++ b/src/Http/RetryMiddleware.php @@ -0,0 +1,64 @@ +push( + GuzzleRetryMiddleware::factory([ + "retry_enabled" => $retries > 0, + "max_retry_attempts" => max(0, $retries), + "retry_on_methods" => self::IDEMPOTENT_METHODS, + "retry_on_timeout" => true, + "retry_on_status" => array_merge([429], range(500, 599)), + // Delay is applied by the callback below so Retry-After can + // be compared with, rather than replace, the jittered delay. + "default_retry_multiplier" => 0.0, + "on_retry_callback" => static function ( + int $retry_count, + float $retry_after, + RequestInterface &$request, + array &$options, + ): void { + $backoff = + self::INITIAL_DELAY_SECONDS * + 2.0 ** (float) ($retry_count - 1); + $jittered_backoff = random_int( + (int) ($backoff * 1000.0), + (int) ($backoff * self::JITTER_MULTIPLIER * 1000.0), + ); + + $options["delay"] = max( + $jittered_backoff, + (int) ceil($retry_after * 1000.0), + ); + }, + ]), + "seam_retry", + ); + } +} diff --git a/src/Http/SerializingClient.php b/src/Http/SerializingClient.php new file mode 100644 index 00000000..7e58742f --- /dev/null +++ b/src/Http/SerializingClient.php @@ -0,0 +1,117 @@ +client->send($request, self::serialize_options($options)); + } + + #[\Override] + public function sendAsync( + RequestInterface $request, + array $options = [], + ): PromiseInterface { + return $this->client->sendAsync( + $request, + self::serialize_options($options), + ); + } + + #[\Override] + public function request( + string $method, + $uri = "", + array $options = [], + ): ResponseInterface { + return $this->client->request( + $method, + $uri, + self::serialize_options($options), + ); + } + + #[\Override] + public function requestAsync( + string $method, + $uri = "", + array $options = [], + ): PromiseInterface { + return $this->client->requestAsync( + $method, + $uri, + self::serialize_options($options), + ); + } + + #[\Override] + public function getConfig(?string $option = null) + { + return $this->client->getConfig($option); + } + + /** + * @param array $options + * @return array + */ + private static function serialize_options(array $options): array + { + if ( + isset($options["query"]) && + ($options["query"] instanceof \stdClass || + is_array($options["query"])) + ) { + $serialized = StrictUrlSearchParamsSerializer::serialize( + $options["query"], + ); + + if ($serialized === "") { + unset($options["query"]); + } else { + $options["query"] = $serialized; + } + } + + if (array_key_exists("json", $options)) { + $options["json"] = NullValue::replace($options["json"]); + } + + return $options; + } +} diff --git a/src/Exceptions/HttpApiError.php b/src/HttpApiError.php similarity index 50% rename from src/Exceptions/HttpApiError.php rename to src/HttpApiError.php index a79a8dd5..23634d6b 100644 --- a/src/Exceptions/HttpApiError.php +++ b/src/HttpApiError.php @@ -2,26 +2,31 @@ namespace Seam; -class HttpApiError extends \Exception +/** + * Raised when the Seam API returns an error response. + */ +class HttpApiError extends \RuntimeException implements SeamException { - private string $errorCode; + protected string $errorCode; private int $statusCode; - private string $requestId; - private ?object $data = null; + private ?string $requestId; + private mixed $data; public function __construct( object $error, int $statusCode, - string $requestId, + ?string $requestId, ) { - $message = $error->message ?? "Unknown error"; - parent::__construct($message); - $this->errorCode = $error->type ?? "unknown"; + parent::__construct($error->message ?? "Unknown error"); + $this->errorCode = $error->type ?? "unknown_error"; $this->statusCode = $statusCode; $this->requestId = $requestId; $this->data = $error->data ?? null; } + /** + * The Seam error type, e.g. `device_not_found`. + */ public function getErrorCode(): string { return $this->errorCode; @@ -32,7 +37,10 @@ public function getStatusCode(): int return $this->statusCode; } - public function getRequestId(): string + /** + * The `seam-request-id` response header, or null when absent. + */ + public function getRequestId(): ?string { return $this->requestId; } diff --git a/src/Exceptions/HttpInvalidInputError.php b/src/HttpInvalidInputError.php similarity index 68% rename from src/Exceptions/HttpInvalidInputError.php rename to src/HttpInvalidInputError.php index 38cd8d74..1ff84ac6 100644 --- a/src/Exceptions/HttpInvalidInputError.php +++ b/src/HttpInvalidInputError.php @@ -2,6 +2,9 @@ namespace Seam; +/** + * Raised when the Seam API rejects the request parameters. + */ class HttpInvalidInputError extends HttpApiError { private object $validationErrors; @@ -9,13 +12,19 @@ class HttpInvalidInputError extends HttpApiError public function __construct( object $error, int $statusCode, - string $requestId, + ?string $requestId, ) { parent::__construct($error, $statusCode, $requestId); $this->errorCode = "invalid_input"; $this->validationErrors = $error->validation_errors ?? (object) []; } + /** + * The validation messages for a request parameter, or an empty array when + * that parameter has none. + * + * @return string[] + */ public function getValidationErrorMessages(string $paramName): array { return $this->validationErrors->{$paramName}->_errors ?? []; diff --git a/src/HttpUnauthorizedError.php b/src/HttpUnauthorizedError.php new file mode 100644 index 00000000..386cba38 --- /dev/null +++ b/src/HttpUnauthorizedError.php @@ -0,0 +1,21 @@ + "unauthorized", + "message" => "Unauthorized", + ], + 401, + $requestId, + ); + } +} diff --git a/src/InvalidOptionsError.php b/src/InvalidOptionsError.php new file mode 100644 index 00000000..d3fd1d84 --- /dev/null +++ b/src/InvalidOptionsError.php @@ -0,0 +1,16 @@ +path; + } + + /** + * The response key that should have carried the resource. + */ + public function getKey(): string + { + return $this->key; + } +} diff --git a/src/InvalidTokenError.php b/src/InvalidTokenError.php new file mode 100644 index 00000000..0a1f881a --- /dev/null +++ b/src/InvalidTokenError.php @@ -0,0 +1,15 @@ + NullValue::NULL, "limit" => 20]); + * // => 'limit=20&name=' + * + * UrlSearchParamsSerializer::serialize(["name" => null, "limit" => 20]); + * // => 'limit=20' + * ``` + * + * Use it wherever the Seam API documents null as a meaningful value, e.g., + * to unset a value in an update request, or to filter by an unset value. + */ +enum NullValue +{ + /** + * Sentinel for a param explicitly set to null. + */ + case NULL; + + /** + * Returns a copy of a value with every NullValue::NULL replaced by null. + * + * The sentinel only distinguishes an explicit null from an omitted param + * within this SDK. Once a request body is being serialized, the param is + * known to be present, so the sentinel becomes the null that JSON has. + * + * Recurses into arrays and stdClass objects without mutating them; every + * other value is returned unchanged. + */ + public static function replace(mixed $value): mixed + { + if ($value instanceof self) { + return null; + } + + if (is_array($value)) { + return array_map(self::replace(...), $value); + } + + if ($value instanceof \stdClass) { + return (object) array_map( + self::replace(...), + get_object_vars($value), + ); + } + + return $value; + } +} diff --git a/src/Options.php b/src/Options.php new file mode 100644 index 00000000..35c75743 --- /dev/null +++ b/src/Options.php @@ -0,0 +1,109 @@ + $options The other options by name, where + * null (or, for an options array, empty) means the option was not given. + */ + public static function check_client_options( + ?object $client, + array $options, + ): void { + if ($client === null) { + return; + } + + foreach ($options as $name => $value) { + if ($value !== null && $value !== []) { + throw new InvalidOptionsError( + "The {$name} option cannot be used with the client option", + ); + } + } + } + + /** + * Reads an environment variable, treating an empty value as unset so that + * an exported-but-blank variable does not override the default. + */ + public static function get_env(string $name): ?string + { + $value = getenv($name); + + if ($value === false || $value === "") { + return null; + } + + return $value; + } + + private static function warn(string $message): void + { + trigger_error($message, E_USER_WARNING); + } +} diff --git a/src/Pagination.php b/src/Pagination.php new file mode 100644 index 00000000..1480ec40 --- /dev/null +++ b/src/Pagination.php @@ -0,0 +1,32 @@ +has_next_page ?? false), + next_page_cursor: $json->next_page_cursor ?? null, + next_page_url: $json->next_page_url ?? null, + ); + } +} diff --git a/src/Paginator.php b/src/Paginator.php index a913cf80..b9582d8d 100644 --- a/src/Paginator.php +++ b/src/Paginator.php @@ -2,12 +2,17 @@ namespace Seam; +/** + * Fetches and walks the pages of a list endpoint. + * + * Create one with `Seam::createPaginator`, passing a callable that invokes the + * list method with a params array. + */ class Paginator { private $request; - private $params; - private $pagination_cache = []; - private const FIRST_PAGE = "FIRST_PAGE"; + private array $params; + private ?Pagination $pagination = null; public function __construct(callable $request, array $params = []) { @@ -15,59 +20,81 @@ public function __construct(callable $request, array $params = []) $this->params = $params; } + /** + * @return array{0: array, 1: Pagination} + */ public function firstPage(): array { - $request = $this->request; - $params = $this->params; - - $params["on_response"] = fn($response) => $this->cachePagination( - $response, - self::FIRST_PAGE, - ); - - $data = $request($params); - - return [$data, $this->pagination_cache[self::FIRST_PAGE]]; + return $this->fetchPage(null); } - public function nextPage(string $next_page_cursor): array + /** + * @return array{0: array, 1: Pagination} + */ + public function nextPage(?string $next_page_cursor): array { - if ($next_page_cursor === null) { + if ($next_page_cursor === null || $next_page_cursor === "") { throw new \InvalidArgumentException( - "Cannot get the next page with a null next_page_cursor", + "Cannot get the next page without a next_page_cursor", ); } + return $this->fetchPage($next_page_cursor); + } + + /** + * @return array{0: array, 1: Pagination} + */ + private function fetchPage(?string $cursor): array + { $request = $this->request; $params = $this->params; - $params["page_cursor"] = $next_page_cursor; - $params["on_response"] = fn($response) => $this->cachePagination( - $response, - $next_page_cursor, - ); + if ($cursor !== null) { + $params["page_cursor"] = $cursor; + } + + // Chained rather than replaced, so a callback the caller passed in + // through the params still fires. + $on_response = $params["on_response"] ?? null; + + $this->pagination = null; + + $params["on_response"] = function ($response) use ($on_response): void { + $this->readPagination($response); + + if (is_callable($on_response)) { + $on_response($response); + } + }; $data = $request($params); - return [$data, $this->pagination_cache[$next_page_cursor]]; + if ($this->pagination === null) { + throw new \InvalidArgumentException( + "Cannot use a paginator with an unpaginated endpoint", + ); + } + + return [$data, $this->pagination]; } - private function cachePagination($response, $next_page_cursor) + private function readPagination($response): void { - $this->pagination_cache[$next_page_cursor] = $response->pagination; + if (!is_object($response) || !isset($response->pagination)) { + throw new \InvalidArgumentException( + "Cannot use a paginator with an unpaginated endpoint", + ); + } + + $this->pagination = Pagination::from_json($response->pagination); } public function flattenToArray(): array { $items = []; - [$response, $pagination] = $this->firstPage(); - $items = array_merge($items, $response); - - while ($pagination->has_next_page) { - [$response, $pagination] = $this->nextPage( - $pagination->next_page_cursor, - ); + foreach ($this->walk() as [$response]) { $items = array_merge($items, $response); } @@ -76,20 +103,35 @@ public function flattenToArray(): array public function flatten() { - [$current, $pagination] = $this->firstPage(); - - foreach ($current as $item) { - yield $item; + foreach ($this->walk() as [$response]) { + foreach ($response as $item) { + yield $item; + } } + } - while ($pagination->has_next_page) { - [$current, $pagination] = $this->nextPage( - $pagination->next_page_cursor, - ); + /** + * @return \Generator + */ + private function walk(): \Generator + { + $page = $this->firstPage(); + $seen = []; - foreach ($current as $item) { - yield $item; + yield $page; + + while ($page[1]->has_next_page) { + $cursor = $page[1]->next_page_cursor; + + if ($cursor === null || isset($seen[$cursor])) { + return; } + + $seen[$cursor] = true; + + $page = $this->nextPage($cursor); + + yield $page; } } } diff --git a/src/Resources/AccessCode.php b/src/Resources/AccessCode.php index 3fe2aa9c..1188cccd 100644 --- a/src/Resources/AccessCode.php +++ b/src/Resources/AccessCode.php @@ -1,508 +1,2582 @@ access_code_id ?? null, - code: $json->code ?? null, - common_code_key: $json->common_code_key ?? null, - created_at: $json->created_at ?? null, - device_id: $json->device_id ?? null, - dormakaba_oracode_metadata: isset($json->dormakaba_oracode_metadata) - ? AccessCodeDormakabaOracodeMetadata::from_json( +namespace Seam\Resources { + /** + * Represents a smart lock [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). + * + * An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. Using the Seam Access Code API, you can easily generate access codes on the hundreds of door lock models with which we integrate. + * + * Seam supports programming two types of access codes: [ongoing](https://docs.seam.co/low-level-apis/smart-locks/access-codes#ongoing-access-codes) and [time-bound](https://docs.seam.co/low-level-apis/smart-locks/access-codes#time-bound-access-codes). To differentiate between the two, refer to the `type` property of the access code. Ongoing codes display as `ongoing`, whereas time-bound codes are labeled `time_bound`. An ongoing access code is active, until it has been removed from the device. To specify an ongoing access code, leave both `starts_at` and `ends_at` empty. A time-bound access code will be programmed at the `starts_at` time and removed at the `ends_at` time. + * + * In addition, for certain devices, Seam also supports [offline access codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes#offline-access-codes). Offline access (PIN) codes are designed for door locks that might not always maintain an internet connection. For this type of access code, the device manufacturer uses encryption keys (tokens) to create server-based registries of algorithmically-generated offline PIN codes. Because the tokens remain synchronized with the managed devices, the locks do not require an active internet connection—and you do not need to be near the locks—to create an offline access code. Then, owners or managers can share these offline codes with users through a variety of mechanisms, such as messaging applications. That is, lock users do not need to install a smartphone application to receive an offline access code. + * + * For granting a person access to a space, [Access Grants](https://docs.seam.co/use-cases/granting-access) are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. + */ + class AccessCode + { + public static function from_json(mixed $json): AccessCode|null + { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + code: $json->code ?? null, + common_code_key: $json->common_code_key ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AccessCode\Errors::from_json($e), + $json->errors ?? [], + ), + is_backup_access_code_available: $json->is_backup_access_code_available ?? + null, + is_external_modification_allowed: $json->is_external_modification_allowed ?? + null, + is_managed: $json->is_managed ?? null, + is_offline_access_code: $json->is_offline_access_code ?? null, + is_one_time_use: $json->is_one_time_use ?? null, + name: $json->name ?? null, + pending_mutations: array_map( + fn( + $p, + ) => \Seam\Resources\AccessCode\PendingMutations::from_json( + $p, + ), + $json->pending_mutations ?? [], + ), + status: $json->status ?? null, + type: $json->type ?? null, + warnings: array_map( + fn($w) => \Seam\Resources\AccessCode\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + dormakaba_oracode_metadata: isset( $json->dormakaba_oracode_metadata, ) - : null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => AccessCodeErrors::from_json($e), - $json->errors ?? [], - ), - is_backup: $json->is_backup ?? null, - is_backup_access_code_available: $json->is_backup_access_code_available ?? - null, - is_external_modification_allowed: $json->is_external_modification_allowed ?? - null, - is_managed: $json->is_managed ?? null, - is_offline_access_code: $json->is_offline_access_code ?? null, - is_one_time_use: $json->is_one_time_use ?? null, - is_scheduled_on_device: $json->is_scheduled_on_device ?? null, - is_waiting_for_code_assignment: $json->is_waiting_for_code_assignment ?? - null, - name: $json->name ?? null, - pending_mutations: array_map( - fn($p) => AccessCodePendingMutations::from_json($p), - $json->pending_mutations ?? [], - ), - pulled_backup_access_code_id: $json->pulled_backup_access_code_id ?? - null, - starts_at: $json->starts_at ?? null, - status: $json->status ?? null, - type: $json->type ?? null, - warnings: array_map( - fn($w) => AccessCodeWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Unique identifier for the access code. - */ - public string|null $access_code_id, - /** - * Code used for access. Typically, a numeric or alphanumeric string. - */ - public string|null $code, - /** - * Unique identifier for a group of access codes that share the same code. - */ - public string|null $common_code_key, - /** - * Date and time at which the access code was created. - */ - public string|null $created_at, - /** - * Unique identifier for the device associated with the access code. - */ - public string|null $device_id, - /** - * Metadata for a dormakaba Oracode managed access code. Only present for access codes from dormakaba Oracode devices. - */ - public AccessCodeDormakabaOracodeMetadata|null $dormakaba_oracode_metadata, - /** - * Date and time after which the time-bound access code becomes inactive. - */ - public string|null $ends_at, - /** - * Errors associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - */ - public array $errors, - /** - * Indicates whether the access code is a backup code. - */ - public bool|null $is_backup, - /** - * Indicates whether a backup access code is available for use if the primary access code is lost or compromised. - */ - public bool|null $is_backup_access_code_available, - /** - * Indicates whether changes to the access code from external sources are permitted. - */ - public bool|null $is_external_modification_allowed, - /** - * Indicates whether Seam manages the access code. - */ - public bool|null $is_managed, - /** - * Indicates whether the access code is intended for use in offline scenarios. If `true`, this code can be created on a device without a network connection. - */ - public bool|null $is_offline_access_code, - /** - * Indicates whether the access code can only be used once. If `true`, the code becomes invalid after the first use. - */ - public bool|null $is_one_time_use, - /** - * Indicates whether the code is set on the device according to a preconfigured schedule. - */ - public bool|null $is_scheduled_on_device, - /** - * Indicates whether the access code is waiting for a code assignment. - */ - public bool|null $is_waiting_for_code_assignment, - /** - * Name of the access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as `first_name` and `last_name`. To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called `appearance`. This is an object with a `name` property and, optionally, `first_name` and `last_name` properties (for providers that break down a name into components). - */ - public string|null $name, - /** - * Collection of pending mutations for the access code. Indicates changes that Seam is in the process of pushing to the device. - */ - public array $pending_mutations, - /** - * Identifier of the pulled backup access code. Used to associate the pulled backup access code with the original access code. - */ - public string|null $pulled_backup_access_code_id, - /** - * Date and time at which the time-bound access code becomes active. - */ - public string|null $starts_at, - /** - * Current status of the access code within the operational lifecycle. Values are `setting`, a transitional phase that indicates that the code is being configured or activated; `set`, which indicates that the code is active and operational; `unset`, which indicates a deactivated or unused state, either before activation or after deliberate deactivation; `removing`, which indicates a transitional period in which the code is being deleted or made inactive; and `unknown`, which indicates an indeterminate state, due to reasons such as system errors or incomplete data, that highlights a potential need for system review or troubleshooting. See also [Lifecycle of Access Codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes/lifecycle-of-access-codes). - */ - public string|null $status, - /** - * Type of the access code. `ongoing` access codes are active continuously until deactivated manually. `time_bound` access codes have a specific duration. - */ - public string|null $type, - /** - * Warnings associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - */ - public array $warnings, - /** - * Unique identifier for the Seam workspace associated with the access code. - */ - public string|null $workspace_id, - ) {} + ? \Seam\Resources\AccessCode\DormakabaOracodeMetadata::from_json( + $json->dormakaba_oracode_metadata, + ) + : null, + ends_at: $json->ends_at ?? null, + is_backup: $json->is_backup ?? null, + is_scheduled_on_device: $json->is_scheduled_on_device ?? null, + is_waiting_for_code_assignment: $json->is_waiting_for_code_assignment ?? + null, + pulled_backup_access_code_id: $json->pulled_backup_access_code_id ?? + null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier for the access code. + */ + public string|null $access_code_id, + /** + * Code used for access. Typically, a numeric or alphanumeric string. + */ + public string|null $code, + /** + * Unique identifier for a group of access codes that share the same code. + */ + public string|null $common_code_key, + /** + * Date and time at which the access code was created. + */ + public string|null $created_at, + /** + * Unique identifier for the device associated with the access code. + */ + public string|null $device_id, + /** + * Errors associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). + * + * @var list<\Seam\Resources\AccessCode\Errors> + */ + public array $errors, + /** + * Indicates whether a backup access code is available for use if the primary access code is lost or compromised. + */ + public bool|null $is_backup_access_code_available, + /** + * Indicates whether changes to the access code from external sources are permitted. + */ + public bool|null $is_external_modification_allowed, + /** + * Indicates whether Seam manages the access code. + */ + public true|null $is_managed, + /** + * Indicates whether the access code is intended for use in offline scenarios. If `true`, this code can be created on a device without a network connection. + */ + public bool|null $is_offline_access_code, + /** + * Indicates whether the access code can only be used once. If `true`, the code becomes invalid after the first use. + */ + public bool|null $is_one_time_use, + /** + * Name of the access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as `first_name` and `last_name`. To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called `appearance`. This is an object with a `name` property and, optionally, `first_name` and `last_name` properties (for providers that break down a name into components). + */ + public string|null $name, + /** + * Collection of pending mutations for the access code. Indicates changes that Seam is in the process of pushing to the device. + * + * @var list<\Seam\Resources\AccessCode\PendingMutations> + */ + public array $pending_mutations, + /** + * Current status of the access code within the operational lifecycle. Values are `setting`, a transitional phase that indicates that the code is being configured or activated; `set`, which indicates that the code is active and operational; `unset`, which indicates a deactivated or unused state, either before activation or after deliberate deactivation; `removing`, which indicates a transitional period in which the code is being deleted or made inactive; and `unknown`, which indicates an indeterminate state, due to reasons such as system errors or incomplete data, that highlights a potential need for system review or troubleshooting. See also [Lifecycle of Access Codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes/lifecycle-of-access-codes). + * + * @var value-of<\Seam\Resources\AccessCode\Status>|string|null + */ + public string|null $status, + /** + * Type of the access code. `ongoing` access codes are active continuously until deactivated manually. `time_bound` access codes have a specific duration. + * + * @var value-of<\Seam\Resources\AccessCode\Type>|string|null + */ + public string|null $type, + /** + * Warnings associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). + * + * @var list<\Seam\Resources\AccessCode\Warnings> + */ + public array $warnings, + /** + * Unique identifier for the Seam workspace associated with the access code. + */ + public string|null $workspace_id, + /** + * Metadata for a dormakaba Oracode managed access code. Only present for access codes from dormakaba Oracode devices. + */ + public \Seam\Resources\AccessCode\DormakabaOracodeMetadata|null $dormakaba_oracode_metadata = null, + /** + * Date and time after which the time-bound access code becomes inactive. + */ + public string|null $ends_at = null, + /** + * Indicates whether the access code is a backup code. + */ + public bool|null $is_backup = null, + /** + * Indicates whether the code is set on the device according to a preconfigured schedule. + */ + public bool|null $is_scheduled_on_device = null, + /** + * Indicates whether the access code is waiting for a code assignment. + */ + public bool|null $is_waiting_for_code_assignment = null, + /** + * Identifier of the pulled backup access code. Used to associate the pulled backup access code with the original access code. + */ + public string|null $pulled_backup_access_code_id = null, + /** + * Date and time at which the time-bound access code becomes active. + */ + public string|null $starts_at = null, + ) {} + } +} + +namespace Seam\Resources\AccessCode { + /** + * Metadata for a dormakaba Oracode managed access code. Only present for access codes from dormakaba Oracode devices. + */ + class DormakabaOracodeMetadata + { + public static function from_json( + mixed $json, + ): DormakabaOracodeMetadata|null { + if (!$json) { + return null; + } + return new self( + is_cancellable: $json->is_cancellable ?? null, + is_early_checkin_able: $json->is_early_checkin_able ?? null, + is_extendable: $json->is_extendable ?? null, + is_overridable: $json->is_overridable ?? null, + site_name: $json->site_name ?? null, + stay_id: $json->stay_id ?? null, + user_level_id: $json->user_level_id ?? null, + user_level_name: $json->user_level_name ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the stay can be cancelled via the Dormakaba Oracode API. + */ + public bool|null $is_cancellable = null, + /** + * Indicates whether early check-in is available for this stay. + */ + public bool|null $is_early_checkin_able = null, + /** + * Indicates whether the stay can be extended via the Dormakaba Oracode API. + */ + public bool|null $is_extendable = null, + /** + * Indicates whether the access code can be overridden. When false, the maximum number of overrides has been reached. + */ + public bool|null $is_overridable = null, + /** + * Dormakaba Oracode site name associated with this access code. + */ + public string|null $site_name = null, + /** + * Dormakaba Oracode stay ID associated with this access code. + */ + public float|null $stay_id = null, + /** + * Dormakaba Oracode user level ID associated with this access code. + */ + public string|null $user_level_id = null, + /** + * Dormakaba Oracode user level name associated with this access code. + */ + public string|null $user_level_name = null, + ) {} + } + + /** + * Errors associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\AccessCode\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessCode\Errors\ErrorCode::PROVIDER_ISSUE + => \Seam\Resources\AccessCode\Errors\ProviderIssue::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::FAILED_TO_SET_ON_DEVICE + => \Seam\Resources\AccessCode\Errors\FailedToSetOnDevice::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::FAILED_TO_REMOVE_FROM_DEVICE + => \Seam\Resources\AccessCode\Errors\FailedToRemoveFromDevice::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::DUPLICATE_CODE_ON_DEVICE + => \Seam\Resources\AccessCode\Errors\DuplicateCodeOnDevice::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::NO_SPACE_FOR_ACCESS_CODE_ON_DEVICE + => \Seam\Resources\AccessCode\Errors\NoSpaceForAccessCodeOnDevice::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::CONFLICTING_EXTERNAL_MODIFICATION + => \Seam\Resources\AccessCode\Errors\ConflictingExternalModification::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::ACCESS_CODE_INACTIVE + => \Seam\Resources\AccessCode\Errors\AccessCodeInactive::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::ACCOUNT_DISCONNECTED + => \Seam\Resources\AccessCode\Errors\AccountDisconnected::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED + => \Seam\Resources\AccessCode\Errors\SaltoKsSubscriptionLimitExceeded::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::INSUFFICIENT_PERMISSIONS + => \Seam\Resources\AccessCode\Errors\InsufficientPermissions::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::DORMAKABA_SITES_DISCONNECTED + => \Seam\Resources\AccessCode\Errors\DormakabaSitesDisconnected::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::DEVICE_OFFLINE + => \Seam\Resources\AccessCode\Errors\DeviceOffline::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::DEVICE_REMOVED + => \Seam\Resources\AccessCode\Errors\DeviceRemoved::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::HUB_DISCONNECTED + => \Seam\Resources\AccessCode\Errors\HubDisconnected::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::DEVICE_DISCONNECTED + => \Seam\Resources\AccessCode\Errors\DeviceDisconnected::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::EMPTY_BACKUP_ACCESS_CODE_POOL + => \Seam\Resources\AccessCode\Errors\EmptyBackupAccessCodePool::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::AUGUST_LOCK_NOT_AUTHORIZED + => \Seam\Resources\AccessCode\Errors\AugustLockNotAuthorized::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::MISSING_DEVICE_CREDENTIALS + => \Seam\Resources\AccessCode\Errors\MissingDeviceCredentials::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::AUXILIARY_HEAT_RUNNING + => \Seam\Resources\AccessCode\Errors\AuxiliaryHeatRunning::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::SUBSCRIPTION_REQUIRED + => \Seam\Resources\AccessCode\Errors\SubscriptionRequired::from_json( + $json, + ), + \Seam\Resources\AccessCode\Errors\ErrorCode::BRIDGE_DISCONNECTED + => \Seam\Resources\AccessCode\Errors\BridgeDisconnected::from_json( + $json, + ), + default => new self( + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Collection of pending mutations for the access code. Indicates changes that Seam is in the process of pushing to the device. Known mutation_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class PendingMutations + { + public static function from_json(mixed $json): PendingMutations|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->mutation_code ?? null) + ? \Seam\Resources\AccessCode\PendingMutations\MutationCode::tryFrom( + $json->mutation_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessCode\PendingMutations\MutationCode::CREATING + => \Seam\Resources\AccessCode\PendingMutations\Creating::from_json( + $json, + ), + \Seam\Resources\AccessCode\PendingMutations\MutationCode::DEFERRING_CREATION + => \Seam\Resources\AccessCode\PendingMutations\DeferringCreation::from_json( + $json, + ), + \Seam\Resources\AccessCode\PendingMutations\MutationCode::DELETING + => \Seam\Resources\AccessCode\PendingMutations\Deleting::from_json( + $json, + ), + \Seam\Resources\AccessCode\PendingMutations\MutationCode::UPDATING_CODE + => \Seam\Resources\AccessCode\PendingMutations\UpdatingCode::from_json( + $json, + ), + \Seam\Resources\AccessCode\PendingMutations\MutationCode::UPDATING_NAME + => \Seam\Resources\AccessCode\PendingMutations\UpdatingName::from_json( + $json, + ), + \Seam\Resources\AccessCode\PendingMutations\MutationCode::UPDATING_TIME_FRAME + => \Seam\Resources\AccessCode\PendingMutations\UpdatingTimeFrame::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + public string|null $created_at, + /** + * Detailed description of the mutation. + */ + public string|null $message, + /** + * Mutation code to indicate that Seam is in the process of setting an access code on the device. + * + * @var value-of<\Seam\Resources\AccessCode\PendingMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + ) {} + } + + /** + * Warnings associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\AccessCode\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessCode\Warnings\WarningCode::CODE_ROTATES_PERIODICALLY + => \Seam\Resources\AccessCode\Warnings\CodeRotatesPeriodically::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::TIME_FRAME_ADJUSTED_FOR_UNKNOWN_TIME_ZONE + => \Seam\Resources\AccessCode\Warnings\TimeFrameAdjustedForUnknownTimeZone::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::EXTERNAL_MODIFICATION_IN_EFFECT + => \Seam\Resources\AccessCode\Warnings\ExternalModificationInEffect::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::DELAY_IN_SETTING_ON_DEVICE + => \Seam\Resources\AccessCode\Warnings\DelayInSettingOnDevice::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::DELAY_IN_REMOVING_FROM_DEVICE + => \Seam\Resources\AccessCode\Warnings\DelayInRemovingFromDevice::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::THIRD_PARTY_INTEGRATION_DETECTED + => \Seam\Resources\AccessCode\Warnings\ThirdPartyIntegrationDetected::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::IGLOO_ALGOPIN_MUST_BE_USED_WITHIN_24_HOURS + => \Seam\Resources\AccessCode\Warnings\IglooAlgopinMustBeUsedWithin_24Hours::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::MANAGEMENT_TRANSFERRED + => \Seam\Resources\AccessCode\Warnings\ManagementTransferred::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::USING_BACKUP_ACCESS_CODE + => \Seam\Resources\AccessCode\Warnings\UsingBackupAccessCode::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\AccessCode\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\AccessCode\Warnings\WarningCode::UNKNOWN_ISSUE_WITH_ACCESS_CODE + => \Seam\Resources\AccessCode\Warnings\UnknownIssueWithAccessCode::from_json( + $json, + ), + default => new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ), + }; + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at = null, + ) {} + } + + enum Status: string + { + case SETTING = "setting"; + case SET = "set"; + case VALUE_UNSET = "unset"; + case REMOVING = "removing"; + case UNKNOWN = "unknown"; + } + + enum Type: string + { + case TIME_BOUND = "time_bound"; + case ONGOING = "ongoing"; + } +} + +namespace Seam\Resources\AccessCode\Errors { + /** + * Indicates a provider-specific issue that prevents the access code from being set or managed. Check the error message for details. + */ + final class ProviderIssue extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): ProviderIssue|null + { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Failed to set code on device. + */ + final class FailedToSetOnDevice extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): FailedToSetOnDevice|null + { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Failed to remove code from device. + */ + final class FailedToRemoveFromDevice extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): FailedToRemoveFromDevice|null { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Duplicate access code detected on device. + */ + final class DuplicateCodeOnDevice extends \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): DuplicateCodeOnDevice|null { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + managed_access_code_id: $json->managed_access_code_id ?? null, + unmanaged_access_code_id: $json->unmanaged_access_code_id ?? + null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + /** + * ID of the managed access code that conflicts with this managed access code, when Seam can identify it. + */ + public string|null $managed_access_code_id = null, + /** + * ID of the unmanaged access code that conflicts with this managed access code, when Seam can identify it. + */ + public string|null $unmanaged_access_code_id = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * No space for access code on device. + */ + final class NoSpaceForAccessCodeOnDevice extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): NoSpaceForAccessCodeOnDevice|null { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Code was modified or removed externally after Seam successfully set it on the device. The external change conflicts with the state that Seam is trying to apply, so Seam will attempt to set the code on the device again. + */ + final class ConflictingExternalModification extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): ConflictingExternalModification|null { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + change_type: $json->change_type ?? null, + created_at: $json->created_at ?? null, + modified_fields: array_map( + fn( + $m, + ) => \Seam\Resources\AccessCode\Errors\ConflictingExternalModification\ModifiedFields::from_json( + $m, + ), + $json->modified_fields ?? [], + ), + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates the type of external modification. `modified` means the code's PIN or schedule was changed. `removed` means the code was deleted from the device. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ConflictingExternalModification\ChangeType>|string|null + */ + public string|null $change_type = null, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + /** + * List of fields that were changed externally, with their previous and new values. + * + * @var list<\Seam\Resources\AccessCode\Errors\ConflictingExternalModification\ModifiedFields>|null + */ + public array|null $modified_fields = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the access code is disabled or inactive on the device. The code exists but will not grant access until re-enabled. + */ + final class AccessCodeInactive extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): AccessCodeInactive|null + { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the account is disconnected. + */ + final class AccountDisconnected extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): AccountDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the Salto site user limit has been reached. + */ + final class SaltoKsSubscriptionLimitExceeded extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that Seam's integration user does not have sufficient permissions on the provider's system to which this device belongs, so Seam cannot manage access codes or unlock the device. See the error message for specifics, then either reauthorize the connected account in Seam or grant the integration user the required permissions in the provider's system. + */ + final class InsufficientPermissions extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): InsufficientPermissions|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that one or more dormakaba sites associated with the connected account could not be connected. Contact dormakaba support. + */ + final class DormakabaSitesDisconnected extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): DormakabaSitesDisconnected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the device is offline. + */ + final class DeviceOffline extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): DeviceOffline|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the device has been removed. + */ + final class DeviceRemoved extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): DeviceRemoved|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the hub is disconnected. + */ + final class HubDisconnected extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): HubDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the device is disconnected. + */ + final class DeviceDisconnected extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): DeviceDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) is empty. + */ + final class EmptyBackupAccessCodePool extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): EmptyBackupAccessCodePool|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the user is not authorized to use the August lock. + */ + final class AugustLockNotAuthorized extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): AugustLockNotAuthorized|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that device credentials are missing. + */ + final class MissingDeviceCredentials extends + \Seam\Resources\AccessCode\Errors + { + public static function from_json( + mixed $json, + ): MissingDeviceCredentials|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the auxiliary heat is running. + */ + final class AuxiliaryHeatRunning extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): AuxiliaryHeatRunning|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that a subscription is required to connect. + */ + final class SubscriptionRequired extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): SubscriptionRequired|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the Seam API cannot communicate with [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge), for example, if the Seam Bridge executable has stopped or if the computer running the Seam Bridge executable is offline. See also [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system#acs_system-errors-seam_bridge_disconnected). + */ + final class BridgeDisconnected extends \Seam\Resources\AccessCode\Errors + { + public static function from_json(mixed $json): BridgeDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + public bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + public bool|null $is_connected_account_error = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + enum ErrorCode: string + { + case PROVIDER_ISSUE = "provider_issue"; + case FAILED_TO_SET_ON_DEVICE = "failed_to_set_on_device"; + case FAILED_TO_REMOVE_FROM_DEVICE = "failed_to_remove_from_device"; + case DUPLICATE_CODE_ON_DEVICE = "duplicate_code_on_device"; + case NO_SPACE_FOR_ACCESS_CODE_ON_DEVICE = "no_space_for_access_code_on_device"; + case CONFLICTING_EXTERNAL_MODIFICATION = "conflicting_external_modification"; + case ACCESS_CODE_INACTIVE = "access_code_inactive"; + case ACCOUNT_DISCONNECTED = "account_disconnected"; + case SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED = "salto_ks_subscription_limit_exceeded"; + case INSUFFICIENT_PERMISSIONS = "insufficient_permissions"; + case DORMAKABA_SITES_DISCONNECTED = "dormakaba_sites_disconnected"; + case DEVICE_OFFLINE = "device_offline"; + case DEVICE_REMOVED = "device_removed"; + case HUB_DISCONNECTED = "hub_disconnected"; + case DEVICE_DISCONNECTED = "device_disconnected"; + case EMPTY_BACKUP_ACCESS_CODE_POOL = "empty_backup_access_code_pool"; + case AUGUST_LOCK_NOT_AUTHORIZED = "august_lock_not_authorized"; + case MISSING_DEVICE_CREDENTIALS = "missing_device_credentials"; + case AUXILIARY_HEAT_RUNNING = "auxiliary_heat_running"; + case SUBSCRIPTION_REQUIRED = "subscription_required"; + case BRIDGE_DISCONNECTED = "bridge_disconnected"; + } } -/** - * Metadata for a dormakaba Oracode managed access code. Only present for access codes from dormakaba Oracode devices. - */ -class AccessCodeDormakabaOracodeMetadata -{ - public static function from_json( - mixed $json, - ): AccessCodeDormakabaOracodeMetadata|null { - if (!$json) { - return null; - } - return new self( - is_cancellable: $json->is_cancellable ?? null, - is_early_checkin_able: $json->is_early_checkin_able ?? null, - is_extendable: $json->is_extendable ?? null, - is_overridable: $json->is_overridable ?? null, - site_name: $json->site_name ?? null, - stay_id: $json->stay_id ?? null, - user_level_id: $json->user_level_id ?? null, - user_level_name: $json->user_level_name ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the stay can be cancelled via the Dormakaba Oracode API. - */ - public bool|null $is_cancellable, - /** - * Indicates whether early check-in is available for this stay. - */ - public bool|null $is_early_checkin_able, - /** - * Indicates whether the stay can be extended via the Dormakaba Oracode API. - */ - public bool|null $is_extendable, - /** - * Indicates whether the access code can be overridden. When false, the maximum number of overrides has been reached. - */ - public bool|null $is_overridable, - /** - * Dormakaba Oracode site name associated with this access code. - */ - public string|null $site_name, - /** - * Dormakaba Oracode stay ID associated with this access code. - */ - public float|null $stay_id, - /** - * Dormakaba Oracode user level ID associated with this access code. - */ - public string|null $user_level_id, - /** - * Dormakaba Oracode user level name associated with this access code. - */ - public string|null $user_level_name, - ) {} +namespace Seam\Resources\AccessCode\Errors\ConflictingExternalModification { + /** + * List of fields that were changed externally, with their previous and new values. + */ + class ModifiedFields + { + public static function from_json(mixed $json): ModifiedFields|null + { + if (!$json) { + return null; + } + return new self( + field: $json->field ?? null, + from: $json->from ?? null, + to: $json->to ?? null, + ); + } + + public function __construct( + /** + * The name of the field that was changed (e.g. `code`, `starts_at`, `ends_at`). + */ + public string|null $field, + /** + * The previous value of the field. + */ + public string|null $from, + /** + * The new value of the field. + */ + public string|null $to, + ) {} + } + + enum ChangeType: string + { + case MODIFIED = "modified"; + case REMOVED = "removed"; + } } -/** - * Errors associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - */ -class AccessCodeErrors -{ - public static function from_json(mixed $json): AccessCodeErrors|null - { - if (!$json) { - return null; - } - return new self( - change_type: $json->change_type ?? null, - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - is_access_code_error: $json->is_access_code_error ?? null, - is_bridge_error: $json->is_bridge_error ?? null, - is_connected_account_error: $json->is_connected_account_error ?? - null, - is_device_error: $json->is_device_error ?? null, - managed_access_code_id: $json->managed_access_code_id ?? null, - message: $json->message ?? null, - modified_fields: array_map( - fn($m) => AccessCodeModifiedFields::from_json($m), - $json->modified_fields ?? [], - ), - unmanaged_access_code_id: $json->unmanaged_access_code_id ?? null, - ); - } - - public function __construct( - /** - * Indicates the type of external modification. `modified` means the code's PIN or schedule was changed. `removed` means the code was deleted from the device. - */ - public string|null $change_type, - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Indicates that this is an access code error. - */ - public bool|null $is_access_code_error, - /** - * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). - */ - public bool|null $is_bridge_error, - /** - * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. - */ - public bool|null $is_connected_account_error, - /** - * Indicates that the error is not a device error. - */ - public bool|null $is_device_error, - /** - * ID of the managed access code that conflicts with this managed access code, when Seam can identify it. - */ - public string|null $managed_access_code_id, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * List of fields that were changed externally, with their previous and new values. - */ - public array $modified_fields, - /** - * ID of the unmanaged access code that conflicts with this managed access code, when Seam can identify it. - */ - public string|null $unmanaged_access_code_id, - ) {} +namespace Seam\Resources\AccessCode\PendingMutations { + /** + * Seam is in the process of setting an access code on the device. + */ + final class Creating extends \Seam\Resources\AccessCode\PendingMutations + { + public static function from_json(mixed $json): Creating|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of setting an access code on the device. + * + * @var value-of<\Seam\Resources\AccessCode\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is waiting until closer to the access code's start time before programming it on the device. + */ + final class DeferringCreation extends + \Seam\Resources\AccessCode\PendingMutations + { + public static function from_json(mixed $json): DeferringCreation|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + scheduled_at: $json->scheduled_at ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of setting an access code on the device. + * + * @var value-of<\Seam\Resources\AccessCode\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * Date and time at which Seam will attempt to program this access code on the device. + */ + public string|null $scheduled_at, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of removing an access code from the device. + */ + final class Deleting extends \Seam\Resources\AccessCode\PendingMutations + { + public static function from_json(mixed $json): Deleting|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of setting an access code on the device. + * + * @var value-of<\Seam\Resources\AccessCode\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an updated PIN code to the device. + */ + final class UpdatingCode extends \Seam\Resources\AccessCode\PendingMutations + { + public static function from_json(mixed $json): UpdatingCode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AccessCode\PendingMutations\UpdatingCode\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AccessCode\PendingMutations\UpdatingCode\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous code configuration. + */ + public \Seam\Resources\AccessCode\PendingMutations\UpdatingCode\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of setting an access code on the device. + * + * @var value-of<\Seam\Resources\AccessCode\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New code configuration. + */ + public \Seam\Resources\AccessCode\PendingMutations\UpdatingCode\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an updated access code name to the device. + */ + final class UpdatingName extends \Seam\Resources\AccessCode\PendingMutations + { + public static function from_json(mixed $json): UpdatingName|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AccessCode\PendingMutations\UpdatingName\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AccessCode\PendingMutations\UpdatingName\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous name configuration. + */ + public \Seam\Resources\AccessCode\PendingMutations\UpdatingName\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of setting an access code on the device. + * + * @var value-of<\Seam\Resources\AccessCode\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New name configuration. + */ + public \Seam\Resources\AccessCode\PendingMutations\UpdatingName\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an updated time frame to the device. + */ + final class UpdatingTimeFrame extends + \Seam\Resources\AccessCode\PendingMutations + { + public static function from_json(mixed $json): UpdatingTimeFrame|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AccessCode\PendingMutations\UpdatingTimeFrame\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AccessCode\PendingMutations\UpdatingTimeFrame\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous time frame configuration. + */ + public \Seam\Resources\AccessCode\PendingMutations\UpdatingTimeFrame\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of setting an access code on the device. + * + * @var value-of<\Seam\Resources\AccessCode\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New time frame configuration. + */ + public \Seam\Resources\AccessCode\PendingMutations\UpdatingTimeFrame\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + enum MutationCode: string + { + case CREATING = "creating"; + case DEFERRING_CREATION = "deferring_creation"; + case DELETING = "deleting"; + case UPDATING_CODE = "updating_code"; + case UPDATING_NAME = "updating_name"; + case UPDATING_TIME_FRAME = "updating_time_frame"; + } } -/** - * Previous code configuration. - */ -class AccessCodeFrom -{ - public static function from_json(mixed $json): AccessCodeFrom|null - { - if (!$json) { - return null; - } - return new self( - code: $json->code ?? null, - ends_at: $json->ends_at ?? null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * Previous PIN code. - */ - public string|null $code, - /** - * Previous end time for the access code. - */ - public string|null $ends_at, - /** - * Previous access code name. - */ - public string|null $name, - /** - * Previous start time for the access code. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\AccessCode\PendingMutations\UpdatingCode { + /** + * Previous code configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(code: $json->code ?? null); + } + + public function __construct( + /** + * Previous PIN code. + */ + public string|null $code, + ) {} + } + + /** + * New code configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(code: $json->code ?? null); + } + + public function __construct( + /** + * New PIN code. + */ + public string|null $code, + ) {} + } } -/** - * List of fields that were changed externally, with their previous and new values. - */ -class AccessCodeModifiedFields -{ - public static function from_json(mixed $json): AccessCodeModifiedFields|null - { - if (!$json) { - return null; - } - return new self( - field: $json->field ?? null, - from: $json->from ?? null, - to: $json->to ?? null, - ); - } - - public function __construct( - /** - * The name of the field that was changed (e.g. `code`, `starts_at`, `ends_at`). - */ - public string|null $field, - /** - * The previous value of the field. - */ - public string|null $from, - /** - * The new value of the field. - */ - public string|null $to, - ) {} +namespace Seam\Resources\AccessCode\PendingMutations\UpdatingName { + /** + * Previous name configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(name: $json->name ?? null); + } + + public function __construct( + /** + * Previous access code name. + */ + public string|null $name, + ) {} + } + + /** + * New name configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(name: $json->name ?? null); + } + + public function __construct( + /** + * New access code name. + */ + public string|null $name, + ) {} + } } -/** - * Collection of pending mutations for the access code. Indicates changes that Seam is in the process of pushing to the device. - */ -class AccessCodePendingMutations -{ - public static function from_json( - mixed $json, - ): AccessCodePendingMutations|null { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - from: isset($json->from) - ? AccessCodeFrom::from_json($json->from) - : null, - message: $json->message ?? null, - mutation_code: $json->mutation_code ?? null, - scheduled_at: $json->scheduled_at ?? null, - to: isset($json->to) ? AccessCodeTo::from_json($json->to) : null, - ); - } - - public function __construct( - /** - * Date and time at which the mutation was created. - */ - public string|null $created_at, - /** - * Previous code configuration. - */ - public AccessCodeFrom|null $from, - /** - * Detailed description of the mutation. - */ - public string|null $message, - /** - * Mutation code to indicate that Seam is in the process of setting an access code on the device. - */ - public string|null $mutation_code, - /** - * Date and time at which Seam will attempt to program this access code on the device. - */ - public string|null $scheduled_at, - /** - * New code configuration. - */ - public AccessCodeTo|null $to, - ) {} +namespace Seam\Resources\AccessCode\PendingMutations\UpdatingTimeFrame { + /** + * Previous time frame configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Previous end time for the access code. + */ + public string|null $ends_at, + /** + * Previous start time for the access code. + */ + public string|null $starts_at, + ) {} + } + + /** + * New time frame configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * New end time for the access code. + */ + public string|null $ends_at, + /** + * New start time for the access code. + */ + public string|null $starts_at, + ) {} + } } -/** - * New code configuration. - */ -class AccessCodeTo -{ - public static function from_json(mixed $json): AccessCodeTo|null - { - if (!$json) { - return null; - } - return new self( - code: $json->code ?? null, - ends_at: $json->ends_at ?? null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * New PIN code. - */ - public string|null $code, - /** - * New end time for the access code. - */ - public string|null $ends_at, - /** - * New access code name. - */ - public string|null $name, - /** - * New start time for the access code. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\AccessCode\Warnings { + /** + * The access code's PIN rotates periodically when the code is renewed. Retrieve the latest code before each use. + */ + final class CodeRotatesPeriodically extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): CodeRotatesPeriodically|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * The device's time zone is unknown and this code's time frame crosses a daylight-saving transition in at least one plausible time zone. A 1-hour safety buffer has been applied to the side of the time frame affected by the transition (`ends_at` for spring-forward, `starts_at` for fall-back) so the code stays active through the shift — the code may be usable up to 1 hour beyond your requested window. Set the device's time zone via `/devices/report_provider_metadata` to clear the buffer and guarantee exact handling. + */ + final class TimeFrameAdjustedForUnknownTimeZone extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): TimeFrameAdjustedForUnknownTimeZone|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Code was modified or removed externally after Seam successfully set it on the device. External modification is allowed for this code, so the externally modified state is being honored. + */ + final class ExternalModificationInEffect extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): ExternalModificationInEffect|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + change_type: $json->change_type ?? null, + created_at: $json->created_at ?? null, + modified_fields: array_map( + fn( + $m, + ) => \Seam\Resources\AccessCode\Warnings\ExternalModificationInEffect\ModifiedFields::from_json( + $m, + ), + $json->modified_fields ?? [], + ), + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Indicates the type of external modification. `modified` means the code's PIN or schedule was changed. `removed` means the code was deleted from the device. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\ExternalModificationInEffect\ChangeType>|string|null + */ + public string|null $change_type = null, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + /** + * List of fields that were changed externally, with their previous and new values. + * + * @var list<\Seam\Resources\AccessCode\Warnings\ExternalModificationInEffect\ModifiedFields>|null + */ + public array|null $modified_fields = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Delay in setting code on device. + */ + final class DelayInSettingOnDevice extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): DelayInSettingOnDevice|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Delay in removing code from device. + */ + final class DelayInRemovingFromDevice extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): DelayInRemovingFromDevice|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Third-party integration detected that may cause access codes to fail. + */ + final class ThirdPartyIntegrationDetected extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): ThirdPartyIntegrationDetected|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Algopins must be used within 24 hours. + */ + final class IglooAlgopinMustBeUsedWithin_24Hours extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): IglooAlgopinMustBeUsedWithin_24Hours|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Management was transferred to another workspace. + */ + final class ManagementTransferred extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): ManagementTransferred|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * A backup access code has been pulled and is being used in place of this access code. + */ + final class UsingBackupAccessCode extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): UsingBackupAccessCode|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Access code is being deleted. + */ + final class BeingDeleted extends \Seam\Resources\AccessCode\Warnings + { + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * An unknown issue occurred with the access code. + */ + final class UnknownIssueWithAccessCode extends + \Seam\Resources\AccessCode\Warnings + { + public static function from_json( + mixed $json, + ): UnknownIssueWithAccessCode|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case CODE_ROTATES_PERIODICALLY = "code_rotates_periodically"; + case TIME_FRAME_ADJUSTED_FOR_UNKNOWN_TIME_ZONE = "time_frame_adjusted_for_unknown_time_zone"; + case EXTERNAL_MODIFICATION_IN_EFFECT = "external_modification_in_effect"; + case DELAY_IN_SETTING_ON_DEVICE = "delay_in_setting_on_device"; + case DELAY_IN_REMOVING_FROM_DEVICE = "delay_in_removing_from_device"; + case THIRD_PARTY_INTEGRATION_DETECTED = "third_party_integration_detected"; + case IGLOO_ALGOPIN_MUST_BE_USED_WITHIN_24_HOURS = "igloo_algopin_must_be_used_within_24_hours"; + case MANAGEMENT_TRANSFERRED = "management_transferred"; + case USING_BACKUP_ACCESS_CODE = "using_backup_access_code"; + case BEING_DELETED = "being_deleted"; + case UNKNOWN_ISSUE_WITH_ACCESS_CODE = "unknown_issue_with_access_code"; + } } -/** - * Warnings associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - */ -class AccessCodeWarnings -{ - public static function from_json(mixed $json): AccessCodeWarnings|null - { - if (!$json) { - return null; - } - return new self( - change_type: $json->change_type ?? null, - created_at: $json->created_at ?? null, - message: $json->message ?? null, - modified_fields: array_map( - fn($m) => AccessCodeModifiedFields::from_json($m), - $json->modified_fields ?? [], - ), - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Indicates the type of external modification. `modified` means the code's PIN or schedule was changed. `removed` means the code was deleted from the device. - */ - public string|null $change_type, - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * List of fields that were changed externally, with their previous and new values. - */ - public array $modified_fields, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\AccessCode\Warnings\ExternalModificationInEffect { + /** + * List of fields that were changed externally, with their previous and new values. + */ + class ModifiedFields + { + public static function from_json(mixed $json): ModifiedFields|null + { + if (!$json) { + return null; + } + return new self( + field: $json->field ?? null, + from: $json->from ?? null, + to: $json->to ?? null, + ); + } + + public function __construct( + /** + * The name of the field that was changed (e.g. `code`, `starts_at`, `ends_at`). + */ + public string|null $field, + /** + * The previous value of the field. + */ + public string|null $from, + /** + * The new value of the field. + */ + public string|null $to, + ) {} + } + + enum ChangeType: string + { + case MODIFIED = "modified"; + case REMOVED = "removed"; + } } diff --git a/src/Resources/AccessGrant.php b/src/Resources/AccessGrant.php index a4ff5cca..678b906e 100644 --- a/src/Resources/AccessGrant.php +++ b/src/Resources/AccessGrant.php @@ -1,441 +1,1113 @@ access_grant_id ?? null, + access_method_ids: $json->access_method_ids ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + ends_at: $json->ends_at ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AccessGrant\Errors::from_json($e), + $json->errors ?? [], + ), + location_ids: $json->location_ids ?? null, + name: $json->name ?? null, + pending_mutations: array_map( + fn( + $p, + ) => \Seam\Resources\AccessGrant\PendingMutations::from_json( + $p, + ), + $json->pending_mutations ?? [], + ), + requested_access_methods: array_map( + fn( + $r, + ) => \Seam\Resources\AccessGrant\RequestedAccessMethods::from_json( + $r, + ), + $json->requested_access_methods ?? [], + ), + space_ids: $json->space_ids ?? null, + starts_at: $json->starts_at ?? null, + user_identity_id: $json->user_identity_id ?? null, + warnings: array_map( + fn($w) => \Seam\Resources\AccessGrant\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + access_grant_key: $json->access_grant_key ?? null, + client_session_token: $json->client_session_token ?? null, + customization_profile_id: $json->customization_profile_id ?? + null, + instant_key_url: $json->instant_key_url ?? null, + reservation_key: $json->reservation_key ?? null, + ); } - return new self( - access_grant_id: $json->access_grant_id ?? null, - access_grant_key: $json->access_grant_key ?? null, - access_method_ids: $json->access_method_ids ?? null, - client_session_token: $json->client_session_token ?? null, - created_at: $json->created_at ?? null, - customization_profile_id: $json->customization_profile_id ?? null, - display_name: $json->display_name ?? null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => AccessGrantErrors::from_json($e), - $json->errors ?? [], - ), - instant_key_url: $json->instant_key_url ?? null, - location_ids: $json->location_ids ?? null, - name: $json->name ?? null, - pending_mutations: array_map( - fn($p) => AccessGrantPendingMutations::from_json($p), - $json->pending_mutations ?? [], - ), - requested_access_methods: array_map( - fn($r) => AccessGrantRequestedAccessMethods::from_json($r), - $json->requested_access_methods ?? [], - ), - reservation_key: $json->reservation_key ?? null, - space_ids: $json->space_ids ?? null, - starts_at: $json->starts_at ?? null, - user_identity_id: $json->user_identity_id ?? null, - warnings: array_map( - fn($w) => AccessGrantWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * ID of the Access Grant. - */ - public string|null $access_grant_id, - /** - * Unique key for the access grant within the workspace. - */ - public string|null $access_grant_key, - /** - * IDs of the access methods created for the Access Grant. - */ - public array|null $access_method_ids, - /** - * Client Session Token. Only returned if the Access Grant has a mobile_key access method. - */ - public string|null $client_session_token, - /** - * Date and time at which the Access Grant was created. - */ - public string|null $created_at, - /** - * ID of the customization profile associated with the Access Grant. - */ - public string|null $customization_profile_id, - /** - * Display name of the Access Grant. - */ - public string|null $display_name, - /** - * Date and time at which the Access Grant ends. - */ - public string|null $ends_at, - /** - * Errors associated with the [access grant](https://docs.seam.co/use-cases/granting-access). - */ - public array $errors, - /** - * Instant Key URL. Only returned if the Access Grant has a single mobile_key access_method. - */ - public string|null $instant_key_url, - /** - * @deprecated Use `space_ids`. - */ - public array|null $location_ids, - /** - * Name of the Access Grant. If not provided, the display name will be computed. - */ - public string|null $name, - /** - * List of pending mutations for the access grant. This shows updates that are in progress. - */ - public array $pending_mutations, - /** - * Access methods that the user requested for the Access Grant. - */ - public array $requested_access_methods, - /** - * Reservation key for the access grant. - */ - public string|null $reservation_key, - /** - * IDs of the spaces to which the Access Grant gives access. - */ - public array|null $space_ids, - /** - * Date and time at which the Access Grant starts. - */ - public string|null $starts_at, - /** - * ID of user identity to which the Access Grant gives access. - */ - public string|null $user_identity_id, - /** - * Warnings associated with the [access grant](https://docs.seam.co/use-cases/granting-access). - */ - public array $warnings, - /** - * ID of the Seam workspace associated with the Access Grant. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * ID of the Access Grant. + */ + public string|null $access_grant_id, + /** + * IDs of the access methods created for the Access Grant. + * + * @var list|null + */ + public array|null $access_method_ids, + /** + * Date and time at which the Access Grant was created. + */ + public string|null $created_at, + /** + * Display name of the Access Grant. + */ + public string|null $display_name, + /** + * Date and time at which the Access Grant ends. + */ + public string|null $ends_at, + /** + * Errors associated with the [access grant](https://docs.seam.co/use-cases/granting-access). + * + * @var list<\Seam\Resources\AccessGrant\Errors> + */ + public array $errors, + /** + * @var list|null + * @deprecated Use `space_ids`. + */ + public array|null $location_ids, + /** + * Name of the Access Grant. If not provided, the display name will be computed. + */ + public string|null $name, + /** + * List of pending mutations for the access grant. This shows updates that are in progress. + * + * @var list<\Seam\Resources\AccessGrant\PendingMutations> + */ + public array $pending_mutations, + /** + * Access methods that the user requested for the Access Grant. + * + * @var list<\Seam\Resources\AccessGrant\RequestedAccessMethods> + */ + public array $requested_access_methods, + /** + * IDs of the spaces to which the Access Grant gives access. + * + * @var list|null + */ + public array|null $space_ids, + /** + * Date and time at which the Access Grant starts. + */ + public string|null $starts_at, + /** + * ID of user identity to which the Access Grant gives access. + */ + public string|null $user_identity_id, + /** + * Warnings associated with the [access grant](https://docs.seam.co/use-cases/granting-access). + * + * @var list<\Seam\Resources\AccessGrant\Warnings> + */ + public array $warnings, + /** + * ID of the Seam workspace associated with the Access Grant. + */ + public string|null $workspace_id, + /** + * Unique key for the access grant within the workspace. + */ + public string|null $access_grant_key = null, + /** + * Client Session Token. Only returned if the Access Grant has a mobile_key access method. + */ + public string|null $client_session_token = null, + /** + * ID of the customization profile associated with the Access Grant. + */ + public string|null $customization_profile_id = null, + /** + * Instant Key URL. Only returned if the Access Grant has a single mobile_key access_method. + */ + public string|null $instant_key_url = null, + /** + * Reservation key for the access grant. + */ + public string|null $reservation_key = null, + ) {} + } } -/** - * Errors associated with the [access grant](https://docs.seam.co/use-cases/granting-access). - */ -class AccessGrantErrors -{ - public static function from_json(mixed $json): AccessGrantErrors|null +namespace Seam\Resources\AccessGrant { + /** + * Errors associated with the [access grant](https://docs.seam.co/use-cases/granting-access). Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\AccessGrant\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessGrant\Errors\ErrorCode::CANNOT_CREATE_REQUESTED_ACCESS_METHODS + => \Seam\Resources\AccessGrant\Errors\CannotCreateRequestedAccessMethods::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + missing_device_ids: $json->missing_device_ids ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. + * + * @var list|null + */ + public array|null $missing_device_ids = null, + ) {} + } + + /** + * List of pending mutations for the access grant. This shows updates that are in progress. Known mutation_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class PendingMutations + { + public static function from_json(mixed $json): PendingMutations|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->mutation_code ?? null) + ? \Seam\Resources\AccessGrant\PendingMutations\MutationCode::tryFrom( + $json->mutation_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessGrant\PendingMutations\MutationCode::UPDATING_SPACES + => \Seam\Resources\AccessGrant\PendingMutations\UpdatingSpaces::from_json( + $json, + ), + \Seam\Resources\AccessGrant\PendingMutations\MutationCode::UPDATING_ACCESS_TIMES + => \Seam\Resources\AccessGrant\PendingMutations\UpdatingAccessTimes::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + public string|null $created_at, + /** + * Detailed description of the mutation. + */ + public string|null $message, + /** + * Mutation code to indicate that Seam is in the process of updating the spaces (devices) associated with this access grant. + * + * @var value-of<\Seam\Resources\AccessGrant\PendingMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + ) {} + } + + /** + * Access methods that the user requested for the Access Grant. + */ + class RequestedAccessMethods { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): RequestedAccessMethods|null { + if (!$json) { + return null; + } + return new self( + created_access_method_ids: $json->created_access_method_ids ?? + null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + mode: $json->mode ?? null, + code: $json->code ?? null, + instant_key_max_use_count: $json->instant_key_max_use_count ?? + null, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - missing_device_ids: $json->missing_device_ids ?? null, - ); + + public function __construct( + /** + * IDs of the access methods created for the requested access method. + * + * @var list|null + */ + public array|null $created_access_method_ids, + /** + * Date and time at which the requested access method was added to the Access Grant. + */ + public string|null $created_at, + /** + * Display name of the access method. + */ + public string|null $display_name, + /** + * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\AccessGrant\RequestedAccessMethods\Mode>|string|null + */ + public string|null $mode, + /** + * Specific PIN code to use for this access method. Only applicable when mode is 'code'. + */ + public string|null $code = null, + /** + * Maximum number of times the instant key can be used. Only applicable when mode is 'mobile_key'. Defaults to 1 if not specified. + */ + public int|null $instant_key_max_use_count = null, + ) {} } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. - */ - public array|null $missing_device_ids, - ) {} + /** + * Warnings associated with the [access grant](https://docs.seam.co/use-cases/granting-access). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\AccessGrant\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessGrant\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\AccessGrant\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\AccessGrant\Warnings\WarningCode::UNDERPROVISIONED_ACCESS + => \Seam\Resources\AccessGrant\Warnings\UnderprovisionedAccess::from_json( + $json, + ), + \Seam\Resources\AccessGrant\Warnings\WarningCode::OVERPROVISIONED_ACCESS + => \Seam\Resources\AccessGrant\Warnings\OverprovisionedAccess::from_json( + $json, + ), + \Seam\Resources\AccessGrant\Warnings\WarningCode::UPDATING_ACCESS_TIMES + => \Seam\Resources\AccessGrant\Warnings\UpdatingAccessTimes::from_json( + $json, + ), + \Seam\Resources\AccessGrant\Warnings\WarningCode::REQUESTED_CODE_UNAVAILABLE + => \Seam\Resources\AccessGrant\Warnings\RequestedCodeUnavailable::from_json( + $json, + ), + \Seam\Resources\AccessGrant\Warnings\WarningCode::DEVICE_DOES_NOT_SUPPORT_ACCESS_CODES + => \Seam\Resources\AccessGrant\Warnings\DeviceDoesNotSupportAccessCodes::from_json( + $json, + ), + \Seam\Resources\AccessGrant\Warnings\WarningCode::DEVICE_TIME_CONSTRAINTS_VIOLATED + => \Seam\Resources\AccessGrant\Warnings\DeviceTimeConstraintsViolated::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } } -/** - * Devices whose access codes could not be revoked during reconciliation. Present when the provider does not support revoking an offline access code (e.g. Dormakaba oracode with exhausted override budget). - */ -class AccessGrantFailedDevices -{ - public static function from_json(mixed $json): AccessGrantFailedDevices|null +namespace Seam\Resources\AccessGrant\Errors { + /** + * Indicates that Seam could not create one or more of the requested access methods for the access grant. + */ + final class CannotCreateRequestedAccessMethods extends + \Seam\Resources\AccessGrant\Errors { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): CannotCreateRequestedAccessMethods|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + missing_device_ids: $json->missing_device_ids ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. + * + * @var list|null + */ + array|null $missing_device_ids = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + missing_device_ids: $missing_device_ids, + ); } - return new self( - device_id: $json->device_id ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); } - public function __construct( - /** - * Device whose access code could not be revoked. - */ - public string|null $device_id, - /** - * Reason the access code could not be revoked (e.g. `offline_access_code_not_revocable`). - */ - public string|null $error_code, - /** - * Human-readable description of why revocation failed. - */ - public string|null $message, - ) {} + enum ErrorCode: string + { + case CANNOT_CREATE_REQUESTED_ACCESS_METHODS = "cannot_create_requested_access_methods"; + } } -/** - * Previous location configuration. - */ -class AccessGrantFrom -{ - public static function from_json(mixed $json): AccessGrantFrom|null +namespace Seam\Resources\AccessGrant\PendingMutations { + /** + * Seam is in the process of updating the devices/spaces associated with this access grant. + */ + final class UpdatingSpaces extends + \Seam\Resources\AccessGrant\PendingMutations { - if (!$json) { - return null; + public static function from_json(mixed $json): UpdatingSpaces|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AccessGrant\PendingMutations\UpdatingSpaces\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AccessGrant\PendingMutations\UpdatingSpaces\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous location configuration. + */ + public \Seam\Resources\AccessGrant\PendingMutations\UpdatingSpaces\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of updating the spaces (devices) associated with this access grant. + * + * @var value-of<\Seam\Resources\AccessGrant\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New location configuration. + */ + public \Seam\Resources\AccessGrant\PendingMutations\UpdatingSpaces\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of updating the access times for this access grant. + */ + final class UpdatingAccessTimes extends + \Seam\Resources\AccessGrant\PendingMutations + { + public static function from_json(mixed $json): UpdatingAccessTimes|null + { + if (!$json) { + return null; + } + return new self( + access_method_ids: $json->access_method_ids ?? null, + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AccessGrant\PendingMutations\UpdatingAccessTimes\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AccessGrant\PendingMutations\UpdatingAccessTimes\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * IDs of the access methods being updated. + * + * @var list|null + */ + public array|null $access_method_ids, + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous access time configuration. + */ + public \Seam\Resources\AccessGrant\PendingMutations\UpdatingAccessTimes\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of updating the spaces (devices) associated with this access grant. + * + * @var value-of<\Seam\Resources\AccessGrant\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access time configuration. + */ + public \Seam\Resources\AccessGrant\PendingMutations\UpdatingAccessTimes\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); } - return new self( - device_ids: $json->device_ids ?? null, - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); } - public function __construct( - /** - * Previous device IDs where access codes existed. - */ - public array|null $device_ids, - /** - * Previous end time for access. - */ - public string|null $ends_at, - /** - * Previous start time for access. - */ - public string|null $starts_at, - ) {} + enum MutationCode: string + { + case UPDATING_SPACES = "updating_spaces"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + } } -/** - * List of pending mutations for the access grant. This shows updates that are in progress. - */ -class AccessGrantPendingMutations -{ - public static function from_json( - mixed $json, - ): AccessGrantPendingMutations|null { - if (!$json) { - return null; +namespace Seam\Resources\AccessGrant\PendingMutations\UpdatingSpaces { + /** + * Previous location configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); } - return new self( - access_method_ids: $json->access_method_ids ?? null, - created_at: $json->created_at ?? null, - from: isset($json->from) - ? AccessGrantFrom::from_json($json->from) - : null, - message: $json->message ?? null, - mutation_code: $json->mutation_code ?? null, - to: isset($json->to) ? AccessGrantTo::from_json($json->to) : null, - ); + + public function __construct( + /** + * Previous device IDs where access codes existed. + * + * @var list|null + */ + public array|null $device_ids, + ) {} } - public function __construct( - /** - * IDs of the access methods being updated. - */ - public array|null $access_method_ids, - /** - * Date and time at which the mutation was created. - */ - public string|null $created_at, - /** - * Previous location configuration. - */ - public AccessGrantFrom|null $from, - /** - * Detailed description of the mutation. - */ - public string|null $message, - /** - * Mutation code to indicate that Seam is in the process of updating the spaces (devices) associated with this access grant. - */ - public string|null $mutation_code, - /** - * New location configuration. - */ - public AccessGrantTo|null $to, - ) {} + /** + * New location configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + device_ids: $json->device_ids ?? null, + common_code_key: $json->common_code_key ?? null, + ); + } + + public function __construct( + /** + * New device IDs where access codes should be created. + * + * @var list|null + */ + public array|null $device_ids, + /** + * Common code key to ensure PIN code reuse across devices. + */ + public string|null $common_code_key = null, + ) {} + } } -/** - * Access methods that the user requested for the Access Grant. - */ -class AccessGrantRequestedAccessMethods -{ - public static function from_json( - mixed $json, - ): AccessGrantRequestedAccessMethods|null { - if (!$json) { - return null; +namespace Seam\Resources\AccessGrant\PendingMutations\UpdatingAccessTimes { + /** + * Previous access time configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); } - return new self( - code: $json->code ?? null, - created_access_method_ids: $json->created_access_method_ids ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - instant_key_max_use_count: $json->instant_key_max_use_count ?? null, - mode: $json->mode ?? null, - ); + + public function __construct( + /** + * Previous end time for access. + */ + public string|null $ends_at, + /** + * Previous start time for access. + */ + public string|null $starts_at, + ) {} + } + + /** + * New access time configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * New end time for access. + */ + public string|null $ends_at, + /** + * New start time for access. + */ + public string|null $starts_at, + ) {} } +} - public function __construct( - /** - * Specific PIN code to use for this access method. Only applicable when mode is 'code'. - */ - public string|null $code, - /** - * IDs of the access methods created for the requested access method. - */ - public array|null $created_access_method_ids, - /** - * Date and time at which the requested access method was added to the Access Grant. - */ - public string|null $created_at, - /** - * Display name of the access method. - */ - public string|null $display_name, - /** - * Maximum number of times the instant key can be used. Only applicable when mode is 'mobile_key'. Defaults to 1 if not specified. - */ - public int|null $instant_key_max_use_count, - /** - * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. - */ - public string|null $mode, - ) {} +namespace Seam\Resources\AccessGrant\RequestedAccessMethods { + enum Mode: string + { + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; + } } -/** - * New location configuration. - */ -class AccessGrantTo -{ - public static function from_json(mixed $json): AccessGrantTo|null +namespace Seam\Resources\AccessGrant\Warnings { + /** + * Indicates that the [access grant](https://docs.seam.co/use-cases/granting-access) is being deleted. + */ + final class BeingDeleted extends \Seam\Resources\AccessGrant\Warnings + { + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the access grant should have access to more locations than it currently does. Access methods are being created for the missing locations. + */ + final class UnderprovisionedAccess extends + \Seam\Resources\AccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): UnderprovisionedAccess|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the access grant has access to locations it should not have. Access methods are being removed from the extra locations. + */ + final class OverprovisionedAccess extends + \Seam\Resources\AccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): OverprovisionedAccess|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + failed_devices: array_map( + fn( + $f, + ) => \Seam\Resources\AccessGrant\Warnings\OverprovisionedAccess\FailedDevices::from_json( + $f, + ), + $json->failed_devices ?? [], + ), + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Devices whose access codes could not be revoked during reconciliation. Present when the provider does not support revoking an offline access code (e.g. Dormakaba oracode with exhausted override budget). + * + * @var list<\Seam\Resources\AccessGrant\Warnings\OverprovisionedAccess\FailedDevices>|null + */ + public array|null $failed_devices = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the access times for this [access grant](https://docs.seam.co/use-cases/granting-access) are being updated. + */ + final class UpdatingAccessTimes extends \Seam\Resources\AccessGrant\Warnings { - if (!$json) { - return null; + public static function from_json(mixed $json): UpdatingAccessTimes|null + { + if (!$json) { + return null; + } + return new self( + access_method_ids: $json->access_method_ids ?? null, + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * IDs of the access methods being updated. + * + * @var list|null + */ + public array|null $access_method_ids, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - common_code_key: $json->common_code_key ?? null, - device_ids: $json->device_ids ?? null, - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); } - public function __construct( - /** - * Common code key to ensure PIN code reuse across devices. - */ - public string|null $common_code_key, - /** - * New device IDs where access codes should be created. - */ - public array|null $device_ids, - /** - * New end time for access. - */ - public string|null $ends_at, - /** - * New start time for access. - */ - public string|null $starts_at, - ) {} + /** + * Indicates that the requested PIN code was already in use on a device, so a different code was assigned. + */ + final class RequestedCodeUnavailable extends + \Seam\Resources\AccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): RequestedCodeUnavailable|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + message: $json->message ?? null, + new_code: $json->new_code ?? null, + original_code: $json->original_code ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * ID of the device where the requested code was unavailable. + */ + public string|null $device_id, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * The new PIN code that was assigned instead. + */ + public string|null $new_code, + /** + * The originally requested PIN code that was unavailable. + */ + public string|null $original_code, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a device in the access grant does not support access codes and was excluded from code materialization. + */ + final class DeviceDoesNotSupportAccessCodes extends + \Seam\Resources\AccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): DeviceDoesNotSupportAccessCodes|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * ID of the device that does not support access codes. + */ + public string|null $device_id, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a device in the access grant cannot program an access code for the grant's time range because of device-specific time constraints. + */ + final class DeviceTimeConstraintsViolated extends + \Seam\Resources\AccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): DeviceTimeConstraintsViolated|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + message: $json->message ?? null, + reason: $json->reason ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * ID of the device whose time constraints the access grant violates. + */ + public string|null $device_id, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Specific reason why the grant's times are not programmable on the device. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\DeviceTimeConstraintsViolated\Reason>|string|null + */ + public string|null $reason, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case BEING_DELETED = "being_deleted"; + case UNDERPROVISIONED_ACCESS = "underprovisioned_access"; + case OVERPROVISIONED_ACCESS = "overprovisioned_access"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + case REQUESTED_CODE_UNAVAILABLE = "requested_code_unavailable"; + case DEVICE_DOES_NOT_SUPPORT_ACCESS_CODES = "device_does_not_support_access_codes"; + case DEVICE_TIME_CONSTRAINTS_VIOLATED = "device_time_constraints_violated"; + } } -/** - * Warnings associated with the [access grant](https://docs.seam.co/use-cases/granting-access). - */ -class AccessGrantWarnings -{ - public static function from_json(mixed $json): AccessGrantWarnings|null +namespace Seam\Resources\AccessGrant\Warnings\OverprovisionedAccess { + /** + * Devices whose access codes could not be revoked during reconciliation. Present when the provider does not support revoking an offline access code (e.g. Dormakaba oracode with exhausted override budget). + */ + class FailedDevices { - if (!$json) { - return null; + public static function from_json(mixed $json): FailedDevices|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); } - return new self( - access_method_ids: $json->access_method_ids ?? null, - created_at: $json->created_at ?? null, - device_id: $json->device_id ?? null, - failed_devices: array_map( - fn($f) => AccessGrantFailedDevices::from_json($f), - $json->failed_devices ?? [], - ), - message: $json->message ?? null, - new_code: $json->new_code ?? null, - original_code: $json->original_code ?? null, - reason: $json->reason ?? null, - warning_code: $json->warning_code ?? null, - ); + + public function __construct( + /** + * Device whose access code could not be revoked. + */ + public string|null $device_id, + /** + * Reason the access code could not be revoked (e.g. `offline_access_code_not_revocable`). + */ + public string|null $error_code, + /** + * Human-readable description of why revocation failed. + */ + public string|null $message, + ) {} } +} - public function __construct( - /** - * IDs of the access methods being updated. - */ - public array|null $access_method_ids, - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * ID of the device where the requested code was unavailable. - */ - public string|null $device_id, - /** - * Devices whose access codes could not be revoked during reconciliation. Present when the provider does not support revoking an offline access code (e.g. Dormakaba oracode with exhausted override budget). - */ - public array $failed_devices, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * The new PIN code that was assigned instead. - */ - public string|null $new_code, - /** - * The originally requested PIN code that was unavailable. - */ - public string|null $original_code, - /** - * Specific reason why the grant's times are not programmable on the device. - */ - public string|null $reason, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\AccessGrant\Warnings\DeviceTimeConstraintsViolated { + enum Reason: string + { + case DURATION_EXCEEDS_MAX = "duration_exceeds_max"; + case TIMES_DO_NOT_MATCH_SLOTS = "times_do_not_match_slots"; + case ONGOING_NOT_SUPPORTED = "ongoing_not_supported"; + } } diff --git a/src/Resources/AccessMethod.php b/src/Resources/AccessMethod.php index c4be2ae1..d331fb43 100644 --- a/src/Resources/AccessMethod.php +++ b/src/Resources/AccessMethod.php @@ -1,303 +1,882 @@ access_method_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AccessMethod\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_issued: $json->is_issued ?? null, + issued_at: $json->issued_at ?? null, + mode: $json->mode ?? null, + pending_mutations: array_map( + fn( + $p, + ) => \Seam\Resources\AccessMethod\PendingMutations::from_json( + $p, + ), + $json->pending_mutations ?? [], + ), + warnings: array_map( + fn($w) => \Seam\Resources\AccessMethod\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + client_session_token: $json->client_session_token ?? null, + code: $json->code ?? null, + customization_profile_id: $json->customization_profile_id ?? + null, + instant_key_url: $json->instant_key_url ?? null, + is_assignment_required: $json->is_assignment_required ?? null, + is_encoding_required: $json->is_encoding_required ?? null, + is_ready_for_assignment: $json->is_ready_for_assignment ?? null, + is_ready_for_encoding: $json->is_ready_for_encoding ?? null, + ); } - return new self( - access_method_id: $json->access_method_id ?? null, - client_session_token: $json->client_session_token ?? null, - code: $json->code ?? null, - created_at: $json->created_at ?? null, - customization_profile_id: $json->customization_profile_id ?? null, - display_name: $json->display_name ?? null, - errors: array_map( - fn($e) => AccessMethodErrors::from_json($e), - $json->errors ?? [], - ), - instant_key_url: $json->instant_key_url ?? null, - is_assignment_required: $json->is_assignment_required ?? null, - is_encoding_required: $json->is_encoding_required ?? null, - is_issued: $json->is_issued ?? null, - is_ready_for_assignment: $json->is_ready_for_assignment ?? null, - is_ready_for_encoding: $json->is_ready_for_encoding ?? null, - issued_at: $json->issued_at ?? null, - mode: $json->mode ?? null, - pending_mutations: array_map( - fn($p) => AccessMethodPendingMutations::from_json($p), - $json->pending_mutations ?? [], - ), - warnings: array_map( - fn($w) => AccessMethodWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); + + public function __construct( + /** + * ID of the access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the access method was created. + */ + public string|null $created_at, + /** + * Display name of the access method. + */ + public string|null $display_name, + /** + * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). + * + * @var list<\Seam\Resources\AccessMethod\Errors> + */ + public array $errors, + /** + * Indicates whether the access method has been issued. + */ + public bool|null $is_issued, + /** + * Date and time at which the access method was issued. + */ + public string|null $issued_at, + /** + * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\AccessMethod\Mode>|string|null + */ + public string|null $mode, + /** + * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. + * + * @var list<\Seam\Resources\AccessMethod\PendingMutations> + */ + public array $pending_mutations, + /** + * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). + * + * @var list<\Seam\Resources\AccessMethod\Warnings> + */ + public array $warnings, + /** + * ID of the Seam workspace associated with the access method. + */ + public string|null $workspace_id, + /** + * Token of the client session associated with the access method. + */ + public string|null $client_session_token = null, + /** + * The actual PIN code for code access methods. + */ + public string|null $code = null, + /** + * ID of the customization profile associated with the access method. + */ + public string|null $customization_profile_id = null, + /** + * URL of the Instant Key for mobile key access methods. + */ + public string|null $instant_key_url = null, + /** + * Indicates whether an existing card credential must be assigned to this access method before it can be issued. Only applies to card-mode access methods on systems that support credential assignment. + */ + public bool|null $is_assignment_required = null, + /** + * Indicates whether encoding with an card encoder is required to issue or reissue the plastic card associated with the access method. + */ + public bool|null $is_encoding_required = null, + /** + * Indicates whether the access method is ready for card assignment. This is true when the access method is in card mode, has not yet been issued, and the system supports credential assignment. + */ + public bool|null $is_ready_for_assignment = null, + /** + * Indicates whether the access method is ready to be encoded. This is true when the credential has been created and the card has not yet been issued. + */ + public bool|null $is_ready_for_encoding = null, + ) {} + } +} + +namespace Seam\Resources\AccessMethod { + /** + * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\AccessMethod\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessMethod\Errors\ErrorCode::FAILED_TO_ISSUE + => \Seam\Resources\AccessMethod\Errors\FailedToIssue::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessMethod\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. Known mutation_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class PendingMutations + { + public static function from_json(mixed $json): PendingMutations|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->mutation_code ?? null) + ? \Seam\Resources\AccessMethod\PendingMutations\MutationCode::tryFrom( + $json->mutation_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessMethod\PendingMutations\MutationCode::PROVISIONING_ACCESS + => \Seam\Resources\AccessMethod\PendingMutations\ProvisioningAccess::from_json( + $json, + ), + \Seam\Resources\AccessMethod\PendingMutations\MutationCode::REVOKING_ACCESS + => \Seam\Resources\AccessMethod\PendingMutations\RevokingAccess::from_json( + $json, + ), + \Seam\Resources\AccessMethod\PendingMutations\MutationCode::UPDATING_ACCESS_TIMES + => \Seam\Resources\AccessMethod\PendingMutations\UpdatingAccessTimes::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + public string|null $created_at, + /** + * Detailed description of the mutation. + */ + public string|null $message, + /** + * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. + * + * @var value-of<\Seam\Resources\AccessMethod\PendingMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + ) {} } - public function __construct( - /** - * ID of the access method. - */ - public string|null $access_method_id, - /** - * Token of the client session associated with the access method. - */ - public string|null $client_session_token, - /** - * The actual PIN code for code access methods. - */ - public string|null $code, - /** - * Date and time at which the access method was created. - */ - public string|null $created_at, - /** - * ID of the customization profile associated with the access method. - */ - public string|null $customization_profile_id, - /** - * Display name of the access method. - */ - public string|null $display_name, - /** - * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). - */ - public array $errors, - /** - * URL of the Instant Key for mobile key access methods. - */ - public string|null $instant_key_url, - /** - * Indicates whether an existing card credential must be assigned to this access method before it can be issued. Only applies to card-mode access methods on systems that support credential assignment. - */ - public bool|null $is_assignment_required, - /** - * Indicates whether encoding with an card encoder is required to issue or reissue the plastic card associated with the access method. - */ - public bool|null $is_encoding_required, - /** - * Indicates whether the access method has been issued. - */ - public bool|null $is_issued, - /** - * Indicates whether the access method is ready for card assignment. This is true when the access method is in card mode, has not yet been issued, and the system supports credential assignment. - */ - public bool|null $is_ready_for_assignment, - /** - * Indicates whether the access method is ready to be encoded. This is true when the credential has been created and the card has not yet been issued. - */ - public bool|null $is_ready_for_encoding, - /** - * Date and time at which the access method was issued. - */ - public string|null $issued_at, - /** - * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. - */ - public string|null $mode, - /** - * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. - */ - public array $pending_mutations, - /** - * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). - */ - public array $warnings, - /** - * ID of the Seam workspace associated with the access method. - */ - public string|null $workspace_id, - ) {} + /** + * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\AccessMethod\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AccessMethod\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\AccessMethod\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\AccessMethod\Warnings\WarningCode::UPDATING_ACCESS_TIMES + => \Seam\Resources\AccessMethod\Warnings\UpdatingAccessTimes::from_json( + $json, + ), + \Seam\Resources\AccessMethod\Warnings\WarningCode::PULLED_BACKUP_ACCESS_CODE + => \Seam\Resources\AccessMethod\Warnings\PulledBackupAccessCode::from_json( + $json, + ), + \Seam\Resources\AccessMethod\Warnings\WarningCode::DELAY_IN_ISSUING + => \Seam\Resources\AccessMethod\Warnings\DelayInIssuing::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessMethod\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } + + enum Mode: string + { + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; + } } -/** - * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). - */ -class AccessMethodErrors -{ - public static function from_json(mixed $json): AccessMethodErrors|null +namespace Seam\Resources\AccessMethod\Errors { + /** + * Indicates that Seam was unable to issue this [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant) before its access grant started, so the recipient may be unable to access the space. This usually points to a problem that needs attention, such as an offline or disconnected device. Seam keeps retrying, and this error clears automatically if the access method is eventually issued. + */ + final class FailedToIssue extends \Seam\Resources\AccessMethod\Errors + { + public static function from_json(mixed $json): FailedToIssue|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessMethod\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + enum ErrorCode: string + { + case FAILED_TO_ISSUE = "failed_to_issue"; + } +} + +namespace Seam\Resources\AccessMethod\PendingMutations { + /** + * Seam is in the process of provisioning access for this access method on new devices. + */ + final class ProvisioningAccess extends + \Seam\Resources\AccessMethod\PendingMutations + { + public static function from_json(mixed $json): ProvisioningAccess|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AccessMethod\PendingMutations\ProvisioningAccess\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AccessMethod\PendingMutations\ProvisioningAccess\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous device configuration. + */ + public \Seam\Resources\AccessMethod\PendingMutations\ProvisioningAccess\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. + * + * @var value-of<\Seam\Resources\AccessMethod\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New device configuration. + */ + public \Seam\Resources\AccessMethod\PendingMutations\ProvisioningAccess\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of revoking access for this access method from devices. + */ + final class RevokingAccess extends + \Seam\Resources\AccessMethod\PendingMutations + { + public static function from_json(mixed $json): RevokingAccess|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AccessMethod\PendingMutations\RevokingAccess\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AccessMethod\PendingMutations\RevokingAccess\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous device configuration. + */ + public \Seam\Resources\AccessMethod\PendingMutations\RevokingAccess\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. + * + * @var value-of<\Seam\Resources\AccessMethod\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New device configuration. + */ + public \Seam\Resources\AccessMethod\PendingMutations\RevokingAccess\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of updating the access times for this access method. + */ + final class UpdatingAccessTimes extends + \Seam\Resources\AccessMethod\PendingMutations { - if (!$json) { - return null; + public static function from_json(mixed $json): UpdatingAccessTimes|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AccessMethod\PendingMutations\UpdatingAccessTimes\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AccessMethod\PendingMutations\UpdatingAccessTimes\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous access time configuration. + */ + public \Seam\Resources\AccessMethod\PendingMutations\UpdatingAccessTimes\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. + * + * @var value-of<\Seam\Resources\AccessMethod\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access time configuration. + */ + public \Seam\Resources\AccessMethod\PendingMutations\UpdatingAccessTimes\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} + enum MutationCode: string + { + case PROVISIONING_ACCESS = "provisioning_access"; + case REVOKING_ACCESS = "revoking_access"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + } } -/** - * Previous device configuration. - */ -class AccessMethodFrom -{ - public static function from_json(mixed $json): AccessMethodFrom|null +namespace Seam\Resources\AccessMethod\PendingMutations\ProvisioningAccess { + /** + * Previous device configuration. + */ + class From { - if (!$json) { - return null; + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); } - return new self( - device_ids: $json->device_ids ?? null, - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); + + public function __construct( + /** + * Previous device IDs where access was provisioned. + * + * @var list|null + */ + public array|null $device_ids, + ) {} } - public function __construct( - /** - * Previous device IDs where access was provisioned. - */ - public array|null $device_ids, - /** - * Previous end time for access. - */ - public string|null $ends_at, - /** - * Previous start time for access. - */ - public string|null $starts_at, - ) {} + /** + * New device configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); + } + + public function __construct( + /** + * New device IDs where access is being provisioned. + * + * @var list|null + */ + public array|null $device_ids, + ) {} + } } -/** - * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. - */ -class AccessMethodPendingMutations -{ - public static function from_json( - mixed $json, - ): AccessMethodPendingMutations|null { - if (!$json) { - return null; +namespace Seam\Resources\AccessMethod\PendingMutations\RevokingAccess { + /** + * Previous device configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); } - return new self( - created_at: $json->created_at ?? null, - from: isset($json->from) - ? AccessMethodFrom::from_json($json->from) - : null, - message: $json->message ?? null, - mutation_code: $json->mutation_code ?? null, - to: isset($json->to) ? AccessMethodTo::from_json($json->to) : null, - ); + + public function __construct( + /** + * Previous device IDs where access existed. + * + * @var list|null + */ + public array|null $device_ids, + ) {} } - public function __construct( - /** - * Date and time at which the mutation was created. - */ - public string|null $created_at, - /** - * Previous device configuration. - */ - public AccessMethodFrom|null $from, - /** - * Detailed description of the mutation. - */ - public string|null $message, - /** - * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. - */ - public string|null $mutation_code, - /** - * New device configuration. - */ - public AccessMethodTo|null $to, - ) {} + /** + * New device configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); + } + + public function __construct( + /** + * New device IDs where access should remain. + * + * @var list|null + */ + public array|null $device_ids, + ) {} + } } -/** - * New device configuration. - */ -class AccessMethodTo -{ - public static function from_json(mixed $json): AccessMethodTo|null +namespace Seam\Resources\AccessMethod\PendingMutations\UpdatingAccessTimes { + /** + * Previous access time configuration. + */ + class From { - if (!$json) { - return null; + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); } - return new self( - device_ids: $json->device_ids ?? null, - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); + + public function __construct( + /** + * Previous end time for access. + */ + public string|null $ends_at, + /** + * Previous start time for access. + */ + public string|null $starts_at, + ) {} } - public function __construct( - /** - * New device IDs where access is being provisioned. - */ - public array|null $device_ids, - /** - * New end time for access. - */ - public string|null $ends_at, - /** - * New start time for access. - */ - public string|null $starts_at, - ) {} + /** + * New access time configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * New end time for access. + */ + public string|null $ends_at, + /** + * New start time for access. + */ + public string|null $starts_at, + ) {} + } } -/** - * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). - */ -class AccessMethodWarnings -{ - public static function from_json(mixed $json): AccessMethodWarnings|null +namespace Seam\Resources\AccessMethod\Warnings { + /** + * Indicates that the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant) is being deleted. + */ + final class BeingDeleted extends \Seam\Resources\AccessMethod\Warnings { - if (!$json) { - return null; + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessMethod\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - original_access_method_id: $json->original_access_method_id ?? null, - warning_code: $json->warning_code ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * ID of the original access method from which this backup access method was split, if applicable. - */ - public string|null $original_access_method_id, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + /** + * Indicates that the access times for this [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant) are being updated. + */ + final class UpdatingAccessTimes extends + \Seam\Resources\AccessMethod\Warnings + { + public static function from_json(mixed $json): UpdatingAccessTimes|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessMethod\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that all attempts to create an access code on this device before the start time failed and a backup access code was used to ensure access was provided in time. + */ + final class PulledBackupAccessCode extends + \Seam\Resources\AccessMethod\Warnings + { + public static function from_json( + mixed $json, + ): PulledBackupAccessCode|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + original_access_method_id: $json->original_access_method_id ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessMethod\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * ID of the original access method from which this backup access method was split, if applicable. + */ + public string|null $original_access_method_id = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that Seam has not yet issued this [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant), even though its access grant is about to begin, so access may not be ready when the recipient arrives. Seam is still attempting to issue it, and this warning clears automatically once issuance succeeds. + */ + final class DelayInIssuing extends \Seam\Resources\AccessMethod\Warnings + { + public static function from_json(mixed $json): DelayInIssuing|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AccessMethod\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case BEING_DELETED = "being_deleted"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + case PULLED_BACKUP_ACCESS_CODE = "pulled_backup_access_code"; + case DELAY_IN_ISSUING = "delay_in_issuing"; + } } diff --git a/src/Resources/AcsAccessGroup.php b/src/Resources/AcsAccessGroup.php index 9d8d7bc4..463cd3d6 100644 --- a/src/Resources/AcsAccessGroup.php +++ b/src/Resources/AcsAccessGroup.php @@ -1,359 +1,1045 @@ access_group_type ?? null, + access_group_type_display_name: $json->access_group_type_display_name ?? + null, + acs_access_group_id: $json->acs_access_group_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AcsAccessGroup\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + external_type: $json->external_type ?? null, + external_type_display_name: $json->external_type_display_name ?? + null, + is_managed: $json->is_managed ?? null, + name: $json->name ?? null, + pending_mutations: array_map( + fn( + $p, + ) => \Seam\Resources\AcsAccessGroup\PendingMutations::from_json( + $p, + ), + $json->pending_mutations ?? [], + ), + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\AcsAccessGroup\Warnings::from_json($w), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + access_schedule: isset($json->access_schedule) + ? \Seam\Resources\AcsAccessGroup\AccessSchedule::from_json( + $json->access_schedule, + ) + : null, + ); } - return new self( - access_group_type: $json->access_group_type ?? null, - access_group_type_display_name: $json->access_group_type_display_name ?? - null, - access_schedule: isset($json->access_schedule) - ? AcsAccessGroupAccessSchedule::from_json( - $json->access_schedule, + + public function __construct( + /** + * @var value-of<\Seam\Resources\AcsAccessGroup\AccessGroupType>|string|null + * @deprecated Use `external_type`. + */ + public string|null $access_group_type, + /** + * @deprecated Use `external_type_display_name`. + */ + public string|null $access_group_type_display_name, + /** + * ID of the access group. + */ + public string|null $acs_access_group_id, + /** + * ID of the access control system that contains the access group. + */ + public string|null $acs_system_id, + /** + * ID of the connected account that contains the access group. + */ + public string|null $connected_account_id, + /** + * Date and time at which the access group was created. + */ + public string|null $created_at, + /** + * Display name for the access group. + */ + public string|null $display_name, + /** + * Errors associated with the `acs_access_group`. + * + * @var list<\Seam\Resources\AcsAccessGroup\Errors> + */ + public array $errors, + /** + * Brand-specific terminology for the access group type. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\ExternalType>|string|null + */ + public string|null $external_type, + /** + * Display name that corresponds to the brand-specific terminology for the access group type. + */ + public string|null $external_type_display_name, + /** + * Indicates whether Seam manages the access group. + */ + public true|null $is_managed, + /** + * Name of the access group. + */ + public string|null $name, + /** + * Collection of pending mutations for the access group. Represents operations that have been requested but not yet completed on the integrated access system. + * + * @var list<\Seam\Resources\AcsAccessGroup\PendingMutations> + */ + public array $pending_mutations, + /** + * Warnings associated with the `acs_access_group`. + * + * @var list<\Seam\Resources\AcsAccessGroup\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the access group. + */ + public string|null $workspace_id, + /** + * `starts_at` and `ends_at` timestamps for the access group's access. + */ + public \Seam\Resources\AcsAccessGroup\AccessSchedule|null $access_schedule = null, + ) {} + } +} + +namespace Seam\Resources\AcsAccessGroup { + /** + * `starts_at` and `ends_at` timestamps for the access group's access. + */ + class AccessSchedule + { + public static function from_json(mixed $json): AccessSchedule|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the user's access ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $ends_at, + /** + * Date and time at which the user's access starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at, + ) {} + } + + /** + * Errors associated with the `acs_access_group`. Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\AcsAccessGroup\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AcsAccessGroup\Errors\ErrorCode::FAILED_TO_CREATE_ON_ACS_SYSTEM + => \Seam\Resources\AcsAccessGroup\Errors\FailedToCreateOnAcsSystem::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Collection of pending mutations for the access group. Represents operations that have been requested but not yet completed on the integrated access system. Known mutation_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class PendingMutations + { + public static function from_json(mixed $json): PendingMutations|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->mutation_code ?? null) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::tryFrom( + $json->mutation_code, ) - : null, - acs_access_group_id: $json->acs_access_group_id ?? null, - acs_system_id: $json->acs_system_id ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - errors: array_map( - fn($e) => AcsAccessGroupErrors::from_json($e), - $json->errors ?? [], - ), - external_type: $json->external_type ?? null, - external_type_display_name: $json->external_type_display_name ?? - null, - is_managed: $json->is_managed ?? null, - name: $json->name ?? null, - pending_mutations: array_map( - fn($p) => AcsAccessGroupPendingMutations::from_json($p), - $json->pending_mutations ?? [], - ), - warnings: array_map( - fn($w) => AcsAccessGroupWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); + : null; + + return match ($discriminant) { + \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::CREATING + => \Seam\Resources\AcsAccessGroup\PendingMutations\Creating::from_json( + $json, + ), + \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::DELETING + => \Seam\Resources\AcsAccessGroup\PendingMutations\Deleting::from_json( + $json, + ), + \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::DEFERRING_DELETION + => \Seam\Resources\AcsAccessGroup\PendingMutations\DeferringDeletion::from_json( + $json, + ), + \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::UPDATING_GROUP_INFORMATION + => \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingGroupInformation::from_json( + $json, + ), + \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::UPDATING_ACCESS_SCHEDULE + => \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingAccessSchedule::from_json( + $json, + ), + \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::UPDATING_USER_MEMBERSHIP + => \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingUserMembership::from_json( + $json, + ), + \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::UPDATING_ENTRANCE_MEMBERSHIP + => \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingEntranceMembership::from_json( + $json, + ), + \Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode::DEFERRING_USER_MEMBERSHIP_UPDATE + => \Seam\Resources\AcsAccessGroup\PendingMutations\DeferringUserMembershipUpdate::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + public string|null $created_at, + /** + * Detailed description of the mutation. + */ + public string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + ) {} + } + + /** + * Warnings associated with the `acs_access_group`. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } + + enum AccessGroupType: string + { + case PTI_UNIT = "pti_unit"; + case PTI_ACCESS_LEVEL = "pti_access_level"; + case SALTO_KS_ACCESS_GROUP = "salto_ks_access_group"; + case BRIVO_GROUP = "brivo_group"; + case SALTO_SPACE_GROUP = "salto_space_group"; + case DORMAKABA_COMMUNITY_ACCESS_GROUP = "dormakaba_community_access_group"; + case DORMAKABA_AMBIANCE_ACCESS_GROUP = "dormakaba_ambiance_access_group"; + case AVIGILON_ALTA_GROUP = "avigilon_alta_group"; + case KISI_ACCESS_GROUP = "kisi_access_group"; + case AKILES_MEMBER_GROUP = "akiles_member_group"; } - public function __construct( - /** - * @deprecated Use `external_type`. - */ - public string|null $access_group_type, - /** - * @deprecated Use `external_type_display_name`. - */ - public string|null $access_group_type_display_name, - /** - * `starts_at` and `ends_at` timestamps for the access group's access. - */ - public AcsAccessGroupAccessSchedule|null $access_schedule, - /** - * ID of the access group. - */ - public string|null $acs_access_group_id, - /** - * ID of the access control system that contains the access group. - */ - public string|null $acs_system_id, - /** - * ID of the connected account that contains the access group. - */ - public string|null $connected_account_id, - /** - * Date and time at which the access group was created. - */ - public string|null $created_at, - /** - * Display name for the access group. - */ - public string|null $display_name, - /** - * Errors associated with the `acs_access_group`. - */ - public array $errors, - /** - * Brand-specific terminology for the access group type. - */ - public string|null $external_type, - /** - * Display name that corresponds to the brand-specific terminology for the access group type. - */ - public string|null $external_type_display_name, - /** - * Indicates whether Seam manages the access group. - */ - public bool|null $is_managed, - /** - * Name of the access group. - */ - public string|null $name, - /** - * Collection of pending mutations for the access group. Represents operations that have been requested but not yet completed on the integrated access system. - */ - public array $pending_mutations, - /** - * Warnings associated with the `acs_access_group`. - */ - public array $warnings, - /** - * ID of the workspace that contains the access group. - */ - public string|null $workspace_id, - ) {} + enum ExternalType: string + { + case PTI_UNIT = "pti_unit"; + case PTI_ACCESS_LEVEL = "pti_access_level"; + case SALTO_KS_ACCESS_GROUP = "salto_ks_access_group"; + case BRIVO_GROUP = "brivo_group"; + case SALTO_SPACE_GROUP = "salto_space_group"; + case DORMAKABA_COMMUNITY_ACCESS_GROUP = "dormakaba_community_access_group"; + case DORMAKABA_AMBIANCE_ACCESS_GROUP = "dormakaba_ambiance_access_group"; + case AVIGILON_ALTA_GROUP = "avigilon_alta_group"; + case KISI_ACCESS_GROUP = "kisi_access_group"; + case AKILES_MEMBER_GROUP = "akiles_member_group"; + } } -/** - * `starts_at` and `ends_at` timestamps for the access group's access. - */ -class AcsAccessGroupAccessSchedule -{ - public static function from_json( - mixed $json, - ): AcsAccessGroupAccessSchedule|null { - if (!$json) { - return null; +namespace Seam\Resources\AcsAccessGroup\Errors { + /** + * Indicates that the [access group](https://docs.seam.co/low-level-apis/access-systems/user-management/assigning-users-to-access-groups) was not created on the [access system](https://docs.seam.co/low-level-apis/access-systems). This is likely due to an internal unexpected error. Contact Seam [support](mailto:support@seam.co). + */ + final class FailedToCreateOnAcsSystem extends + \Seam\Resources\AcsAccessGroup\Errors + { + public static function from_json( + mixed $json, + ): FailedToCreateOnAcsSystem|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); } - return new self( - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); } - public function __construct( - /** - * Date and time at which the user's access ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $ends_at, - /** - * Date and time at which the user's access starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at, - ) {} + enum ErrorCode: string + { + case FAILED_TO_CREATE_ON_ACS_SYSTEM = "failed_to_create_on_acs_system"; + } } -/** - * Errors associated with the `acs_access_group`. - */ -class AcsAccessGroupErrors -{ - public static function from_json(mixed $json): AcsAccessGroupErrors|null +namespace Seam\Resources\AcsAccessGroup\PendingMutations { + /** + * Seam is in the process of pushing an access group creation to the integrated access system. + */ + final class Creating extends \Seam\Resources\AcsAccessGroup\PendingMutations + { + public static function from_json(mixed $json): Creating|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an access group deletion to the integrated access system. + */ + final class Deleting extends \Seam\Resources\AcsAccessGroup\PendingMutations + { + public static function from_json(mixed $json): Deleting|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * This access group is scheduled for automatic deletion when its access window expires. + */ + final class DeferringDeletion extends + \Seam\Resources\AcsAccessGroup\PendingMutations + { + public static function from_json(mixed $json): DeferringDeletion|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an access group information update to the integrated access system. + */ + final class UpdatingGroupInformation extends + \Seam\Resources\AcsAccessGroup\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingGroupInformation|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingGroupInformation\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingGroupInformation\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Old access group information. + */ + public \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingGroupInformation\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access group information. + */ + public \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingGroupInformation\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an access schedule update to the integrated access system. + */ + final class UpdatingAccessSchedule extends + \Seam\Resources\AcsAccessGroup\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingAccessSchedule|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingAccessSchedule\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingAccessSchedule\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Old access schedule information. + */ + public \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingAccessSchedule\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access schedule information. + */ + public \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingAccessSchedule\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing a user membership update to the integrated access system. + */ + final class UpdatingUserMembership extends + \Seam\Resources\AcsAccessGroup\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingUserMembership|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingUserMembership\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingUserMembership\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Old user membership. + */ + public \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingUserMembership\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New user membership. + */ + public \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingUserMembership\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an entrance membership update to the integrated access system. + */ + final class UpdatingEntranceMembership extends + \Seam\Resources\AcsAccessGroup\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingEntranceMembership|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingEntranceMembership\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingEntranceMembership\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Old entrance membership. + */ + public \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingEntranceMembership\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New entrance membership. + */ + public \Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingEntranceMembership\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * A scheduled user membership change is pending for this access group. + */ + final class DeferringUserMembershipUpdate extends + \Seam\Resources\AcsAccessGroup\PendingMutations { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): DeferringUserMembershipUpdate|null { + if (!$json) { + return null; + } + return new self( + acs_user_id: $json->acs_user_id ?? null, + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + variant: $json->variant ?? null, + ); + } + + public function __construct( + /** + * ID of the user involved in the scheduled change. + */ + public string|null $acs_user_id, + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * Whether the user is scheduled to be added to or removed from this access group. + * + * @var value-of<\Seam\Resources\AcsAccessGroup\PendingMutations\DeferringUserMembershipUpdate\Variant>|string|null + */ + public string|null $variant, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} + enum MutationCode: string + { + case CREATING = "creating"; + case DELETING = "deleting"; + case DEFERRING_DELETION = "deferring_deletion"; + case UPDATING_GROUP_INFORMATION = "updating_group_information"; + case UPDATING_ACCESS_SCHEDULE = "updating_access_schedule"; + case UPDATING_USER_MEMBERSHIP = "updating_user_membership"; + case UPDATING_ENTRANCE_MEMBERSHIP = "updating_entrance_membership"; + case DEFERRING_USER_MEMBERSHIP_UPDATE = "deferring_user_membership_update"; + } } -/** - * Old access group information. - */ -class AcsAccessGroupFrom -{ - public static function from_json(mixed $json): AcsAccessGroupFrom|null +namespace Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingGroupInformation { + /** + * Old access group information. + */ + class From { - if (!$json) { - return null; + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(name: $json->name ?? null); } - return new self( - acs_entrance_id: $json->acs_entrance_id ?? null, - acs_user_id: $json->acs_user_id ?? null, - ends_at: $json->ends_at ?? null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - ); + + public function __construct( + /** + * Name of the access group. + */ + public string|null $name = null, + ) {} } - public function __construct( - /** - * Old entrance ID. - */ - public string|null $acs_entrance_id, - /** - * Old user ID. - */ - public string|null $acs_user_id, - /** - * Ending time for the access schedule. - */ - public string|null $ends_at, - /** - * Name of the access group. - */ - public string|null $name, - /** - * Starting time for the access schedule. - */ - public string|null $starts_at, - ) {} + /** + * New access group information. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(name: $json->name ?? null); + } + + public function __construct( + /** + * Name of the access group. + */ + public string|null $name = null, + ) {} + } } -/** - * Collection of pending mutations for the access group. Represents operations that have been requested but not yet completed on the integrated access system. - */ -class AcsAccessGroupPendingMutations -{ - public static function from_json( - mixed $json, - ): AcsAccessGroupPendingMutations|null { - if (!$json) { - return null; +namespace Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingAccessSchedule { + /** + * Old access schedule information. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); } - return new self( - acs_user_id: $json->acs_user_id ?? null, - created_at: $json->created_at ?? null, - from: isset($json->from) - ? AcsAccessGroupFrom::from_json($json->from) - : null, - message: $json->message ?? null, - mutation_code: $json->mutation_code ?? null, - to: isset($json->to) - ? AcsAccessGroupTo::from_json($json->to) - : null, - variant: $json->variant ?? null, - ); + + public function __construct( + /** + * Ending time for the access schedule. + */ + public string|null $ends_at, + /** + * Starting time for the access schedule. + */ + public string|null $starts_at, + ) {} } - public function __construct( - /** - * ID of the user involved in the scheduled change. - */ - public string|null $acs_user_id, - /** - * Date and time at which the mutation was created. - */ - public string|null $created_at, - /** - * Old access group information. - */ - public AcsAccessGroupFrom|null $from, - /** - * Detailed description of the mutation. - */ - public string|null $message, - /** - * Mutation code to indicate that Seam is in the process of pushing an access group creation to the integrated access system. - */ - public string|null $mutation_code, - /** - * New access group information. - */ - public AcsAccessGroupTo|null $to, - /** - * Whether the user is scheduled to be added to or removed from this access group. - */ - public string|null $variant, - ) {} + /** + * New access schedule information. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Ending time for the access schedule. + */ + public string|null $ends_at, + /** + * Starting time for the access schedule. + */ + public string|null $starts_at, + ) {} + } } -/** - * New access group information. - */ -class AcsAccessGroupTo -{ - public static function from_json(mixed $json): AcsAccessGroupTo|null +namespace Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingUserMembership { + /** + * Old user membership. + */ + class From { - if (!$json) { - return null; + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(acs_user_id: $json->acs_user_id ?? null); } - return new self( - acs_entrance_id: $json->acs_entrance_id ?? null, - acs_user_id: $json->acs_user_id ?? null, - ends_at: $json->ends_at ?? null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - ); + + public function __construct( + /** + * Old user ID. + */ + public string|null $acs_user_id, + ) {} } - public function __construct( - /** - * New entrance ID. - */ - public string|null $acs_entrance_id, - /** - * New user ID. - */ - public string|null $acs_user_id, - /** - * Ending time for the access schedule. - */ - public string|null $ends_at, - /** - * Name of the access group. - */ - public string|null $name, - /** - * Starting time for the access schedule. - */ - public string|null $starts_at, - ) {} + /** + * New user membership. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(acs_user_id: $json->acs_user_id ?? null); + } + + public function __construct( + /** + * New user ID. + */ + public string|null $acs_user_id, + ) {} + } } -/** - * Warnings associated with the `acs_access_group`. - */ -class AcsAccessGroupWarnings -{ - public static function from_json(mixed $json): AcsAccessGroupWarnings|null +namespace Seam\Resources\AcsAccessGroup\PendingMutations\UpdatingEntranceMembership { + /** + * Old entrance membership. + */ + class From { - if (!$json) { - return null; + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(acs_entrance_id: $json->acs_entrance_id ?? null); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); + + public function __construct( + /** + * Old entrance ID. + */ + public string|null $acs_entrance_id, + ) {} } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + /** + * New entrance membership. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(acs_entrance_id: $json->acs_entrance_id ?? null); + } + + public function __construct( + /** + * New entrance ID. + */ + public string|null $acs_entrance_id, + ) {} + } +} + +namespace Seam\Resources\AcsAccessGroup\PendingMutations\DeferringUserMembershipUpdate { + enum Variant: string + { + case ADDING = "adding"; + case REMOVING = "removing"; + } +} + +namespace Seam\Resources\AcsAccessGroup\Warnings { + enum WarningCode: string + { + case UNKNOWN_ISSUE_WITH_ACS_ACCESS_GROUP = "unknown_issue_with_acs_access_group"; + case BEING_DELETED = "being_deleted"; + } } diff --git a/src/Resources/AcsCredential.php b/src/Resources/AcsCredential.php index 6ec383e7..87f5c279 100644 --- a/src/Resources/AcsCredential.php +++ b/src/Resources/AcsCredential.php @@ -1,383 +1,801 @@ access_method ?? null, - acs_credential_id: $json->acs_credential_id ?? null, - acs_credential_pool_id: $json->acs_credential_pool_id ?? null, - acs_system_id: $json->acs_system_id ?? null, - acs_user_id: $json->acs_user_id ?? null, - akiles_metadata: isset($json->akiles_metadata) - ? AcsCredentialAkilesMetadata::from_json($json->akiles_metadata) - : null, - assa_abloy_vostio_metadata: isset($json->assa_abloy_vostio_metadata) - ? AcsCredentialAssaAbloyVostioMetadata::from_json( + public static function from_json(mixed $json): AcsCredential|null + { + if (!$json) { + return null; + } + return new self( + access_method: $json->access_method ?? null, + acs_credential_id: $json->acs_credential_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AcsCredential\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_managed: $json->is_managed ?? null, + warnings: array_map( + fn($w) => \Seam\Resources\AcsCredential\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + acs_credential_pool_id: $json->acs_credential_pool_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + akiles_metadata: isset($json->akiles_metadata) + ? \Seam\Resources\AcsCredential\AkilesMetadata::from_json( + $json->akiles_metadata, + ) + : null, + assa_abloy_vostio_metadata: isset( $json->assa_abloy_vostio_metadata, ) - : null, - card_number: $json->card_number ?? null, - code: $json->code ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => AcsCredentialErrors::from_json($e), - $json->errors ?? [], - ), - external_type: $json->external_type ?? null, - external_type_display_name: $json->external_type_display_name ?? - null, - is_issued: $json->is_issued ?? null, - is_latest_desired_state_synced_with_provider: $json->is_latest_desired_state_synced_with_provider ?? - null, - is_managed: $json->is_managed ?? null, - is_multi_phone_sync_credential: $json->is_multi_phone_sync_credential ?? - null, - is_one_time_use: $json->is_one_time_use ?? null, - issued_at: $json->issued_at ?? null, - latest_desired_state_synced_with_provider_at: $json->latest_desired_state_synced_with_provider_at ?? - null, - parent_acs_credential_id: $json->parent_acs_credential_id ?? null, - starts_at: $json->starts_at ?? null, - user_identity_id: $json->user_identity_id ?? null, - visionline_metadata: isset($json->visionline_metadata) - ? AcsCredentialVisionlineMetadata::from_json( - $json->visionline_metadata, + ? \Seam\Resources\AcsCredential\AssaAbloyVostioMetadata::from_json( + $json->assa_abloy_vostio_metadata, + ) + : null, + card_number: $json->card_number ?? null, + code: $json->code ?? null, + ends_at: $json->ends_at ?? null, + external_type: $json->external_type ?? null, + external_type_display_name: $json->external_type_display_name ?? + null, + is_issued: $json->is_issued ?? null, + is_latest_desired_state_synced_with_provider: $json->is_latest_desired_state_synced_with_provider ?? + null, + is_multi_phone_sync_credential: $json->is_multi_phone_sync_credential ?? + null, + is_one_time_use: $json->is_one_time_use ?? null, + issued_at: $json->issued_at ?? null, + latest_desired_state_synced_with_provider_at: $json->latest_desired_state_synced_with_provider_at ?? + null, + parent_acs_credential_id: $json->parent_acs_credential_id ?? + null, + starts_at: $json->starts_at ?? null, + user_identity_id: $json->user_identity_id ?? null, + visionline_metadata: isset($json->visionline_metadata) + ? \Seam\Resources\AcsCredential\VisionlineMetadata::from_json( + $json->visionline_metadata, + ) + : null, + ); + } + + public function __construct( + /** + * Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\AcsCredential\AccessMethod>|string|null + */ + public string|null $access_method, + /** + * ID of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $acs_credential_id, + /** + * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $acs_system_id, + /** + * ID of the [connected account](https://docs.seam.co/core-concepts/connected-accounts) to which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $connected_account_id, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. + */ + public string|null $created_at, + /** + * Display name that corresponds to the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. + */ + public string|null $display_name, + /** + * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list<\Seam\Resources\AcsCredential\Errors> + */ + public array $errors, + /** + * Indicates whether Seam manages the credential. + */ + public true|null $is_managed, + /** + * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list<\Seam\Resources\AcsCredential\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $workspace_id, + /** + * ID of the credential pool to which the credential belongs. + */ + public string|null $acs_credential_pool_id = null, + /** + * ID of the [ACS user](https://docs.seam.co/low-level-apis/access-systems/user-management) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $acs_user_id = null, + /** + * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\AcsCredential\AkilesMetadata|null $akiles_metadata = null, + /** + * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\AcsCredential\AssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata = null, + /** + * Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $card_number = null, + /** + * Access (PIN) code for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $code = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. + */ + public string|null $ends_at = null, + /** + * Brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. Supported values: `pti_card`, `brivo_credential`, `hid_credential`, `visionline_card`. + * + * @var value-of<\Seam\Resources\AcsCredential\ExternalType>|string|null + */ + public string|null $external_type = null, + /** + * Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. + */ + public string|null $external_type_display_name = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been encoded onto a card. + */ + public bool|null $is_issued = null, + /** + * Indicates whether the latest state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been synced from Seam to the provider. + */ + public bool|null $is_latest_desired_state_synced_with_provider = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is a [multi-phone sync credential](https://docs.seam.co/capability-guides/mobile-access/issuing-mobile-credentials-from-an-access-control-system#what-are-multi-phone-sync-credentials). + */ + public bool|null $is_multi_phone_sync_credential = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) can only be used once. If `true`, the code becomes invalid after the first use. + */ + public bool|null $is_one_time_use = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was encoded onto a card. + */ + public string|null $issued_at = null, + /** + * Date and time at which the state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was most recently synced from Seam to the provider. + */ + public string|null $latest_desired_state_synced_with_provider_at = null, + /** + * ID of the parent [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $parent_acs_credential_id = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at = null, + /** + * ID of the [user identity](https://docs.seam.co/api/user_identities) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $user_identity_id = null, + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\AcsCredential\VisionlineMetadata|null $visionline_metadata = null, + ) {} + } +} + +namespace Seam\Resources\AcsCredential { + /** + * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class AkilesMetadata + { + public static function from_json(mixed $json): AkilesMetadata|null + { + if (!$json) { + return null; + } + return new self(member_pin_id: $json->member_pin_id ?? null); + } + + public function __construct( + /** + * ID of the Akiles member PIN. + */ + public string|null $member_pin_id = null, + ) {} + } + + /** + * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class AssaAbloyVostioMetadata + { + public static function from_json( + mixed $json, + ): AssaAbloyVostioMetadata|null { + if (!$json) { + return null; + } + return new self( + auto_join: $json->auto_join ?? null, + door_names: $json->door_names ?? null, + endpoint_id: $json->endpoint_id ?? null, + key_id: $json->key_id ?? null, + key_issuing_request_id: $json->key_issuing_request_id ?? null, + override_guest_acs_entrance_ids: $json->override_guest_acs_entrance_ids ?? + null, + ); + } + + public function __construct( + /** + * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. + */ + public bool|null $auto_join = null, + /** + * Names of the doors to which to grant access in the Vostio access system. + * + * @var list|null + */ + public array|null $door_names = null, + /** + * Endpoint ID in the Vostio access system. + */ + public string|null $endpoint_id = null, + /** + * Key ID in the Vostio access system. + */ + public string|null $key_id = null, + /** + * Key issuing request ID in the Vostio access system. + */ + public string|null $key_issuing_request_id = null, + /** + * IDs of the guest entrances to override in the Vostio access system. + * + * @var list|null + */ + public array|null $override_guest_acs_entrance_ids = null, + ) {} + } + + /** + * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + public string|null $error_code, + public string|null $message, + ) {} + } + + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class VisionlineMetadata + { + public static function from_json(mixed $json): VisionlineMetadata|null + { + if (!$json) { + return null; + } + return new self( + auto_join: $json->auto_join ?? null, + card_function_type: $json->card_function_type ?? null, + card_id: $json->card_id ?? null, + common_acs_entrance_ids: $json->common_acs_entrance_ids ?? null, + credential_id: $json->credential_id ?? null, + guest_acs_entrance_ids: $json->guest_acs_entrance_ids ?? null, + is_valid: $json->is_valid ?? null, + joiner_acs_credential_ids: $json->joiner_acs_credential_ids ?? + null, + ); + } + + public function __construct( + /** + * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. + */ + public bool|null $auto_join = null, + /** + * Card function type in the Visionline access system. + * + * @var value-of<\Seam\Resources\AcsCredential\VisionlineMetadata\CardFunctionType>|string|null + */ + public string|null $card_function_type = null, + /** + * ID of the card in the Visionline access system. + */ + public string|null $card_id = null, + /** + * Common entrance IDs in the Visionline access system. + * + * @var list|null + */ + public array|null $common_acs_entrance_ids = null, + /** + * ID of the credential in the Visionline access system. + */ + public string|null $credential_id = null, + /** + * Guest entrance IDs in the Visionline access system. + * + * @var list|null + */ + public array|null $guest_acs_entrance_ids = null, + /** + * Indicates whether the credential is valid. + */ + public bool|null $is_valid = null, + /** + * IDs of the credentials to which you want to join. + * + * @var list|null + */ + public array|null $joiner_acs_credential_ids = null, + ) {} + } + + /** + * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\AcsCredential\Warnings\WarningCode::tryFrom( + $json->warning_code, ) - : null, - warnings: array_map( - fn($w) => AcsCredentialWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); + : null; + + return match ($discriminant) { + \Seam\Resources\AcsCredential\Warnings\WarningCode::WAITING_TO_BE_ISSUED + => \Seam\Resources\AcsCredential\Warnings\WaitingToBeIssued::from_json( + $json, + ), + \Seam\Resources\AcsCredential\Warnings\WarningCode::SCHEDULE_EXTERNALLY_MODIFIED + => \Seam\Resources\AcsCredential\Warnings\ScheduleExternallyModified::from_json( + $json, + ), + \Seam\Resources\AcsCredential\Warnings\WarningCode::SCHEDULE_MODIFIED + => \Seam\Resources\AcsCredential\Warnings\ScheduleModified::from_json( + $json, + ), + \Seam\Resources\AcsCredential\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\AcsCredential\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\AcsCredential\Warnings\WarningCode::UNKNOWN_ISSUE_WITH_ACS_CREDENTIAL + => \Seam\Resources\AcsCredential\Warnings\UnknownIssueWithAcsCredential::from_json( + $json, + ), + \Seam\Resources\AcsCredential\Warnings\WarningCode::NEEDS_TO_BE_REISSUED + => \Seam\Resources\AcsCredential\Warnings\NeedsToBeReissued::from_json( + $json, + ), + \Seam\Resources\AcsCredential\Warnings\WarningCode::REQUESTED_CODE_UNAVAILABLE + => \Seam\Resources\AcsCredential\Warnings\RequestedCodeUnavailable::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsCredential\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } + + enum AccessMethod: string + { + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; } - public function __construct( - /** - * Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. - */ - public string|null $access_method, - /** - * ID of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $acs_credential_id, - /** - * ID of the credential pool to which the credential belongs. - */ - public string|null $acs_credential_pool_id, - /** - * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $acs_system_id, - /** - * ID of the [ACS user](https://docs.seam.co/low-level-apis/access-systems/user-management) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $acs_user_id, - /** - * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public AcsCredentialAkilesMetadata|null $akiles_metadata, - /** - * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public AcsCredentialAssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata, - /** - * Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $card_number, - /** - * Access (PIN) code for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $code, - /** - * ID of the [connected account](https://docs.seam.co/core-concepts/connected-accounts) to which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $connected_account_id, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. - */ - public string|null $created_at, - /** - * Display name that corresponds to the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. - */ - public string|null $display_name, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. - */ - public string|null $ends_at, - /** - * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public array $errors, - /** - * Brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. Supported values: `pti_card`, `brivo_credential`, `hid_credential`, `visionline_card`. - */ - public string|null $external_type, - /** - * Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. - */ - public string|null $external_type_display_name, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been encoded onto a card. - */ - public bool|null $is_issued, - /** - * Indicates whether the latest state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been synced from Seam to the provider. - */ - public bool|null $is_latest_desired_state_synced_with_provider, - /** - * Indicates whether Seam manages the credential. - */ - public bool|null $is_managed, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is a [multi-phone sync credential](https://docs.seam.co/capability-guides/mobile-access/issuing-mobile-credentials-from-an-access-control-system#what-are-multi-phone-sync-credentials). - */ - public bool|null $is_multi_phone_sync_credential, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) can only be used once. If `true`, the code becomes invalid after the first use. - */ - public bool|null $is_one_time_use, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was encoded onto a card. - */ - public string|null $issued_at, - /** - * Date and time at which the state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was most recently synced from Seam to the provider. - */ - public string|null $latest_desired_state_synced_with_provider_at, - /** - * ID of the parent [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $parent_acs_credential_id, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at, - /** - * ID of the [user identity](https://docs.seam.co/api/user_identities) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $user_identity_id, - /** - * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public AcsCredentialVisionlineMetadata|null $visionline_metadata, - /** - * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public array $warnings, - /** - * ID of the workspace that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $workspace_id, - ) {} + enum ExternalType: string + { + case PTI_CARD = "pti_card"; + case BRIVO_CREDENTIAL = "brivo_credential"; + case HID_CREDENTIAL = "hid_credential"; + case VISIONLINE_CARD = "visionline_card"; + case SALTO_KS_CREDENTIAL = "salto_ks_credential"; + case ASSA_ABLOY_VOSTIO_KEY = "assa_abloy_vostio_key"; + case SALTO_SPACE_KEY = "salto_space_key"; + case LATCH_ACCESS = "latch_access"; + case DORMAKABA_AMBIANCE_CREDENTIAL = "dormakaba_ambiance_credential"; + case HOTEK_CARD = "hotek_card"; + case SALTO_KS_TAG = "salto_ks_tag"; + case AVIGILON_ALTA_CREDENTIAL = "avigilon_alta_credential"; + case KISI_CREDENTIAL = "kisi_credential"; + case AKILES_CREDENTIAL = "akiles_credential"; + } } -/** - * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class AcsCredentialAkilesMetadata -{ - public static function from_json( - mixed $json, - ): AcsCredentialAkilesMetadata|null { - if (!$json) { - return null; +namespace Seam\Resources\AcsCredential\VisionlineMetadata { + enum CardFunctionType: string + { + case GUEST = "guest"; + case STAFF = "staff"; + } +} + +namespace Seam\Resources\AcsCredential\Warnings { + /** + * Indicates that the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is waiting to be issued. + */ + final class WaitingToBeIssued extends \Seam\Resources\AcsCredential\Warnings + { + public static function from_json(mixed $json): WaitingToBeIssued|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsCredential\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self(member_pin_id: $json->member_pin_id ?? null); } - public function __construct( - /** - * ID of the Akiles member PIN. - */ - public string|null $member_pin_id, - ) {} -} + /** + * Indicates that the schedule of one of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials)'s children was modified externally. + */ + final class ScheduleExternallyModified extends + \Seam\Resources\AcsCredential\Warnings + { + public static function from_json( + mixed $json, + ): ScheduleExternallyModified|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class AcsCredentialAssaAbloyVostioMetadata -{ - public static function from_json( - mixed $json, - ): AcsCredentialAssaAbloyVostioMetadata|null { - if (!$json) { - return null; + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsCredential\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - auto_join: $json->auto_join ?? null, - door_names: $json->door_names ?? null, - endpoint_id: $json->endpoint_id ?? null, - key_id: $json->key_id ?? null, - key_issuing_request_id: $json->key_issuing_request_id ?? null, - override_guest_acs_entrance_ids: $json->override_guest_acs_entrance_ids ?? - null, - ); } - public function __construct( - /** - * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. - */ - public bool|null $auto_join, - /** - * Names of the doors to which to grant access in the Vostio access system. - */ - public array|null $door_names, - /** - * Endpoint ID in the Vostio access system. - */ - public string|null $endpoint_id, - /** - * Key ID in the Vostio access system. - */ - public string|null $key_id, - /** - * Key issuing request ID in the Vostio access system. - */ - public string|null $key_issuing_request_id, - /** - * IDs of the guest entrances to override in the Vostio access system. - */ - public array|null $override_guest_acs_entrance_ids, - ) {} -} + /** + * Indicates that the schedule of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was modified to avoid creating a credential with a start date in the past. + */ + final class ScheduleModified extends \Seam\Resources\AcsCredential\Warnings + { + public static function from_json(mixed $json): ScheduleModified|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class AcsCredentialErrors -{ - public static function from_json(mixed $json): AcsCredentialErrors|null + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsCredential\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is being deleted. + */ + final class BeingDeleted extends \Seam\Resources\AcsCredential\Warnings { - if (!$json) { - return null; + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsCredential\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - public string|null $error_code, - public string|null $message, - ) {} -} + /** + * An unknown issue occurred while syncing the state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) with the provider. This issue may affect the proper functioning of the credential. + */ + final class UnknownIssueWithAcsCredential extends + \Seam\Resources\AcsCredential\Warnings + { + public static function from_json( + mixed $json, + ): UnknownIssueWithAcsCredential|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class AcsCredentialVisionlineMetadata -{ - public static function from_json( - mixed $json, - ): AcsCredentialVisionlineMetadata|null { - if (!$json) { - return null; + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsCredential\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - auto_join: $json->auto_join ?? null, - card_function_type: $json->card_function_type ?? null, - card_id: $json->card_id ?? null, - common_acs_entrance_ids: $json->common_acs_entrance_ids ?? null, - credential_id: $json->credential_id ?? null, - guest_acs_entrance_ids: $json->guest_acs_entrance_ids ?? null, - is_valid: $json->is_valid ?? null, - joiner_acs_credential_ids: $json->joiner_acs_credential_ids ?? null, - ); } - public function __construct( - /** - * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. - */ - public bool|null $auto_join, - /** - * Card function type in the Visionline access system. - */ - public string|null $card_function_type, - /** - * ID of the card in the Visionline access system. - */ - public string|null $card_id, - /** - * Common entrance IDs in the Visionline access system. - */ - public array|null $common_acs_entrance_ids, - /** - * ID of the credential in the Visionline access system. - */ - public string|null $credential_id, - /** - * Guest entrance IDs in the Visionline access system. - */ - public array|null $guest_acs_entrance_ids, - /** - * Indicates whether the credential is valid. - */ - public bool|null $is_valid, - /** - * IDs of the credentials to which you want to join. - */ - public array|null $joiner_acs_credential_ids, - ) {} -} + /** + * Access permissions for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) have changed. [Reissue](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners/creating-and-encoding-card-based-credentials) (re-encode) the credential. This issue may affect the proper functioning of the credential. + */ + final class NeedsToBeReissued extends \Seam\Resources\AcsCredential\Warnings + { + public static function from_json(mixed $json): NeedsToBeReissued|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsCredential\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class AcsCredentialWarnings -{ - public static function from_json(mixed $json): AcsCredentialWarnings|null + /** + * Indicates that the requested PIN code could not be used, so the access system assigned a different code. Give the guest the assigned code. + */ + final class RequestedCodeUnavailable extends + \Seam\Resources\AcsCredential\Warnings { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): RequestedCodeUnavailable|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + new_code: $json->new_code ?? null, + original_code: $json->original_code ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * The PIN code that was assigned instead. + */ + public string|null $new_code, + /** + * The originally requested PIN code that could not be used. + */ + public string|null $original_code, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsCredential\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + enum WarningCode: string + { + case WAITING_TO_BE_ISSUED = "waiting_to_be_issued"; + case SCHEDULE_EXTERNALLY_MODIFIED = "schedule_externally_modified"; + case SCHEDULE_MODIFIED = "schedule_modified"; + case BEING_DELETED = "being_deleted"; + case UNKNOWN_ISSUE_WITH_ACS_CREDENTIAL = "unknown_issue_with_acs_credential"; + case NEEDS_TO_BE_REISSUED = "needs_to_be_reissued"; + case REQUESTED_CODE_UNAVAILABLE = "requested_code_unavailable"; + } } diff --git a/src/Resources/AcsEncoder.php b/src/Resources/AcsEncoder.php index 2375f65d..16fe4738 100644 --- a/src/Resources/AcsEncoder.php +++ b/src/Resources/AcsEncoder.php @@ -1,105 +1,118 @@ acs_encoder_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AcsEncoder\Errors::from_json($e), + $json->errors ?? [], + ), + workspace_id: $json->workspace_id ?? null, + ); } - return new self( - acs_encoder_id: $json->acs_encoder_id ?? null, - acs_system_id: $json->acs_system_id ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - errors: array_map( - fn($e) => AcsEncoderErrors::from_json($e), - $json->errors ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * ID of the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). - */ - public string|null $acs_encoder_id, - /** - * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). - */ - public string|null $acs_system_id, - /** - * ID of the connected account that contains the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). - */ - public string|null $connected_account_id, - /** - * Date and time at which the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners) was created. - */ - public string|null $created_at, - /** - * Display name for the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). - */ - public string|null $display_name, - /** - * Errors associated with the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). - */ - public array $errors, - /** - * ID of the workspace that contains the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * ID of the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). + */ + public string|null $acs_encoder_id, + /** + * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). + */ + public string|null $acs_system_id, + /** + * ID of the connected account that contains the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). + */ + public string|null $connected_account_id, + /** + * Date and time at which the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners) was created. + */ + public string|null $created_at, + /** + * Display name for the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). + */ + public string|null $display_name, + /** + * Errors associated with the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). + * + * @var list<\Seam\Resources\AcsEncoder\Errors> + */ + public array $errors, + /** + * ID of the workspace that contains the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). + */ + public string|null $workspace_id, + ) {} + } } -/** - * Errors associated with the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). - */ -class AcsEncoderErrors -{ - public static function from_json(mixed $json): AcsEncoderErrors|null +namespace Seam\Resources\AcsEncoder { + /** + * Errors associated with the [encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). + */ + class Errors { - if (!$json) { - return null; + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsEncoder\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} } +} - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} +namespace Seam\Resources\AcsEncoder\Errors { + enum ErrorCode: string + { + case ACS_ENCODER_REMOVED = "acs_encoder_removed"; + } } diff --git a/src/Resources/AcsEntrance.php b/src/Resources/AcsEntrance.php index 870e7b6a..d12132df 100644 --- a/src/Resources/AcsEntrance.php +++ b/src/Resources/AcsEntrance.php @@ -1,751 +1,1071 @@ acs_entrance_id ?? null, - acs_system_id: $json->acs_system_id ?? null, - akiles_metadata: isset($json->akiles_metadata) - ? AcsEntranceAkilesMetadata::from_json($json->akiles_metadata) - : null, - assa_abloy_vostio_metadata: isset($json->assa_abloy_vostio_metadata) - ? AcsEntranceAssaAbloyVostioMetadata::from_json( + public static function from_json(mixed $json): AcsEntrance|null + { + if (!$json) { + return null; + } + return new self( + acs_entrance_id: $json->acs_entrance_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AcsEntrance\Errors::from_json($e), + $json->errors ?? [], + ), + space_ids: $json->space_ids ?? null, + warnings: array_map( + fn($w) => \Seam\Resources\AcsEntrance\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + akiles_metadata: isset($json->akiles_metadata) + ? \Seam\Resources\AcsEntrance\AkilesMetadata::from_json( + $json->akiles_metadata, + ) + : null, + assa_abloy_vostio_metadata: isset( $json->assa_abloy_vostio_metadata, ) - : null, - avigilon_alta_metadata: isset($json->avigilon_alta_metadata) - ? AcsEntranceAvigilonAltaMetadata::from_json( - $json->avigilon_alta_metadata, - ) - : null, - brivo_metadata: isset($json->brivo_metadata) - ? AcsEntranceBrivoMetadata::from_json($json->brivo_metadata) - : null, - can_belong_to_reservation: $json->can_belong_to_reservation ?? null, - can_unlock_with_card: $json->can_unlock_with_card ?? null, - can_unlock_with_cloud_key: $json->can_unlock_with_cloud_key ?? null, - can_unlock_with_code: $json->can_unlock_with_code ?? null, - can_unlock_with_mobile_key: $json->can_unlock_with_mobile_key ?? - null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - dormakaba_ambiance_metadata: isset( - $json->dormakaba_ambiance_metadata, - ) - ? AcsEntranceDormakabaAmbianceMetadata::from_json( + ? \Seam\Resources\AcsEntrance\AssaAbloyVostioMetadata::from_json( + $json->assa_abloy_vostio_metadata, + ) + : null, + avigilon_alta_metadata: isset($json->avigilon_alta_metadata) + ? \Seam\Resources\AcsEntrance\AvigilonAltaMetadata::from_json( + $json->avigilon_alta_metadata, + ) + : null, + brivo_metadata: isset($json->brivo_metadata) + ? \Seam\Resources\AcsEntrance\BrivoMetadata::from_json( + $json->brivo_metadata, + ) + : null, + can_belong_to_reservation: $json->can_belong_to_reservation ?? + null, + can_unlock_with_card: $json->can_unlock_with_card ?? null, + can_unlock_with_cloud_key: $json->can_unlock_with_cloud_key ?? + null, + can_unlock_with_code: $json->can_unlock_with_code ?? null, + can_unlock_with_mobile_key: $json->can_unlock_with_mobile_key ?? + null, + dormakaba_ambiance_metadata: isset( $json->dormakaba_ambiance_metadata, ) - : null, - dormakaba_community_metadata: isset( - $json->dormakaba_community_metadata, - ) - ? AcsEntranceDormakabaCommunityMetadata::from_json( + ? \Seam\Resources\AcsEntrance\DormakabaAmbianceMetadata::from_json( + $json->dormakaba_ambiance_metadata, + ) + : null, + dormakaba_community_metadata: isset( $json->dormakaba_community_metadata, ) - : null, - errors: array_map( - fn($e) => AcsEntranceErrors::from_json($e), - $json->errors ?? [], - ), - hotek_metadata: isset($json->hotek_metadata) - ? AcsEntranceHotekMetadata::from_json($json->hotek_metadata) - : null, - is_locked: $json->is_locked ?? null, - latch_metadata: isset($json->latch_metadata) - ? AcsEntranceLatchMetadata::from_json($json->latch_metadata) - : null, - salto_ks_metadata: isset($json->salto_ks_metadata) - ? AcsEntranceSaltoKsMetadata::from_json( - $json->salto_ks_metadata, - ) - : null, - salto_space_metadata: isset($json->salto_space_metadata) - ? AcsEntranceSaltoSpaceMetadata::from_json( - $json->salto_space_metadata, - ) - : null, - space_ids: $json->space_ids ?? null, - visionline_metadata: isset($json->visionline_metadata) - ? AcsEntranceVisionlineMetadata::from_json( - $json->visionline_metadata, - ) - : null, - warnings: array_map( - fn($w) => AcsEntranceWarnings::from_json($w), - $json->warnings ?? [], - ), - ); - } + ? \Seam\Resources\AcsEntrance\DormakabaCommunityMetadata::from_json( + $json->dormakaba_community_metadata, + ) + : null, + hotek_metadata: isset($json->hotek_metadata) + ? \Seam\Resources\AcsEntrance\HotekMetadata::from_json( + $json->hotek_metadata, + ) + : null, + is_locked: $json->is_locked ?? null, + latch_metadata: isset($json->latch_metadata) + ? \Seam\Resources\AcsEntrance\LatchMetadata::from_json( + $json->latch_metadata, + ) + : null, + salto_ks_metadata: isset($json->salto_ks_metadata) + ? \Seam\Resources\AcsEntrance\SaltoKsMetadata::from_json( + $json->salto_ks_metadata, + ) + : null, + salto_space_metadata: isset($json->salto_space_metadata) + ? \Seam\Resources\AcsEntrance\SaltoSpaceMetadata::from_json( + $json->salto_space_metadata, + ) + : null, + visionline_metadata: isset($json->visionline_metadata) + ? \Seam\Resources\AcsEntrance\VisionlineMetadata::from_json( + $json->visionline_metadata, + ) + : null, + ); + } - public function __construct( - /** - * ID of the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public string|null $acs_entrance_id, - /** - * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public string|null $acs_system_id, - /** - * Akiles-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceAkilesMetadata|null $akiles_metadata, - /** - * ASSA ABLOY Vostio-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceAssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata, - /** - * Avigilon Alta-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceAvigilonAltaMetadata|null $avigilon_alta_metadata, - /** - * Brivo-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceBrivoMetadata|null $brivo_metadata, - /** - * Indicates whether the ACS entrance can belong to a reservation via an access_grant.reservation_key. - */ - public bool|null $can_belong_to_reservation, - /** - * Indicates whether the ACS entrance can be unlocked with card credentials. - */ - public bool|null $can_unlock_with_card, - /** - * Indicates whether the ACS entrance can be unlocked with cloud key credentials. - */ - public bool|null $can_unlock_with_cloud_key, - /** - * Indicates whether the ACS entrance can be unlocked with pin codes. - */ - public bool|null $can_unlock_with_code, - /** - * Indicates whether the ACS entrance can be unlocked with mobile key credentials. - */ - public bool|null $can_unlock_with_mobile_key, - /** - * ID of the [connected account](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public string|null $connected_account_id, - /** - * Date and time at which the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) was created. - */ - public string|null $created_at, - /** - * Display name for the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public string|null $display_name, - /** - * dormakaba Ambiance-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceDormakabaAmbianceMetadata|null $dormakaba_ambiance_metadata, - /** - * dormakaba Community-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceDormakabaCommunityMetadata|null $dormakaba_community_metadata, - /** - * Errors associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public array $errors, - /** - * Hotek-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceHotekMetadata|null $hotek_metadata, - /** - * Indicates whether the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) is currently locked. - */ - public bool|null $is_locked, - /** - * Latch-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceLatchMetadata|null $latch_metadata, - /** - * Salto KS-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceSaltoKsMetadata|null $salto_ks_metadata, - /** - * Salto Space-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceSaltoSpaceMetadata|null $salto_space_metadata, - /** - * IDs of the spaces that the entrance is in. - */ - public array|null $space_ids, - /** - * Visionline-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public AcsEntranceVisionlineMetadata|null $visionline_metadata, - /** - * Warnings associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public array $warnings, - ) {} + public function __construct( + /** + * ID of the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public string|null $acs_entrance_id, + /** + * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public string|null $acs_system_id, + /** + * ID of the [connected account](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public string|null $connected_account_id, + /** + * Date and time at which the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) was created. + */ + public string|null $created_at, + /** + * Display name for the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public string|null $display_name, + /** + * Errors associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + * + * @var list<\Seam\Resources\AcsEntrance\Errors> + */ + public array $errors, + /** + * IDs of the spaces that the entrance is in. + * + * @var list|null + */ + public array|null $space_ids, + /** + * Warnings associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + * + * @var list<\Seam\Resources\AcsEntrance\Warnings> + */ + public array $warnings, + /** + * Akiles-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\AkilesMetadata|null $akiles_metadata = null, + /** + * ASSA ABLOY Vostio-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\AssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata = null, + /** + * Avigilon Alta-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\AvigilonAltaMetadata|null $avigilon_alta_metadata = null, + /** + * Brivo-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\BrivoMetadata|null $brivo_metadata = null, + /** + * Indicates whether the ACS entrance can belong to a reservation via an access_grant.reservation_key. + */ + public bool|null $can_belong_to_reservation = null, + /** + * Indicates whether the ACS entrance can be unlocked with card credentials. + */ + public bool|null $can_unlock_with_card = null, + /** + * Indicates whether the ACS entrance can be unlocked with cloud key credentials. + */ + public bool|null $can_unlock_with_cloud_key = null, + /** + * Indicates whether the ACS entrance can be unlocked with pin codes. + */ + public bool|null $can_unlock_with_code = null, + /** + * Indicates whether the ACS entrance can be unlocked with mobile key credentials. + */ + public bool|null $can_unlock_with_mobile_key = null, + /** + * dormakaba Ambiance-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\DormakabaAmbianceMetadata|null $dormakaba_ambiance_metadata = null, + /** + * dormakaba Community-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\DormakabaCommunityMetadata|null $dormakaba_community_metadata = null, + /** + * Hotek-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\HotekMetadata|null $hotek_metadata = null, + /** + * Indicates whether the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) is currently locked. + */ + public bool|null $is_locked = null, + /** + * Latch-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\LatchMetadata|null $latch_metadata = null, + /** + * Salto KS-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\SaltoKsMetadata|null $salto_ks_metadata = null, + /** + * Salto Space-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\SaltoSpaceMetadata|null $salto_space_metadata = null, + /** + * Visionline-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public \Seam\Resources\AcsEntrance\VisionlineMetadata|null $visionline_metadata = null, + ) {} + } } -/** - * Actions the gadget exposes (for example, open). - */ -class AcsEntranceActions -{ - public static function from_json(mixed $json): AcsEntranceActions|null +namespace Seam\Resources\AcsEntrance { + /** + * Akiles-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class AkilesMetadata { - if (!$json) { - return null; + public static function from_json(mixed $json): AkilesMetadata|null + { + if (!$json) { + return null; + } + return new self( + actions: array_map( + fn( + $a, + ) => \Seam\Resources\AcsEntrance\AkilesMetadata\Actions::from_json( + $a, + ), + $json->actions ?? [], + ), + gadget_id: $json->gadget_id ?? null, + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + ); } - return new self(id: $json->id ?? null, name: $json->name ?? null); - } - public function __construct( - /** - * ID of the gadget action. - */ - public string|null $id, - /** - * Name of the gadget action. - */ - public string|null $name, - ) {} -} + public function __construct( + /** + * Actions the gadget exposes (for example, open). + * + * @var list<\Seam\Resources\AcsEntrance\AkilesMetadata\Actions>|null + */ + public array|null $actions = null, + /** + * ID of the Akiles gadget. + */ + public string|null $gadget_id = null, + /** + * ID of the Akiles site the gadget belongs to. + */ + public string|null $site_id = null, + /** + * Name of the Akiles site the gadget belongs to. + */ + public string|null $site_name = null, + ) {} + } -/** - * Akiles-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceAkilesMetadata -{ - public static function from_json( - mixed $json, - ): AcsEntranceAkilesMetadata|null { - if (!$json) { - return null; + /** + * ASSA ABLOY Vostio-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class AssaAbloyVostioMetadata + { + public static function from_json( + mixed $json, + ): AssaAbloyVostioMetadata|null { + if (!$json) { + return null; + } + return new self( + door_name: $json->door_name ?? null, + door_number: $json->door_number ?? null, + door_type: $json->door_type ?? null, + pms_id: $json->pms_id ?? null, + stand_open: $json->stand_open ?? null, + ); } - return new self( - actions: array_map( - fn($a) => AcsEntranceActions::from_json($a), - $json->actions ?? [], - ), - gadget_id: $json->gadget_id ?? null, - site_id: $json->site_id ?? null, - site_name: $json->site_name ?? null, - ); + + public function __construct( + /** + * Name of the door in the Vostio access system. + */ + public string|null $door_name = null, + /** + * Number of the door in the Vostio access system. + */ + public float|null $door_number = null, + /** + * Type of the door in the Vostio access system. + * + * @var value-of<\Seam\Resources\AcsEntrance\AssaAbloyVostioMetadata\DoorType>|string|null + */ + public string|null $door_type = null, + /** + * PMS ID of the door in the Vostio access system. + */ + public string|null $pms_id = null, + /** + * Indicates whether keys are allowed to set the door in stand open mode in the Vostio access system. + */ + public bool|null $stand_open = null, + ) {} } - public function __construct( - /** - * Actions the gadget exposes (for example, open). - */ - public array $actions, - /** - * ID of the Akiles gadget. - */ - public string|null $gadget_id, - /** - * ID of the Akiles site the gadget belongs to. - */ - public string|null $site_id, - /** - * Name of the Akiles site the gadget belongs to. - */ - public string|null $site_name, - ) {} -} + /** + * Avigilon Alta-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class AvigilonAltaMetadata + { + public static function from_json(mixed $json): AvigilonAltaMetadata|null + { + if (!$json) { + return null; + } + return new self( + entry_name: $json->entry_name ?? null, + entry_relays_total_count: $json->entry_relays_total_count ?? + null, + org_name: $json->org_name ?? null, + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + zone_id: $json->zone_id ?? null, + zone_name: $json->zone_name ?? null, + ); + } + + public function __construct( + /** + * Entry name for an Avigilon Alta system. + */ + public string|null $entry_name = null, + /** + * Total count of entry relays for an Avigilon Alta system. + */ + public float|null $entry_relays_total_count = null, + /** + * Organization name for an Avigilon Alta system. + */ + public string|null $org_name = null, + /** + * Site ID for an Avigilon Alta system. + */ + public float|null $site_id = null, + /** + * Site name for an Avigilon Alta system. + */ + public string|null $site_name = null, + /** + * Zone ID for an Avigilon Alta system. + */ + public float|null $zone_id = null, + /** + * Zone name for an Avigilon Alta system. + */ + public string|null $zone_name = null, + ) {} + } -/** - * ASSA ABLOY Vostio-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceAssaAbloyVostioMetadata -{ - public static function from_json( - mixed $json, - ): AcsEntranceAssaAbloyVostioMetadata|null { - if (!$json) { - return null; + /** + * Brivo-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class BrivoMetadata + { + public static function from_json(mixed $json): BrivoMetadata|null + { + if (!$json) { + return null; + } + return new self( + access_point_id: $json->access_point_id ?? null, + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + ); } - return new self( - door_name: $json->door_name ?? null, - door_number: $json->door_number ?? null, - door_type: $json->door_type ?? null, - pms_id: $json->pms_id ?? null, - stand_open: $json->stand_open ?? null, - ); + + public function __construct( + /** + * ID of the access point in the Brivo access system. + */ + public string|null $access_point_id = null, + /** + * ID of the site that the access point belongs to. + */ + public float|null $site_id = null, + /** + * Name of the site that the access point belongs to. + */ + public string|null $site_name = null, + ) {} } - public function __construct( - /** - * Name of the door in the Vostio access system. - */ - public string|null $door_name, - /** - * Number of the door in the Vostio access system. - */ - public float|null $door_number, - /** - * Type of the door in the Vostio access system. - */ - public string|null $door_type, - /** - * PMS ID of the door in the Vostio access system. - */ - public string|null $pms_id, - /** - * Indicates whether keys are allowed to set the door in stand open mode in the Vostio access system. - */ - public bool|null $stand_open, - ) {} -} + /** + * dormakaba Ambiance-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class DormakabaAmbianceMetadata + { + public static function from_json( + mixed $json, + ): DormakabaAmbianceMetadata|null { + if (!$json) { + return null; + } + return new self( + access_point_name: $json->access_point_name ?? null, + ); + } + + public function __construct( + /** + * Name of the access point in the dormakaba Ambiance access system. + */ + public string|null $access_point_name = null, + ) {} + } -/** - * Avigilon Alta-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceAvigilonAltaMetadata -{ - public static function from_json( - mixed $json, - ): AcsEntranceAvigilonAltaMetadata|null { - if (!$json) { - return null; + /** + * dormakaba Community-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class DormakabaCommunityMetadata + { + public static function from_json( + mixed $json, + ): DormakabaCommunityMetadata|null { + if (!$json) { + return null; + } + return new self( + access_point_profile: $json->access_point_profile ?? null, + ); } - return new self( - entry_name: $json->entry_name ?? null, - entry_relays_total_count: $json->entry_relays_total_count ?? null, - org_name: $json->org_name ?? null, - site_id: $json->site_id ?? null, - site_name: $json->site_name ?? null, - zone_id: $json->zone_id ?? null, - zone_name: $json->zone_name ?? null, - ); + + public function __construct( + /** + * Type of access point profile in the dormakaba Community access system. + */ + public string|null $access_point_profile = null, + ) {} } - public function __construct( - /** - * Entry name for an Avigilon Alta system. - */ - public string|null $entry_name, - /** - * Total count of entry relays for an Avigilon Alta system. - */ - public float|null $entry_relays_total_count, - /** - * Organization name for an Avigilon Alta system. - */ - public string|null $org_name, - /** - * Site ID for an Avigilon Alta system. - */ - public float|null $site_id, - /** - * Site name for an Avigilon Alta system. - */ - public string|null $site_name, - /** - * Zone ID for an Avigilon Alta system. - */ - public float|null $zone_id, - /** - * Zone name for an Avigilon Alta system. - */ - public string|null $zone_name, - ) {} -} + /** + * Errors associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } -/** - * Brivo-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceBrivoMetadata -{ - public static function from_json(mixed $json): AcsEntranceBrivoMetadata|null + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Hotek-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class HotekMetadata { - if (!$json) { - return null; + public static function from_json(mixed $json): HotekMetadata|null + { + if (!$json) { + return null; + } + return new self( + common_area_name: $json->common_area_name ?? null, + common_area_number: $json->common_area_number ?? null, + room_number: $json->room_number ?? null, + ); } - return new self( - access_point_id: $json->access_point_id ?? null, - site_id: $json->site_id ?? null, - site_name: $json->site_name ?? null, - ); + + public function __construct( + /** + * Display name of the entrance. + */ + public string|null $common_area_name = null, + /** + * Display name of the entrance. + */ + public string|null $common_area_number = null, + /** + * Room number of the entrance. + */ + public string|null $room_number = null, + ) {} } - public function __construct( - /** - * ID of the access point in the Brivo access system. - */ - public string|null $access_point_id, - /** - * ID of the site that the access point belongs to. - */ - public float|null $site_id, - /** - * Name of the site that the access point belongs to. - */ - public string|null $site_name, - ) {} -} + /** + * Latch-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class LatchMetadata + { + public static function from_json(mixed $json): LatchMetadata|null + { + if (!$json) { + return null; + } + return new self( + accessibility_type: $json->accessibility_type ?? null, + door_name: $json->door_name ?? null, + door_type: $json->door_type ?? null, + is_connected: $json->is_connected ?? null, + ); + } + + public function __construct( + /** + * Accessibility type in the Latch access system. + */ + public string|null $accessibility_type = null, + /** + * Name of the door in the Latch access system. + */ + public string|null $door_name = null, + /** + * Type of the door in the Latch access system. + */ + public string|null $door_type = null, + /** + * Indicates whether the entrance is connected. + */ + public bool|null $is_connected = null, + ) {} + } -/** - * dormakaba Ambiance-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceDormakabaAmbianceMetadata -{ - public static function from_json( - mixed $json, - ): AcsEntranceDormakabaAmbianceMetadata|null { - if (!$json) { - return null; + /** + * Salto KS-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class SaltoKsMetadata + { + public static function from_json(mixed $json): SaltoKsMetadata|null + { + if (!$json) { + return null; + } + return new self( + battery_level: $json->battery_level ?? null, + door_name: $json->door_name ?? null, + intrusion_alarm: $json->intrusion_alarm ?? null, + left_open_alarm: $json->left_open_alarm ?? null, + lock_type: $json->lock_type ?? null, + locked_state: $json->locked_state ?? null, + online: $json->online ?? null, + privacy_mode: $json->privacy_mode ?? null, + ); } - return new self(access_point_name: $json->access_point_name ?? null); + + public function __construct( + /** + * Battery level of the door access device. + */ + public string|null $battery_level = null, + /** + * Name of the door in the Salto KS access system. + */ + public string|null $door_name = null, + /** + * Indicates whether an intrusion alarm is active on the door. + */ + public bool|null $intrusion_alarm = null, + /** + * Indicates whether the door is left open. + */ + public bool|null $left_open_alarm = null, + /** + * Type of the lock in the Salto KS access system. + */ + public string|null $lock_type = null, + /** + * Locked state of the door in the Salto KS access system. + */ + public string|null $locked_state = null, + /** + * Indicates whether the door access device is online. + */ + public bool|null $online = null, + /** + * Indicates whether privacy mode is enabled for the lock. + */ + public bool|null $privacy_mode = null, + ) {} } - public function __construct( - /** - * Name of the access point in the dormakaba Ambiance access system. - */ - public string|null $access_point_name, - ) {} -} + /** + * Salto Space-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class SaltoSpaceMetadata + { + public static function from_json(mixed $json): SaltoSpaceMetadata|null + { + if (!$json) { + return null; + } + return new self( + audit_on_keys: $json->audit_on_keys ?? null, + door_description: $json->door_description ?? null, + door_id: $json->door_id ?? null, + door_name: $json->door_name ?? null, + room_description: $json->room_description ?? null, + room_name: $json->room_name ?? null, + ); + } + + public function __construct( + /** + * Indicates whether AuditOnKeys is enabled for the door in the Salto Space access system. + */ + public bool|null $audit_on_keys = null, + /** + * Description of the door in the Salto Space access system. + */ + public string|null $door_description = null, + /** + * Door ID in the Salto Space access system. + */ + public string|null $door_id = null, + /** + * Name of the door in the Salto Space access system. + */ + public string|null $door_name = null, + /** + * Description of the room in the Salto Space access system. + */ + public string|null $room_description = null, + /** + * Name of the room in the Salto Space access system. + */ + public string|null $room_name = null, + ) {} + } -/** - * dormakaba Community-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceDormakabaCommunityMetadata -{ - public static function from_json( - mixed $json, - ): AcsEntranceDormakabaCommunityMetadata|null { - if (!$json) { - return null; + /** + * Visionline-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + class VisionlineMetadata + { + public static function from_json(mixed $json): VisionlineMetadata|null + { + if (!$json) { + return null; + } + return new self( + door_category: $json->door_category ?? null, + door_name: $json->door_name ?? null, + profiles: array_map( + fn( + $p, + ) => \Seam\Resources\AcsEntrance\VisionlineMetadata\Profiles::from_json( + $p, + ), + $json->profiles ?? [], + ), + ); } - return new self( - access_point_profile: $json->access_point_profile ?? null, - ); + + public function __construct( + /** + * Category of the door in the Visionline access system. + * + * @var value-of<\Seam\Resources\AcsEntrance\VisionlineMetadata\DoorCategory>|string|null + */ + public string|null $door_category = null, + /** + * Name of the door in the Visionline access system. + */ + public string|null $door_name = null, + /** + * Profile for the door in the Visionline access system. + * + * @var list<\Seam\Resources\AcsEntrance\VisionlineMetadata\Profiles>|null + */ + public array|null $profiles = null, + ) {} } - public function __construct( - /** - * Type of access point profile in the dormakaba Community access system. - */ - public string|null $access_point_profile, - ) {} + /** + * Warnings associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\AcsEntrance\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AcsEntrance\Warnings\WarningCode::SALTO_KS_ENTRANCE_ACCESS_CODE_SUPPORT_REMOVED + => \Seam\Resources\AcsEntrance\Warnings\SaltoKsEntranceAccessCodeSupportRemoved::from_json( + $json, + ), + \Seam\Resources\AcsEntrance\Warnings\WarningCode::ENTRANCE_SHARES_ZONE + => \Seam\Resources\AcsEntrance\Warnings\EntranceSharesZone::from_json( + $json, + ), + \Seam\Resources\AcsEntrance\Warnings\WarningCode::ENTRANCE_SETUP_REQUIRED + => \Seam\Resources\AcsEntrance\Warnings\EntranceSetupRequired::from_json( + $json, + ), + \Seam\Resources\AcsEntrance\Warnings\WarningCode::SALTO_KS_PRIVACY_MODE + => \Seam\Resources\AcsEntrance\Warnings\SaltoKsPrivacyMode::from_json( + $json, + ), + \Seam\Resources\AcsEntrance\Warnings\WarningCode::PRIVACY_MODE + => \Seam\Resources\AcsEntrance\Warnings\PrivacyMode::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsEntrance\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } } -/** - * Errors associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceErrors -{ - public static function from_json(mixed $json): AcsEntranceErrors|null +namespace Seam\Resources\AcsEntrance\AkilesMetadata { + /** + * Actions the gadget exposes (for example, open). + */ + class Actions { - if (!$json) { - return null; + public static function from_json(mixed $json): Actions|null + { + if (!$json) { + return null; + } + return new self(id: $json->id ?? null, name: $json->name ?? null); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); + + public function __construct( + /** + * ID of the gadget action. + */ + public string|null $id = null, + /** + * Name of the gadget action. + */ + public string|null $name = null, + ) {} } +} - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} +namespace Seam\Resources\AcsEntrance\AssaAbloyVostioMetadata { + enum DoorType: string + { + case COMMON_DOOR = "CommonDoor"; + case ENTRANCE_DOOR = "EntranceDoor"; + case GUEST_DOOR = "GuestDoor"; + case ELEVATOR = "Elevator"; + } } -/** - * Hotek-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceHotekMetadata -{ - public static function from_json(mixed $json): AcsEntranceHotekMetadata|null +namespace Seam\Resources\AcsEntrance\VisionlineMetadata { + /** + * Profile for the door in the Visionline access system. + */ + class Profiles { - if (!$json) { - return null; + public static function from_json(mixed $json): Profiles|null + { + if (!$json) { + return null; + } + return new self( + visionline_door_profile_id: $json->visionline_door_profile_id ?? + null, + visionline_door_profile_type: $json->visionline_door_profile_type ?? + null, + ); } - return new self( - common_area_name: $json->common_area_name ?? null, - common_area_number: $json->common_area_number ?? null, - room_number: $json->room_number ?? null, - ); + + public function __construct( + /** + * Door profile ID in the Visionline access system. + */ + public string|null $visionline_door_profile_id = null, + /** + * Door profile type in the Visionline access system. + * + * @var value-of<\Seam\Resources\AcsEntrance\VisionlineMetadata\Profiles\VisionlineDoorProfileType>|string|null + */ + public string|null $visionline_door_profile_type = null, + ) {} } - public function __construct( - /** - * Display name of the entrance. - */ - public string|null $common_area_name, - /** - * Display name of the entrance. - */ - public string|null $common_area_number, - /** - * Room number of the entrance. - */ - public string|null $room_number, - ) {} + enum DoorCategory: string + { + case ENTRANCE = "entrance"; + case GUEST = "guest"; + case ELEVATOR_READER = "elevator reader"; + case COMMON = "common"; + case COMMON_PMS = "common (PMS)"; + } } -/** - * Latch-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceLatchMetadata -{ - public static function from_json(mixed $json): AcsEntranceLatchMetadata|null +namespace Seam\Resources\AcsEntrance\VisionlineMetadata\Profiles { + enum VisionlineDoorProfileType: string { - if (!$json) { - return null; - } - return new self( - accessibility_type: $json->accessibility_type ?? null, - door_name: $json->door_name ?? null, - door_type: $json->door_type ?? null, - is_connected: $json->is_connected ?? null, - ); + case BLE = "BLE"; + case COMMON_DOOR = "commonDoor"; + case TOUCH = "touch"; } - - public function __construct( - /** - * Accessibility type in the Latch access system. - */ - public string|null $accessibility_type, - /** - * Name of the door in the Latch access system. - */ - public string|null $door_name, - /** - * Type of the door in the Latch access system. - */ - public string|null $door_type, - /** - * Indicates whether the entrance is connected. - */ - public bool|null $is_connected, - ) {} } -/** - * Profile for the door in the Visionline access system. - */ -class AcsEntranceProfiles -{ - public static function from_json(mixed $json): AcsEntranceProfiles|null +namespace Seam\Resources\AcsEntrance\Warnings { + /** + * Indicates that a change in the reported device model has been detected for this Salto KS entrance, which may occur after an IQ hub reset. Access code support may be affected. See https://help.getseam.com/articles/5098842588-salto-ks-lock-loses-access-code-support for troubleshooting steps. + */ + final class SaltoKsEntranceAccessCodeSupportRemoved extends + \Seam\Resources\AcsEntrance\Warnings { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): SaltoKsEntranceAccessCodeSupportRemoved|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsEntrance\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - visionline_door_profile_id: $json->visionline_door_profile_id ?? - null, - visionline_door_profile_type: $json->visionline_door_profile_type ?? - null, - ); } - public function __construct( - /** - * Door profile ID in the Visionline access system. - */ - public string|null $visionline_door_profile_id, - /** - * Door profile type in the Visionline access system. - */ - public string|null $visionline_door_profile_type, - ) {} -} + /** + * Indicates that this entrance shares a zone with other entrances in Avigilon Alta and cannot be added to an access group individually. + */ + final class EntranceSharesZone extends \Seam\Resources\AcsEntrance\Warnings + { + public static function from_json(mixed $json): EntranceSharesZone|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Salto KS-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceSaltoKsMetadata -{ - public static function from_json( - mixed $json, - ): AcsEntranceSaltoKsMetadata|null { - if (!$json) { - return null; + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsEntrance\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - battery_level: $json->battery_level ?? null, - door_name: $json->door_name ?? null, - intrusion_alarm: $json->intrusion_alarm ?? null, - left_open_alarm: $json->left_open_alarm ?? null, - lock_type: $json->lock_type ?? null, - locked_state: $json->locked_state ?? null, - online: $json->online ?? null, - privacy_mode: $json->privacy_mode ?? null, - ); } - public function __construct( - /** - * Battery level of the door access device. - */ - public string|null $battery_level, - /** - * Name of the door in the Salto KS access system. - */ - public string|null $door_name, - /** - * Indicates whether an intrusion alarm is active on the door. - */ - public bool|null $intrusion_alarm, - /** - * Indicates whether the door is left open. - */ - public bool|null $left_open_alarm, - /** - * Type of the lock in the Salto KS access system. - */ - public string|null $lock_type, - /** - * Locked state of the door in the Salto KS access system. - */ - public string|null $locked_state, - /** - * Indicates whether the door access device is online. - */ - public bool|null $online, - /** - * Indicates whether privacy mode is enabled for the lock. - */ - public bool|null $privacy_mode, - ) {} -} + /** + * Indicates that this entrance requires additional configuration in the access control system before Seam can fully manage it. + */ + final class EntranceSetupRequired extends + \Seam\Resources\AcsEntrance\Warnings + { + public static function from_json( + mixed $json, + ): EntranceSetupRequired|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Salto Space-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceSaltoSpaceMetadata -{ - public static function from_json( - mixed $json, - ): AcsEntranceSaltoSpaceMetadata|null { - if (!$json) { - return null; + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsEntrance\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - audit_on_keys: $json->audit_on_keys ?? null, - door_description: $json->door_description ?? null, - door_id: $json->door_id ?? null, - door_name: $json->door_name ?? null, - room_description: $json->room_description ?? null, - room_name: $json->room_name ?? null, - ); } - public function __construct( - /** - * Indicates whether AuditOnKeys is enabled for the door in the Salto Space access system. - */ - public bool|null $audit_on_keys, - /** - * Description of the door in the Salto Space access system. - */ - public string|null $door_description, - /** - * Door ID in the Salto Space access system. - */ - public string|null $door_id, - /** - * Name of the door in the Salto Space access system. - */ - public string|null $door_name, - /** - * Description of the room in the Salto Space access system. - */ - public string|null $room_description, - /** - * Name of the room in the Salto Space access system. - */ - public string|null $room_name, - ) {} -} + /** + * Indicates that this entrance is in privacy mode. When privacy mode is enabled, access codes, mobile keys, and remote unlocks will not work unless the user has admin access. + */ + final class SaltoKsPrivacyMode extends \Seam\Resources\AcsEntrance\Warnings + { + public static function from_json(mixed $json): SaltoKsPrivacyMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Visionline-specific metadata associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceVisionlineMetadata -{ - public static function from_json( - mixed $json, - ): AcsEntranceVisionlineMetadata|null { - if (!$json) { - return null; + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsEntrance\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - door_category: $json->door_category ?? null, - door_name: $json->door_name ?? null, - profiles: array_map( - fn($p) => AcsEntranceProfiles::from_json($p), - $json->profiles ?? [], - ), - ); } - public function __construct( - /** - * Category of the door in the Visionline access system. - */ - public string|null $door_category, - /** - * Name of the door in the Visionline access system. - */ - public string|null $door_name, - /** - * Profile for the door in the Visionline access system. - */ - public array $profiles, - ) {} -} - -/** - * Warnings associated with the [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ -class AcsEntranceWarnings -{ - public static function from_json(mixed $json): AcsEntranceWarnings|null + /** + * Indicates that this entrance is in privacy mode. When privacy mode is enabled, access codes, mobile keys, and remote unlocks will not work unless the user has admin access. + */ + final class PrivacyMode extends \Seam\Resources\AcsEntrance\Warnings { - if (!$json) { - return null; + public static function from_json(mixed $json): PrivacyMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsEntrance\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + enum WarningCode: string + { + case SALTO_KS_ENTRANCE_ACCESS_CODE_SUPPORT_REMOVED = "salto_ks_entrance_access_code_support_removed"; + case ENTRANCE_SHARES_ZONE = "entrance_shares_zone"; + case ENTRANCE_SETUP_REQUIRED = "entrance_setup_required"; + case SALTO_KS_PRIVACY_MODE = "salto_ks_privacy_mode"; + case PRIVACY_MODE = "privacy_mode"; + } } diff --git a/src/Resources/AcsSystem.php b/src/Resources/AcsSystem.php index 76b4f601..88a89b3c 100644 --- a/src/Resources/AcsSystem.php +++ b/src/Resources/AcsSystem.php @@ -1,273 +1,980 @@ acs_system_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + connected_account_ids: $json->connected_account_ids ?? null, + created_at: $json->created_at ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AcsSystem\Errors::from_json($e), + $json->errors ?? [], + ), + image_alt_text: $json->image_alt_text ?? null, + image_url: $json->image_url ?? null, + is_credential_manager: $json->is_credential_manager ?? null, + location: isset($json->location) + ? \Seam\Resources\AcsSystem\Location::from_json( + $json->location, + ) + : null, + name: $json->name ?? null, + warnings: array_map( + fn($w) => \Seam\Resources\AcsSystem\Warnings::from_json($w), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + acs_access_group_count: $json->acs_access_group_count ?? null, + acs_user_count: $json->acs_user_count ?? null, + default_credential_manager_acs_system_id: $json->default_credential_manager_acs_system_id ?? + null, + external_type: $json->external_type ?? null, + external_type_display_name: $json->external_type_display_name ?? + null, + system_type: $json->system_type ?? null, + system_type_display_name: $json->system_type_display_name ?? + null, + visionline_metadata: isset($json->visionline_metadata) + ? \Seam\Resources\AcsSystem\VisionlineMetadata::from_json( + $json->visionline_metadata, + ) + : null, + ); } - return new self( - acs_access_group_count: $json->acs_access_group_count ?? null, - acs_system_id: $json->acs_system_id ?? null, - acs_user_count: $json->acs_user_count ?? null, - connected_account_id: $json->connected_account_id ?? null, - connected_account_ids: $json->connected_account_ids ?? null, - created_at: $json->created_at ?? null, - default_credential_manager_acs_system_id: $json->default_credential_manager_acs_system_id ?? - null, - errors: array_map( - fn($e) => AcsSystemErrors::from_json($e), - $json->errors ?? [], - ), - external_type: $json->external_type ?? null, - external_type_display_name: $json->external_type_display_name ?? - null, - image_alt_text: $json->image_alt_text ?? null, - image_url: $json->image_url ?? null, - is_credential_manager: $json->is_credential_manager ?? null, - location: isset($json->location) - ? AcsSystemLocation::from_json($json->location) - : null, - name: $json->name ?? null, - system_type: $json->system_type ?? null, - system_type_display_name: $json->system_type_display_name ?? null, - visionline_metadata: isset($json->visionline_metadata) - ? AcsSystemVisionlineMetadata::from_json( - $json->visionline_metadata, + + public function __construct( + /** + * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public string|null $acs_system_id, + /** + * ID of the connected account associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public string|null $connected_account_id, + /** + * IDs of the [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). + * + * @var list|null + * @deprecated Use `connected_account_id`. + */ + public array|null $connected_account_ids, + /** + * Date and time at which the [access control system](https://docs.seam.co/low-level-apis/access-systems) was created. + */ + public string|null $created_at, + /** + * Errors associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). + * + * @var list<\Seam\Resources\AcsSystem\Errors> + */ + public array $errors, + /** + * Alternative text for the [access control system](https://docs.seam.co/low-level-apis/access-systems) image. + */ + public string|null $image_alt_text, + /** + * URL for the image that represents the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public string|null $image_url, + /** + * Indicates whether the `acs_system` is a credential manager. + */ + public bool|null $is_credential_manager, + /** + * Location information for the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public \Seam\Resources\AcsSystem\Location|null $location, + /** + * Name of the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public string|null $name, + /** + * Warnings associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). + * + * @var list<\Seam\Resources\AcsSystem\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public string|null $workspace_id, + /** + * Number of access groups in the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public float|null $acs_access_group_count = null, + /** + * Number of users in the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public float|null $acs_user_count = null, + /** + * ID of the default credential manager `acs_system` for this [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public string|null $default_credential_manager_acs_system_id = null, + /** + * Brand-specific terminology for the [access control system](https://docs.seam.co/low-level-apis/access-systems) type. + * + * @var value-of<\Seam\Resources\AcsSystem\ExternalType>|string|null + */ + public string|null $external_type = null, + /** + * Display name that corresponds to the brand-specific terminology for the [access control system](https://docs.seam.co/low-level-apis/access-systems) type. + */ + public string|null $external_type_display_name = null, + /** + * @var value-of<\Seam\Resources\AcsSystem\SystemType>|string|null + * @deprecated Use `external_type`. + */ + public string|null $system_type = null, + /** + * @deprecated Use `external_type_display_name`. + */ + public string|null $system_type_display_name = null, + /** + * Visionline-specific metadata for the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + public \Seam\Resources\AcsSystem\VisionlineMetadata|null $visionline_metadata = null, + ) {} + } +} + +namespace Seam\Resources\AcsSystem { + /** + * Errors associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\AcsSystem\Errors\ErrorCode::tryFrom( + $json->error_code, ) - : null, - warnings: array_map( - fn($w) => AcsSystemWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); + : null; + + return match ($discriminant) { + \Seam\Resources\AcsSystem\Errors\ErrorCode::SEAM_BRIDGE_DISCONNECTED + => \Seam\Resources\AcsSystem\Errors\SeamBridgeDisconnected::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Errors\ErrorCode::BRIDGE_DISCONNECTED + => \Seam\Resources\AcsSystem\Errors\BridgeDisconnected::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Errors\ErrorCode::VISIONLINE_INSTANCE_UNREACHABLE + => \Seam\Resources\AcsSystem\Errors\VisionlineInstanceUnreachable::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Errors\ErrorCode::SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED + => \Seam\Resources\AcsSystem\Errors\SaltoKsSubscriptionLimitExceeded::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Errors\ErrorCode::INSUFFICIENT_PERMISSIONS + => \Seam\Resources\AcsSystem\Errors\InsufficientPermissions::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Errors\ErrorCode::ACS_SYSTEM_DISCONNECTED + => \Seam\Resources\AcsSystem\Errors\AcsSystemDisconnected::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Errors\ErrorCode::ACCOUNT_DISCONNECTED + => \Seam\Resources\AcsSystem\Errors\AccountDisconnected::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Errors\ErrorCode::SALTO_KS_CERTIFICATION_EXPIRED + => \Seam\Resources\AcsSystem\Errors\SaltoKsCertificationExpired::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Errors\ErrorCode::PROVIDER_SERVICE_UNAVAILABLE + => \Seam\Resources\AcsSystem\Errors\ProviderServiceUnavailable::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} } - public function __construct( - /** - * Number of access groups in the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public float|null $acs_access_group_count, - /** - * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public string|null $acs_system_id, - /** - * Number of users in the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public float|null $acs_user_count, - /** - * ID of the connected account associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public string|null $connected_account_id, - /** - * IDs of the [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). - * - * @deprecated Use `connected_account_id`. - */ - public array|null $connected_account_ids, - /** - * Date and time at which the [access control system](https://docs.seam.co/low-level-apis/access-systems) was created. - */ - public string|null $created_at, - /** - * ID of the default credential manager `acs_system` for this [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public string|null $default_credential_manager_acs_system_id, - /** - * Errors associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public array $errors, - /** - * Brand-specific terminology for the [access control system](https://docs.seam.co/low-level-apis/access-systems) type. - */ - public string|null $external_type, - /** - * Display name that corresponds to the brand-specific terminology for the [access control system](https://docs.seam.co/low-level-apis/access-systems) type. - */ - public string|null $external_type_display_name, - /** - * Alternative text for the [access control system](https://docs.seam.co/low-level-apis/access-systems) image. - */ - public string|null $image_alt_text, - /** - * URL for the image that represents the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public string|null $image_url, - /** - * Indicates whether the `acs_system` is a credential manager. - */ - public bool|null $is_credential_manager, - /** - * Location information for the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public AcsSystemLocation|null $location, - /** - * Name of the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public string|null $name, - /** - * @deprecated Use `external_type`. - */ - public string|null $system_type, - /** - * @deprecated Use `external_type_display_name`. - */ - public string|null $system_type_display_name, - /** - * Visionline-specific metadata for the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public AcsSystemVisionlineMetadata|null $visionline_metadata, - /** - * Warnings associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public array $warnings, - /** - * ID of the workspace that contains the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ - public string|null $workspace_id, - ) {} -} + /** + * Location information for the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + class Location + { + public static function from_json(mixed $json): Location|null + { + if (!$json) { + return null; + } + return new self(time_zone: $json->time_zone ?? null); + } + + public function __construct( + /** + * Time zone in which the [access control system](https://docs.seam.co/low-level-apis/access-systems) is located. + */ + public string|null $time_zone, + ) {} + } -/** - * Errors associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ -class AcsSystemErrors -{ - public static function from_json(mixed $json): AcsSystemErrors|null + /** + * Visionline-specific metadata for the [access control system](https://docs.seam.co/low-level-apis/access-systems). + */ + class VisionlineMetadata { - if (!$json) { - return null; + public static function from_json(mixed $json): VisionlineMetadata|null + { + if (!$json) { + return null; + } + return new self( + lan_address: $json->lan_address ?? null, + mobile_access_uuid: $json->mobile_access_uuid ?? null, + system_id: $json->system_id ?? null, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - is_bridge_error: $json->is_bridge_error ?? null, - message: $json->message ?? null, - ); + + public function __construct( + /** + * IP address or hostname of the main Visionline server relative to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge) on the local network. + */ + public string|null $lan_address = null, + /** + * Keyset loaded into a reader. Mobile keys and reader administration tools securely authenticate only with readers programmed with a matching keyset. + */ + public string|null $mobile_access_uuid = null, + /** + * Unique ID assigned by the ASSA ABLOY licensing team that identifies each hotel in your credential manager. + */ + public string|null $system_id = null, + ) {} + } + + /** + * Warnings associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\AcsSystem\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AcsSystem\Warnings\WarningCode::SALTO_KS_SUBSCRIPTION_LIMIT_ALMOST_REACHED + => \Seam\Resources\AcsSystem\Warnings\SaltoKsSubscriptionLimitAlmostReached::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Warnings\WarningCode::TIME_ZONE_DOES_NOT_MATCH_LOCATION + => \Seam\Resources\AcsSystem\Warnings\TimeZoneDoesNotMatchLocation::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Warnings\WarningCode::SETUP_REQUIRED + => \Seam\Resources\AcsSystem\Warnings\SetupRequired::from_json( + $json, + ), + \Seam\Resources\AcsSystem\Warnings\WarningCode::UNKNOWN_ISSUE_WITH_ACS_SYSTEM + => \Seam\Resources\AcsSystem\Warnings\UnknownIssueWithAcsSystem::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } + + enum ExternalType: string + { + case PTI_SITE = "pti_site"; + case AVIGILON_ALTA_ORG = "avigilon_alta_org"; + case SALTO_KS_SITE = "salto_ks_site"; + case SALTO_SPACE_SYSTEM = "salto_space_system"; + case BRIVO_ACCOUNT = "brivo_account"; + case HID_CREDENTIAL_MANAGER_ORGANIZATION = "hid_credential_manager_organization"; + case VISIONLINE_SYSTEM = "visionline_system"; + case ASSA_ABLOY_CREDENTIAL_SERVICE = "assa_abloy_credential_service"; + case LATCH_BUILDING = "latch_building"; + case DORMAKABA_COMMUNITY_SITE = "dormakaba_community_site"; + case DORMAKABA_AMBIANCE_SITE = "dormakaba_ambiance_site"; + case LEGIC_CONNECT_CREDENTIAL_SERVICE = "legic_connect_credential_service"; + case ASSA_ABLOY_VOSTIO = "assa_abloy_vostio"; + case ASSA_ABLOY_VOSTIO_CREDENTIAL_SERVICE = "assa_abloy_vostio_credential_service"; + case HOTEK_SITE = "hotek_site"; + case KISI_ORGANIZATION = "kisi_organization"; + case AKILES_ORGANIZATION = "akiles_organization"; } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Indicates whether the error is related to the [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). - */ - public bool|null $is_bridge_error, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} + enum SystemType: string + { + case PTI_SITE = "pti_site"; + case AVIGILON_ALTA_ORG = "avigilon_alta_org"; + case SALTO_KS_SITE = "salto_ks_site"; + case SALTO_SPACE_SYSTEM = "salto_space_system"; + case BRIVO_ACCOUNT = "brivo_account"; + case HID_CREDENTIAL_MANAGER_ORGANIZATION = "hid_credential_manager_organization"; + case VISIONLINE_SYSTEM = "visionline_system"; + case ASSA_ABLOY_CREDENTIAL_SERVICE = "assa_abloy_credential_service"; + case LATCH_BUILDING = "latch_building"; + case DORMAKABA_COMMUNITY_SITE = "dormakaba_community_site"; + case DORMAKABA_AMBIANCE_SITE = "dormakaba_ambiance_site"; + case LEGIC_CONNECT_CREDENTIAL_SERVICE = "legic_connect_credential_service"; + case ASSA_ABLOY_VOSTIO = "assa_abloy_vostio"; + case ASSA_ABLOY_VOSTIO_CREDENTIAL_SERVICE = "assa_abloy_vostio_credential_service"; + case HOTEK_SITE = "hotek_site"; + case KISI_ORGANIZATION = "kisi_organization"; + case AKILES_ORGANIZATION = "akiles_organization"; + } } -/** - * Location information for the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ -class AcsSystemLocation -{ - public static function from_json(mixed $json): AcsSystemLocation|null +namespace Seam\Resources\AcsSystem\Errors { + /** + * Indicates that the Seam API cannot communicate with [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge), for example, if Seam Bridge executable has stopped or if the computer running the Seam Bridge executable is offline. + * This error might also occur if Seam Bridge is connected to the wrong [workspace](https://docs.seam.co/core-concepts/workspaces). + * See also [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system#acs_system-errors-seam_bridge_disconnected). + */ + final class SeamBridgeDisconnected extends \Seam\Resources\AcsSystem\Errors { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): SeamBridgeDisconnected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); } - return new self(time_zone: $json->time_zone ?? null); } - public function __construct( - /** - * Time zone in which the [access control system](https://docs.seam.co/low-level-apis/access-systems) is located. - */ - public string|null $time_zone, - ) {} -} + /** + * Indicates that the Seam API cannot communicate with [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge), for example, if Seam Bridge executable has stopped or if the computer running the Seam Bridge executable is offline. + * See also [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system#acs_system-errors-seam_bridge_disconnected). + */ + final class BridgeDisconnected extends \Seam\Resources\AcsSystem\Errors + { + public static function from_json(mixed $json): BridgeDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates whether the error is related to the [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + public bool|null $is_bridge_error = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge) is functioning correctly and the Seam API can communicate with Seam Bridge, but the Seam API cannot connect to the on-premises [Visionline access control system](https://docs.seam.co/device-and-system-integration-guides/assa-abloy-visionline-access-control-system). + * For example, the IP address of the on-premises access control system may be set incorrectly within the Seam [workspace](https://docs.seam.co/core-concepts/workspaces). + * See also [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system#acs_system-errors-visionline_instance_unreachable). + */ + final class VisionlineInstanceUnreachable extends + \Seam\Resources\AcsSystem\Errors + { + public static function from_json( + mixed $json, + ): VisionlineInstanceUnreachable|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the maximum number of users allowed for the site has been reached. This means that new access codes cannot be created. Contact Salto support to increase the user limit. + */ + final class SaltoKsSubscriptionLimitExceeded extends + \Seam\Resources\AcsSystem\Errors + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that Seam's integration user does not have sufficient permissions on the provider's system backing this [access control system](https://docs.seam.co/low-level-apis/access-systems). Access cannot be managed until permissions are restored. See the error message for specifics, then either reauthorize the connected account in Seam or grant the integration user the required permissions in the provider's system. + */ + final class InsufficientPermissions extends \Seam\Resources\AcsSystem\Errors + { + public static function from_json( + mixed $json, + ): InsufficientPermissions|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } -/** - * Visionline-specific metadata for the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ -class AcsSystemVisionlineMetadata -{ - public static function from_json( - mixed $json, - ): AcsSystemVisionlineMetadata|null { - if (!$json) { - return null; + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); } - return new self( - lan_address: $json->lan_address ?? null, - mobile_access_uuid: $json->mobile_access_uuid ?? null, - system_id: $json->system_id ?? null, - ); } - public function __construct( - /** - * IP address or hostname of the main Visionline server relative to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge) on the local network. - */ - public string|null $lan_address, - /** - * Keyset loaded into a reader. Mobile keys and reader administration tools securely authenticate only with readers programmed with a matching keyset. - */ - public string|null $mobile_access_uuid, - /** - * Unique ID assigned by the ASSA ABLOY licensing team that identifies each hotel in your credential manager. - */ - public string|null $system_id, - ) {} + /** + * Indicates that the [access control system](https://docs.seam.co/low-level-apis/access-systems) has been disconnected. See [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system) to resolve the issue. + */ + final class AcsSystemDisconnected extends \Seam\Resources\AcsSystem\Errors + { + public static function from_json( + mixed $json, + ): AcsSystemDisconnected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the login credentials are invalid. Reconnect the account using a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews) to restore access. + */ + final class AccountDisconnected extends \Seam\Resources\AcsSystem\Errors + { + public static function from_json(mixed $json): AccountDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the [access control system](https://docs.seam.co/low-level-apis/access-systems) has lost its Salto KS certification. Contact [support](mailto:support@seam.co) to regain access. + */ + final class SaltoKsCertificationExpired extends + \Seam\Resources\AcsSystem\Errors + { + public static function from_json( + mixed $json, + ): SaltoKsCertificationExpired|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the access control system provider's service is temporarily unavailable. Seam will automatically retry and reconnect when the service becomes available again. + */ + final class ProviderServiceUnavailable extends + \Seam\Resources\AcsSystem\Errors + { + public static function from_json( + mixed $json, + ): ProviderServiceUnavailable|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + enum ErrorCode: string + { + case SEAM_BRIDGE_DISCONNECTED = "seam_bridge_disconnected"; + case BRIDGE_DISCONNECTED = "bridge_disconnected"; + case VISIONLINE_INSTANCE_UNREACHABLE = "visionline_instance_unreachable"; + case SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED = "salto_ks_subscription_limit_exceeded"; + case INSUFFICIENT_PERMISSIONS = "insufficient_permissions"; + case ACS_SYSTEM_DISCONNECTED = "acs_system_disconnected"; + case ACCOUNT_DISCONNECTED = "account_disconnected"; + case SALTO_KS_CERTIFICATION_EXPIRED = "salto_ks_certification_expired"; + case PROVIDER_SERVICE_UNAVAILABLE = "provider_service_unavailable"; + } } -/** - * Warnings associated with the [access control system](https://docs.seam.co/low-level-apis/access-systems). - */ -class AcsSystemWarnings -{ - public static function from_json(mixed $json): AcsSystemWarnings|null +namespace Seam\Resources\AcsSystem\Warnings { + /** + * Indicates that the Salto KS site has exceeded 80% of the maximum number of allowed users. Increase your subscription limit or delete some users from your site to rectify the issue. + */ + final class SaltoKsSubscriptionLimitAlmostReached extends + \Seam\Resources\AcsSystem\Warnings + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitAlmostReached|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates the [access control system](https://docs.seam.co/low-level-apis/access-systems) time zone could not be determined because the reported physical location does not match the time zone configured on the physical [ACS entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + final class TimeZoneDoesNotMatchLocation extends + \Seam\Resources\AcsSystem\Warnings + { + public static function from_json( + mixed $json, + ): TimeZoneDoesNotMatchLocation|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + misconfigured_acs_entrance_ids: $json->misconfigured_acs_entrance_ids ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * @var list|null + * @deprecated this field is deprecated. + */ + public array|null $misconfigured_acs_entrance_ids = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the access control system requires additional setup before it can be fully operational. Follow the instructions in the warning message to complete the setup. + */ + final class SetupRequired extends \Seam\Resources\AcsSystem\Warnings + { + public static function from_json(mixed $json): SetupRequired|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that Seam encountered an unexpected error while syncing this [access control system](https://docs.seam.co/low-level-apis/access-systems), so its users, credentials, and access groups may be out of date. Seam retries on every sync cycle and clears this warning once a sync succeeds; if it persists, contact [support](mailto:support@seam.co). + */ + final class UnknownIssueWithAcsSystem extends + \Seam\Resources\AcsSystem\Warnings { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): UnknownIssueWithAcsSystem|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\AcsSystem\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - misconfigured_acs_entrance_ids: $json->misconfigured_acs_entrance_ids ?? - null, - warning_code: $json->warning_code ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * @deprecated this field is deprecated. - */ - public array|null $misconfigured_acs_entrance_ids, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + enum WarningCode: string + { + case SALTO_KS_SUBSCRIPTION_LIMIT_ALMOST_REACHED = "salto_ks_subscription_limit_almost_reached"; + case TIME_ZONE_DOES_NOT_MATCH_LOCATION = "time_zone_does_not_match_location"; + case SETUP_REQUIRED = "setup_required"; + case UNKNOWN_ISSUE_WITH_ACS_SYSTEM = "unknown_issue_with_acs_system"; + } } diff --git a/src/Resources/AcsUser.php b/src/Resources/AcsUser.php index b8df0217..c063569a 100644 --- a/src/Resources/AcsUser.php +++ b/src/Resources/AcsUser.php @@ -1,478 +1,1710 @@ access_schedule) - ? AcsUserAccessSchedule::from_json($json->access_schedule) - : null, - acs_system_id: $json->acs_system_id ?? null, - acs_user_id: $json->acs_user_id ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - email: $json->email ?? null, - email_address: $json->email_address ?? null, - errors: array_map( - fn($e) => AcsUserErrors::from_json($e), - $json->errors ?? [], - ), - external_type: $json->external_type ?? null, - external_type_display_name: $json->external_type_display_name ?? - null, - full_name: $json->full_name ?? null, - hid_acs_system_id: $json->hid_acs_system_id ?? null, - is_managed: $json->is_managed ?? null, - is_suspended: $json->is_suspended ?? null, - pending_mutations: array_map( - fn($p) => AcsUserPendingMutations::from_json($p), - $json->pending_mutations ?? [], - ), - phone_number: $json->phone_number ?? null, - salto_ks_metadata: isset($json->salto_ks_metadata) - ? AcsUserSaltoKsMetadata::from_json($json->salto_ks_metadata) - : null, - salto_space_metadata: isset($json->salto_space_metadata) - ? AcsUserSaltoSpaceMetadata::from_json( - $json->salto_space_metadata, +namespace Seam\Resources { + /** + * Represents a [user](https://docs.seam.co/low-level-apis/access-systems/user-management) in an [access system](https://docs.seam.co/low-level-apis/access-systems). + * + * An access system user typically refers to an individual who requires access, like an employee or resident. Each user can possess multiple credentials that serve as their keys or identifiers for access. The type of credential can vary widely. For example, in the Salto system, a user can have a PIN code, a mobile app account, and a fob. In other platforms, it is not uncommon for a user to have more than one of the same credential type, such as multiple key cards. Additionally, these credentials can have a schedule or validity period. + * + * For details about how to configure users in your access system, see the corresponding [system integration guide](https://docs.seam.co/device-and-system-integration-guides#access-control-systems). + */ + class AcsUser + { + public static function from_json(mixed $json): AcsUser|null + { + if (!$json) { + return null; + } + return new self( + acs_system_id: $json->acs_system_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn($e) => \Seam\Resources\AcsUser\Errors::from_json($e), + $json->errors ?? [], + ), + is_managed: $json->is_managed ?? null, + warnings: array_map( + fn($w) => \Seam\Resources\AcsUser\Warnings::from_json($w), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + access_schedule: isset($json->access_schedule) + ? \Seam\Resources\AcsUser\AccessSchedule::from_json( + $json->access_schedule, + ) + : null, + email: $json->email ?? null, + email_address: $json->email_address ?? null, + external_type: $json->external_type ?? null, + external_type_display_name: $json->external_type_display_name ?? + null, + full_name: $json->full_name ?? null, + hid_acs_system_id: $json->hid_acs_system_id ?? null, + is_suspended: $json->is_suspended ?? null, + pending_mutations: array_map( + fn( + $p, + ) => \Seam\Resources\AcsUser\PendingMutations::from_json( + $p, + ), + $json->pending_mutations ?? [], + ), + phone_number: $json->phone_number ?? null, + salto_ks_metadata: isset($json->salto_ks_metadata) + ? \Seam\Resources\AcsUser\SaltoKsMetadata::from_json( + $json->salto_ks_metadata, + ) + : null, + salto_space_metadata: isset($json->salto_space_metadata) + ? \Seam\Resources\AcsUser\SaltoSpaceMetadata::from_json( + $json->salto_space_metadata, + ) + : null, + user_identity_email_address: $json->user_identity_email_address ?? + null, + user_identity_full_name: $json->user_identity_full_name ?? null, + user_identity_id: $json->user_identity_id ?? null, + user_identity_phone_number: $json->user_identity_phone_number ?? + null, + ); + } + + public function __construct( + /** + * ID of the [access system](https://docs.seam.co/low-level-apis/access-systems) that contains the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $acs_system_id, + /** + * ID of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $acs_user_id, + /** + * The ID of the connected account that is associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $connected_account_id, + /** + * Date and time at which the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was created. + */ + public string|null $created_at, + /** + * Display name for the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $display_name, + /** + * Errors associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + * + * @var list<\Seam\Resources\AcsUser\Errors> + */ + public array $errors, + /** + * Indicates whether Seam manages the access system user. + */ + public true|null $is_managed, + /** + * Warnings associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + * + * @var list<\Seam\Resources\AcsUser\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $workspace_id, + /** + * `starts_at` and `ends_at` timestamps for the [access system user's](https://docs.seam.co/low-level-apis/access-systems/user-management) access. + */ + public \Seam\Resources\AcsUser\AccessSchedule|null $access_schedule = null, + /** + * @deprecated use email_address. + */ + public string|null $email = null, + /** + * Email address of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $email_address = null, + /** + * Brand-specific terminology for the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) type. + * + * @var value-of<\Seam\Resources\AcsUser\ExternalType>|string|null + */ + public string|null $external_type = null, + /** + * Display name that corresponds to the brand-specific terminology for the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) type. + */ + public string|null $external_type_display_name = null, + /** + * Full name of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $full_name = null, + /** + * ID of the HID access control system associated with the user. + */ + public string|null $hid_acs_system_id = null, + /** + * Indicates whether the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) is currently [suspended](https://docs.seam.co/low-level-apis/access-systems/user-management/suspending-and-unsuspending-users). + */ + public bool|null $is_suspended = null, + /** + * Pending mutations associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). Seam is in the process of pushing these mutations to the integrated access system. + * + * @var list<\Seam\Resources\AcsUser\PendingMutations>|null + */ + public array|null $pending_mutations = null, + /** + * Phone number of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) in E.164 format (for example, `+15555550100`). + */ + public string|null $phone_number = null, + /** + * Salto KS-specific metadata associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public \Seam\Resources\AcsUser\SaltoKsMetadata|null $salto_ks_metadata = null, + /** + * Salto Space-specific metadata associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public \Seam\Resources\AcsUser\SaltoSpaceMetadata|null $salto_space_metadata = null, + /** + * Email address of the user identity associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $user_identity_email_address = null, + /** + * Full name of the user identity associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $user_identity_full_name = null, + /** + * ID of the user identity associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + public string|null $user_identity_id = null, + /** + * Phone number of the user identity associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) in E.164 format (for example, `+15555550100`). + */ + public string|null $user_identity_phone_number = null, + ) {} + } +} + +namespace Seam\Resources\AcsUser { + /** + * `starts_at` and `ends_at` timestamps for the [access system user's](https://docs.seam.co/low-level-apis/access-systems/user-management) access. + */ + class AccessSchedule + { + public static function from_json(mixed $json): AccessSchedule|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the user's access ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $ends_at, + /** + * Date and time at which the user's access starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at, + ) {} + } + + /** + * Errors associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\AcsUser\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AcsUser\Errors\ErrorCode::DELETED_EXTERNALLY + => \Seam\Resources\AcsUser\Errors\DeletedExternally::from_json( + $json, + ), + \Seam\Resources\AcsUser\Errors\ErrorCode::SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED + => \Seam\Resources\AcsUser\Errors\SaltoKsSubscriptionLimitExceeded::from_json( + $json, + ), + \Seam\Resources\AcsUser\Errors\ErrorCode::FAILED_TO_CREATE_ON_ACS_SYSTEM + => \Seam\Resources\AcsUser\Errors\FailedToCreateOnAcsSystem::from_json( + $json, + ), + \Seam\Resources\AcsUser\Errors\ErrorCode::FAILED_TO_UPDATE_ON_ACS_SYSTEM + => \Seam\Resources\AcsUser\Errors\FailedToUpdateOnAcsSystem::from_json( + $json, + ), + \Seam\Resources\AcsUser\Errors\ErrorCode::FAILED_TO_DELETE_ON_ACS_SYSTEM + => \Seam\Resources\AcsUser\Errors\FailedToDeleteOnAcsSystem::from_json( + $json, + ), + \Seam\Resources\AcsUser\Errors\ErrorCode::LATCH_CONFLICT_WITH_RESIDENT_USER + => \Seam\Resources\AcsUser\Errors\LatchConflictWithResidentUser::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * @var value-of<\Seam\Resources\AcsUser\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Pending mutations associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). Seam is in the process of pushing these mutations to the integrated access system. Known mutation_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class PendingMutations + { + public static function from_json(mixed $json): PendingMutations|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->mutation_code ?? null) + ? \Seam\Resources\AcsUser\PendingMutations\MutationCode::tryFrom( + $json->mutation_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\AcsUser\PendingMutations\MutationCode::CREATING + => \Seam\Resources\AcsUser\PendingMutations\Creating::from_json( + $json, + ), + \Seam\Resources\AcsUser\PendingMutations\MutationCode::DELETING + => \Seam\Resources\AcsUser\PendingMutations\Deleting::from_json( + $json, + ), + \Seam\Resources\AcsUser\PendingMutations\MutationCode::DEFERRING_CREATION + => \Seam\Resources\AcsUser\PendingMutations\DeferringCreation::from_json( + $json, + ), + \Seam\Resources\AcsUser\PendingMutations\MutationCode::UPDATING_USER_INFORMATION + => \Seam\Resources\AcsUser\PendingMutations\UpdatingUserInformation::from_json( + $json, + ), + \Seam\Resources\AcsUser\PendingMutations\MutationCode::UPDATING_ACCESS_SCHEDULE + => \Seam\Resources\AcsUser\PendingMutations\UpdatingAccessSchedule::from_json( + $json, + ), + \Seam\Resources\AcsUser\PendingMutations\MutationCode::UPDATING_SUSPENSION_STATE + => \Seam\Resources\AcsUser\PendingMutations\UpdatingSuspensionState::from_json( + $json, + ), + \Seam\Resources\AcsUser\PendingMutations\MutationCode::UPDATING_GROUP_MEMBERSHIP + => \Seam\Resources\AcsUser\PendingMutations\UpdatingGroupMembership::from_json( + $json, + ), + \Seam\Resources\AcsUser\PendingMutations\MutationCode::DEFERRING_GROUP_MEMBERSHIP_UPDATE + => \Seam\Resources\AcsUser\PendingMutations\DeferringGroupMembershipUpdate::from_json( + $json, + ), + \Seam\Resources\AcsUser\PendingMutations\MutationCode::UPDATING_CREDENTIAL_ASSIGNMENT + => \Seam\Resources\AcsUser\PendingMutations\UpdatingCredentialAssignment::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + public string|null $created_at, + /** + * Detailed description of the mutation. + */ + public string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + ) {} + } + + /** + * Salto KS-specific metadata associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + class SaltoKsMetadata + { + public static function from_json(mixed $json): SaltoKsMetadata|null + { + if (!$json) { + return null; + } + return new self(is_subscribed: $json->is_subscribed ?? null); + } + + public function __construct( + /** + * Indicates whether the user holds an active subscription slot on the Salto KS site. Only subscribed users can unlock doors and count against the site's user-subscription limit. A user may not be subscribed because their access schedule has not started or has ended, the site has reached its subscription limit, or they were manually unsubscribed. This is distinct from `is_suspended`, which reflects whether the user has been explicitly blocked. + */ + public bool|null $is_subscribed = null, + ) {} + } + + /** + * Salto Space-specific metadata associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). + */ + class SaltoSpaceMetadata + { + public static function from_json(mixed $json): SaltoSpaceMetadata|null + { + if (!$json) { + return null; + } + return new self( + audit_openings: $json->audit_openings ?? null, + user_id: $json->user_id ?? null, + ); + } + + public function __construct( + /** + * Indicates whether AuditOpenings is enabled for the user in the Salto Space access system. + */ + public bool|null $audit_openings = null, + /** + * User ID in the Salto Space access system. + */ + public string|null $user_id = null, + ) {} + } + + /** + * Warnings associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\AcsUser\Warnings\WarningCode::tryFrom( + $json->warning_code, ) - : null, - user_identity_email_address: $json->user_identity_email_address ?? - null, - user_identity_full_name: $json->user_identity_full_name ?? null, - user_identity_id: $json->user_identity_id ?? null, - user_identity_phone_number: $json->user_identity_phone_number ?? - null, - warnings: array_map( - fn($w) => AcsUserWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * `starts_at` and `ends_at` timestamps for the [access system user's](https://docs.seam.co/low-level-apis/access-systems/user-management) access. - */ - public AcsUserAccessSchedule|null $access_schedule, - /** - * ID of the [access system](https://docs.seam.co/low-level-apis/access-systems) that contains the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $acs_system_id, - /** - * ID of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $acs_user_id, - /** - * The ID of the connected account that is associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $connected_account_id, - /** - * Date and time at which the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was created. - */ - public string|null $created_at, - /** - * Display name for the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $display_name, - /** - * @deprecated use email_address. - */ - public string|null $email, - /** - * Email address of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $email_address, - /** - * Errors associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public array $errors, - /** - * Brand-specific terminology for the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) type. - */ - public string|null $external_type, - /** - * Display name that corresponds to the brand-specific terminology for the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) type. - */ - public string|null $external_type_display_name, - /** - * Full name of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $full_name, - /** - * ID of the HID access control system associated with the user. - */ - public string|null $hid_acs_system_id, - /** - * Indicates whether Seam manages the access system user. - */ - public bool|null $is_managed, - /** - * Indicates whether the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) is currently [suspended](https://docs.seam.co/low-level-apis/access-systems/user-management/suspending-and-unsuspending-users). - */ - public bool|null $is_suspended, - /** - * Pending mutations associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). Seam is in the process of pushing these mutations to the integrated access system. - */ - public array $pending_mutations, - /** - * Phone number of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) in E.164 format (for example, `+15555550100`). - */ - public string|null $phone_number, - /** - * Salto KS-specific metadata associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public AcsUserSaltoKsMetadata|null $salto_ks_metadata, - /** - * Salto Space-specific metadata associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public AcsUserSaltoSpaceMetadata|null $salto_space_metadata, - /** - * Email address of the user identity associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $user_identity_email_address, - /** - * Full name of the user identity associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $user_identity_full_name, - /** - * ID of the user identity associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $user_identity_id, - /** - * Phone number of the user identity associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) in E.164 format (for example, `+15555550100`). - */ - public string|null $user_identity_phone_number, - /** - * Warnings associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public array $warnings, - /** - * ID of the workspace that contains the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ - public string|null $workspace_id, - ) {} + : null; + + return match ($discriminant) { + \Seam\Resources\AcsUser\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\AcsUser\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\AcsUser\Warnings\WarningCode::SALTO_KS_USER_NOT_SUBSCRIBED + => \Seam\Resources\AcsUser\Warnings\SaltoKsUserNotSubscribed::from_json( + $json, + ), + \Seam\Resources\AcsUser\Warnings\WarningCode::ACS_USER_INACTIVE + => \Seam\Resources\AcsUser\Warnings\AcsUserInactive::from_json( + $json, + ), + \Seam\Resources\AcsUser\Warnings\WarningCode::UNKNOWN_ISSUE_WITH_ACS_USER + => \Seam\Resources\AcsUser\Warnings\UnknownIssueWithAcsUser::from_json( + $json, + ), + \Seam\Resources\AcsUser\Warnings\WarningCode::LATCH_RESIDENT_USER + => \Seam\Resources\AcsUser\Warnings\LatchResidentUser::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * @var value-of<\Seam\Resources\AcsUser\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } + + enum ExternalType: string + { + case PTI_USER = "pti_user"; + case BRIVO_USER = "brivo_user"; + case HID_CREDENTIAL_MANAGER_USER = "hid_credential_manager_user"; + case SALTO_SITE_USER = "salto_site_user"; + case LATCH_USER = "latch_user"; + case DORMAKABA_COMMUNITY_USER = "dormakaba_community_user"; + case SALTO_SPACE_USER = "salto_space_user"; + case AVIGILON_ALTA_USER = "avigilon_alta_user"; + case KISI_USER = "kisi_user"; + } } -/** - * `starts_at` and `ends_at` timestamps for the [access system user's](https://docs.seam.co/low-level-apis/access-systems/user-management) access. - */ -class AcsUserAccessSchedule -{ - public static function from_json(mixed $json): AcsUserAccessSchedule|null - { - if (!$json) { - return null; - } - return new self( - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * Date and time at which the user's access ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $ends_at, - /** - * Date and time at which the user's access starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\AcsUser\Errors { + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was deleted from the [access system](https://docs.seam.co/low-level-apis/access-systems) outside of Seam. + */ + final class DeletedExternally extends \Seam\Resources\AcsUser\Errors + { + public static function from_json(mixed $json): DeletedExternally|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * @var value-of<\Seam\Resources\AcsUser\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) could not be subscribed on Salto KS because the subscription limit has been exceeded. + */ + final class SaltoKsSubscriptionLimitExceeded extends + \Seam\Resources\AcsUser\Errors + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * @var value-of<\Seam\Resources\AcsUser\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was not created on the [access system](https://docs.seam.co/low-level-apis/access-systems). This is likely due to an internal unexpected error. Contact Seam [support](mailto:support@seam.co). + */ + final class FailedToCreateOnAcsSystem extends \Seam\Resources\AcsUser\Errors + { + public static function from_json( + mixed $json, + ): FailedToCreateOnAcsSystem|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * @var value-of<\Seam\Resources\AcsUser\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was not updated on the [access system](https://docs.seam.co/low-level-apis/access-systems). This is likely due to an internal unexpected error. Contact Seam [support](mailto:support@seam.co). + */ + final class FailedToUpdateOnAcsSystem extends \Seam\Resources\AcsUser\Errors + { + public static function from_json( + mixed $json, + ): FailedToUpdateOnAcsSystem|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * @var value-of<\Seam\Resources\AcsUser\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was not deleted on the [access system](https://docs.seam.co/low-level-apis/access-systems). This is likely due to an internal unexpected error. Contact Seam [support](mailto:support@seam.co). + */ + final class FailedToDeleteOnAcsSystem extends \Seam\Resources\AcsUser\Errors + { + public static function from_json( + mixed $json, + ): FailedToDeleteOnAcsSystem|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * @var value-of<\Seam\Resources\AcsUser\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was created from the Seam API but also exists on Mission Control. This is unsupported. Contact Seam [support](mailto:support@seam.co). + */ + final class LatchConflictWithResidentUser extends + \Seam\Resources\AcsUser\Errors + { + public static function from_json( + mixed $json, + ): LatchConflictWithResidentUser|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * @var value-of<\Seam\Resources\AcsUser\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + enum ErrorCode: string + { + case DELETED_EXTERNALLY = "deleted_externally"; + case SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED = "salto_ks_subscription_limit_exceeded"; + case FAILED_TO_CREATE_ON_ACS_SYSTEM = "failed_to_create_on_acs_system"; + case FAILED_TO_UPDATE_ON_ACS_SYSTEM = "failed_to_update_on_acs_system"; + case FAILED_TO_DELETE_ON_ACS_SYSTEM = "failed_to_delete_on_acs_system"; + case LATCH_CONFLICT_WITH_RESIDENT_USER = "latch_conflict_with_resident_user"; + } +} + +namespace Seam\Resources\AcsUser\PendingMutations { + /** + * Seam is in the process of pushing a user creation to the integrated access system. + */ + final class Creating extends \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json(mixed $json): Creating|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing a user deletion to the integrated access system. + */ + final class Deleting extends \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json(mixed $json): Deleting|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * User exists in Seam but has not been pushed to the provider yet. Will be created when a credential is issued. + */ + final class DeferringCreation extends + \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json(mixed $json): DeferringCreation|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + scheduled_at: $json->scheduled_at ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * Optional: When the user creation is scheduled to occur. + */ + public string|null $scheduled_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + final class UpdatingUserInformation extends + \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingUserInformation|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingUserInformation\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingUserInformation\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Old access system user information. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingUserInformation\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access system user information. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingUserInformation\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an access schedule update to the integrated access system. + */ + final class UpdatingAccessSchedule extends + \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingAccessSchedule|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingAccessSchedule\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingAccessSchedule\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Old access schedule information. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingAccessSchedule\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access schedule information. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingAccessSchedule\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing a suspension state update to the integrated access system. + */ + final class UpdatingSuspensionState extends + \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingSuspensionState|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingSuspensionState\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingSuspensionState\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Old user suspension state information. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingSuspensionState\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New user suspension state information. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingSuspensionState\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of pushing an access group membership update to the integrated access system. + */ + final class UpdatingGroupMembership extends + \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingGroupMembership|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingGroupMembership\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingGroupMembership\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Old access group membership. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingGroupMembership\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access group membership. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingGroupMembership\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * A scheduled access group membership change is pending for this user. + */ + final class DeferringGroupMembershipUpdate extends + \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json( + mixed $json, + ): DeferringGroupMembershipUpdate|null { + if (!$json) { + return null; + } + return new self( + acs_access_group_id: $json->acs_access_group_id ?? null, + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + variant: $json->variant ?? null, + ); + } + + public function __construct( + /** + * ID of the access group involved in the scheduled change. + */ + public string|null $acs_access_group_id, + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * Whether the user is scheduled to be added to or removed from the access group. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\DeferringGroupMembershipUpdate\Variant>|string|null + */ + public string|null $variant, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of assigning or unassigning a credential to the user on the integrated access system. + */ + final class UpdatingCredentialAssignment extends + \Seam\Resources\AcsUser\PendingMutations + { + public static function from_json( + mixed $json, + ): UpdatingCredentialAssignment|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingCredentialAssignment\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\AcsUser\PendingMutations\UpdatingCredentialAssignment\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous credential assignment. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingCredentialAssignment\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. + * + * @var value-of<\Seam\Resources\AcsUser\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New credential assignment. + */ + public \Seam\Resources\AcsUser\PendingMutations\UpdatingCredentialAssignment\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + enum MutationCode: string + { + case CREATING = "creating"; + case DELETING = "deleting"; + case DEFERRING_CREATION = "deferring_creation"; + case UPDATING_USER_INFORMATION = "updating_user_information"; + case UPDATING_ACCESS_SCHEDULE = "updating_access_schedule"; + case UPDATING_SUSPENSION_STATE = "updating_suspension_state"; + case UPDATING_GROUP_MEMBERSHIP = "updating_group_membership"; + case DEFERRING_GROUP_MEMBERSHIP_UPDATE = "deferring_group_membership_update"; + case UPDATING_CREDENTIAL_ASSIGNMENT = "updating_credential_assignment"; + } } -/** - * Errors associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ -class AcsUserErrors -{ - public static function from_json(mixed $json): AcsUserErrors|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} +namespace Seam\Resources\AcsUser\PendingMutations\UpdatingUserInformation { + /** + * Old access system user information. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + email_address: $json->email_address ?? null, + full_name: $json->full_name ?? null, + phone_number: $json->phone_number ?? null, + ); + } + + public function __construct( + /** + * Email address of the access system user. + */ + public string|null $email_address = null, + /** + * Full name of the access system user. + */ + public string|null $full_name = null, + /** + * Phone number of the access system user. + */ + public string|null $phone_number = null, + ) {} + } + + /** + * New access system user information. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + email_address: $json->email_address ?? null, + full_name: $json->full_name ?? null, + phone_number: $json->phone_number ?? null, + ); + } + + public function __construct( + /** + * Email address of the access system user. + */ + public string|null $email_address = null, + /** + * Full name of the access system user. + */ + public string|null $full_name = null, + /** + * Phone number of the access system user. + */ + public string|null $phone_number = null, + ) {} + } } -/** - * Old access system user information. - */ -class AcsUserFrom -{ - public static function from_json(mixed $json): AcsUserFrom|null - { - if (!$json) { - return null; - } - return new self( - acs_access_group_id: $json->acs_access_group_id ?? null, - acs_credential_id: $json->acs_credential_id ?? null, - email_address: $json->email_address ?? null, - ends_at: $json->ends_at ?? null, - full_name: $json->full_name ?? null, - is_suspended: $json->is_suspended ?? null, - phone_number: $json->phone_number ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * Old access group ID. - */ - public string|null $acs_access_group_id, - /** - * Previous credential ID. - */ - public string|null $acs_credential_id, - /** - * Email address of the access system user. - */ - public string|null $email_address, - /** - * Starting time for the access schedule. - */ - public string|null $ends_at, - /** - * Full name of the access system user. - */ - public string|null $full_name, - public bool|null $is_suspended, - /** - * Phone number of the access system user. - */ - public string|null $phone_number, - /** - * Starting time for the access schedule. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\AcsUser\PendingMutations\UpdatingAccessSchedule { + /** + * Old access schedule information. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Starting time for the access schedule. + */ + public string|null $ends_at, + /** + * Starting time for the access schedule. + */ + public string|null $starts_at, + ) {} + } + + /** + * New access schedule information. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Starting time for the access schedule. + */ + public string|null $ends_at, + /** + * Starting time for the access schedule. + */ + public string|null $starts_at, + ) {} + } } -/** - * Pending mutations associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). Seam is in the process of pushing these mutations to the integrated access system. - */ -class AcsUserPendingMutations -{ - public static function from_json(mixed $json): AcsUserPendingMutations|null - { - if (!$json) { - return null; - } - return new self( - acs_access_group_id: $json->acs_access_group_id ?? null, - created_at: $json->created_at ?? null, - from: isset($json->from) - ? AcsUserFrom::from_json($json->from) - : null, - message: $json->message ?? null, - mutation_code: $json->mutation_code ?? null, - scheduled_at: $json->scheduled_at ?? null, - to: isset($json->to) ? AcsUserTo::from_json($json->to) : null, - variant: $json->variant ?? null, - ); - } - - public function __construct( - /** - * ID of the access group involved in the scheduled change. - */ - public string|null $acs_access_group_id, - /** - * Date and time at which the mutation was created. - */ - public string|null $created_at, - /** - * Old access system user information. - */ - public AcsUserFrom|null $from, - /** - * Detailed description of the mutation. - */ - public string|null $message, - /** - * Mutation code to indicate that Seam is in the process of pushing a user creation to the integrated access system. - */ - public string|null $mutation_code, - /** - * Optional: When the user creation is scheduled to occur. - */ - public string|null $scheduled_at, - /** - * New access system user information. - */ - public AcsUserTo|null $to, - /** - * Whether the user is scheduled to be added to or removed from the access group. - */ - public string|null $variant, - ) {} +namespace Seam\Resources\AcsUser\PendingMutations\UpdatingSuspensionState { + /** + * Old user suspension state information. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(is_suspended: $json->is_suspended ?? null); + } + + public function __construct(public bool|null $is_suspended) {} + } + + /** + * New user suspension state information. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(is_suspended: $json->is_suspended ?? null); + } + + public function __construct(public bool|null $is_suspended) {} + } } -/** - * Salto KS-specific metadata associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ -class AcsUserSaltoKsMetadata -{ - public static function from_json(mixed $json): AcsUserSaltoKsMetadata|null +namespace Seam\Resources\AcsUser\PendingMutations\UpdatingGroupMembership { + /** + * Old access group membership. + */ + class From { - if (!$json) { - return null; + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + acs_access_group_id: $json->acs_access_group_id ?? null, + ); } - return new self(is_subscribed: $json->is_subscribed ?? null); + + public function __construct( + /** + * Old access group ID. + */ + public string|null $acs_access_group_id, + ) {} } - public function __construct( - /** - * Indicates whether the user holds an active subscription slot on the Salto KS site. Only subscribed users can unlock doors and count against the site's user-subscription limit. A user may not be subscribed because their access schedule has not started or has ended, the site has reached its subscription limit, or they were manually unsubscribed. This is distinct from `is_suspended`, which reflects whether the user has been explicitly blocked. - */ - public bool|null $is_subscribed, - ) {} + /** + * New access group membership. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + acs_access_group_id: $json->acs_access_group_id ?? null, + ); + } + + public function __construct( + /** + * New access group ID. + */ + public string|null $acs_access_group_id, + ) {} + } } -/** - * Salto Space-specific metadata associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ -class AcsUserSaltoSpaceMetadata -{ - public static function from_json( - mixed $json, - ): AcsUserSaltoSpaceMetadata|null { - if (!$json) { - return null; - } - return new self( - audit_openings: $json->audit_openings ?? null, - user_id: $json->user_id ?? null, - ); - } - - public function __construct( - /** - * Indicates whether AuditOpenings is enabled for the user in the Salto Space access system. - */ - public bool|null $audit_openings, - /** - * User ID in the Salto Space access system. - */ - public string|null $user_id, - ) {} +namespace Seam\Resources\AcsUser\PendingMutations\DeferringGroupMembershipUpdate { + enum Variant: string + { + case ADDING = "adding"; + case REMOVING = "removing"; + } } -/** - * New access system user information. - */ -class AcsUserTo -{ - public static function from_json(mixed $json): AcsUserTo|null - { - if (!$json) { - return null; - } - return new self( - acs_access_group_id: $json->acs_access_group_id ?? null, - acs_credential_id: $json->acs_credential_id ?? null, - email_address: $json->email_address ?? null, - ends_at: $json->ends_at ?? null, - full_name: $json->full_name ?? null, - is_suspended: $json->is_suspended ?? null, - phone_number: $json->phone_number ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * New access group ID. - */ - public string|null $acs_access_group_id, - /** - * New credential ID. - */ - public string|null $acs_credential_id, - /** - * Email address of the access system user. - */ - public string|null $email_address, - /** - * Starting time for the access schedule. - */ - public string|null $ends_at, - /** - * Full name of the access system user. - */ - public string|null $full_name, - public bool|null $is_suspended, - /** - * Phone number of the access system user. - */ - public string|null $phone_number, - /** - * Starting time for the access schedule. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\AcsUser\PendingMutations\UpdatingCredentialAssignment { + /** + * Previous credential assignment. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + acs_credential_id: $json->acs_credential_id ?? null, + ); + } + + public function __construct( + /** + * Previous credential ID. + */ + public string|null $acs_credential_id, + ) {} + } + + /** + * New credential assignment. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + acs_credential_id: $json->acs_credential_id ?? null, + ); + } + + public function __construct( + /** + * New credential ID. + */ + public string|null $acs_credential_id, + ) {} + } } -/** - * Warnings associated with the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). - */ -class AcsUserWarnings -{ - public static function from_json(mixed $json): AcsUserWarnings|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - public string|null $warning_code, - ) {} +namespace Seam\Resources\AcsUser\Warnings { + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) is being deleted from the [access system](https://docs.seam.co/low-level-apis/access-systems). This is a temporary state, and the access system user will be deleted shortly. + */ + final class BeingDeleted extends \Seam\Resources\AcsUser\Warnings + { + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * @var value-of<\Seam\Resources\AcsUser\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) is not subscribed on Salto KS, so they cannot unlock doors or perform any actions. This occurs when the their access schedule hasn’t started yet, if their access schedule has ended, if the site has reached its limit for active users (subscription slots), or if they have been manually unsubscribed. + */ + final class SaltoKsUserNotSubscribed extends + \Seam\Resources\AcsUser\Warnings + { + public static function from_json( + mixed $json, + ): SaltoKsUserNotSubscribed|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * @var value-of<\Seam\Resources\AcsUser\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) exists but is not currently able to gain access—for example, because their access schedule has not started yet or has ended, the access system has reached its limit for active users, or they have been unsubscribed or deactivated. Refer to the warning message for the provider-specific reason. This is distinct from `is_suspended`, which indicates the user has been explicitly blocked. + */ + final class AcsUserInactive extends \Seam\Resources\AcsUser\Warnings + { + public static function from_json(mixed $json): AcsUserInactive|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * @var value-of<\Seam\Resources\AcsUser\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * An unknown issue occurred while syncing the state of this [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) with the provider. This issue may affect the proper functioning of this user. + */ + final class UnknownIssueWithAcsUser extends \Seam\Resources\AcsUser\Warnings + { + public static function from_json( + mixed $json, + ): UnknownIssueWithAcsUser|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * @var value-of<\Seam\Resources\AcsUser\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was created on Latch Mission Control. Please use the Latch Mission Control to manage this user. + */ + final class LatchResidentUser extends \Seam\Resources\AcsUser\Warnings + { + public static function from_json(mixed $json): LatchResidentUser|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * @var value-of<\Seam\Resources\AcsUser\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case BEING_DELETED = "being_deleted"; + case SALTO_KS_USER_NOT_SUBSCRIBED = "salto_ks_user_not_subscribed"; + case ACS_USER_INACTIVE = "acs_user_inactive"; + case UNKNOWN_ISSUE_WITH_ACS_USER = "unknown_issue_with_acs_user"; + case LATCH_RESIDENT_USER = "latch_resident_user"; + } } diff --git a/src/Resources/ActionAttempt.php b/src/Resources/ActionAttempt.php index 3448d210..7977ece8 100644 --- a/src/Resources/ActionAttempt.php +++ b/src/Resources/ActionAttempt.php @@ -1,888 +1,3552 @@ action_attempt_id ?? null, - action_type: $json->action_type ?? null, - error: isset($json->error) - ? ActionAttemptError::from_json($json->error) - : null, - result: isset($json->result) - ? ActionAttemptResult::from_json($json->result) - : null, - status: $json->status ?? null, - ); - } - - public function __construct( - /** - * ID of the action attempt. - */ - public string|null $action_attempt_id, - /** - * Action attempt to track the status of locking a door. - */ - public string|null $action_type, - /** - * Error associated with the action. - */ - public ActionAttemptError|null $error, - /** - * Result of the action. - */ - public ActionAttemptResult|null $result, - public string|null $status, - ) {} +namespace Seam\Resources { + /** + * Base class for actions whose completion is tracked asynchronously. Known action_type values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class ActionAttempt + { + public static function from_json(mixed $json): ActionAttempt|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->action_type ?? null) + ? \Seam\Resources\ActionAttempt\ActionType::tryFrom( + $json->action_type, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\ActionAttempt\ActionType::LOCK_DOOR + => \Seam\Resources\ActionAttempt\LockDoor::from_json($json), + \Seam\Resources\ActionAttempt\ActionType::UNLOCK_DOOR + => \Seam\Resources\ActionAttempt\UnlockDoor::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::SCAN_CREDENTIAL + => \Seam\Resources\ActionAttempt\ScanCredential::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::ENCODE_CREDENTIAL + => \Seam\Resources\ActionAttempt\EncodeCredential::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::SCAN_TO_ASSIGN_CREDENTIAL + => \Seam\Resources\ActionAttempt\ScanToAssignCredential::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::ASSIGN_CREDENTIAL + => \Seam\Resources\ActionAttempt\AssignCredential::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::RESET_SANDBOX_WORKSPACE + => \Seam\Resources\ActionAttempt\ResetSandboxWorkspace::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::SET_FAN_MODE + => \Seam\Resources\ActionAttempt\SetFanMode::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::SET_HVAC_MODE + => \Seam\Resources\ActionAttempt\SetHvacMode::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::ACTIVATE_CLIMATE_PRESET + => \Seam\Resources\ActionAttempt\ActivateClimatePreset::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::SIMULATE_KEYPAD_CODE_ENTRY + => \Seam\Resources\ActionAttempt\SimulateKeypadCodeEntry::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::SIMULATE_MANUAL_LOCK_VIA_KEYPAD + => \Seam\Resources\ActionAttempt\SimulateManualLockViaKeypad::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::PUSH_THERMOSTAT_PROGRAMS + => \Seam\Resources\ActionAttempt\PushThermostatPrograms::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::CONFIGURE_AUTO_LOCK + => \Seam\Resources\ActionAttempt\ConfigureAutoLock::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::SYNC_ACCESS_CODES + => \Seam\Resources\ActionAttempt\SyncAccessCodes::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::CREATE_ACCESS_CODE + => \Seam\Resources\ActionAttempt\CreateAccessCode::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::DELETE_ACCESS_CODE + => \Seam\Resources\ActionAttempt\DeleteAccessCode::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::UPDATE_ACCESS_CODE + => \Seam\Resources\ActionAttempt\UpdateAccessCode::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::CREATE_NOISE_THRESHOLD + => \Seam\Resources\ActionAttempt\CreateNoiseThreshold::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::DELETE_NOISE_THRESHOLD + => \Seam\Resources\ActionAttempt\DeleteNoiseThreshold::from_json( + $json, + ), + \Seam\Resources\ActionAttempt\ActionType::UPDATE_NOISE_THRESHOLD + => \Seam\Resources\ActionAttempt\UpdateNoiseThreshold::from_json( + $json, + ), + default => new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + status: $json->status ?? null, + ), + }; + } + + public function __construct( + /** + * ID of the action attempt. + */ + public string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + public string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\Error|null $error, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + public string|null $status, + ) {} + } } -/** - * Snapshot of credential data read from the physical encoder. - */ -class ActionAttemptAcsCredentialOnEncoder -{ - public static function from_json( - mixed $json, - ): ActionAttemptAcsCredentialOnEncoder|null { - if (!$json) { - return null; - } - return new self( - card_number: $json->card_number ?? null, - created_at: $json->created_at ?? null, - ends_at: $json->ends_at ?? null, - is_issued: $json->is_issued ?? null, - starts_at: $json->starts_at ?? null, - visionline_metadata: isset($json->visionline_metadata) - ? ActionAttemptVisionlineMetadata::from_json( - $json->visionline_metadata, +namespace Seam\Resources\ActionAttempt { + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + class Error + { + public static function from_json(mixed $json): Error|null + { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + type: $json->type ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Type of the error. + */ + public string|null $type, + ) {} + } + + /** + * Locking a door is pending. + */ + final class LockDoor extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): LockDoor|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\LockDoor\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\LockDoor\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Unlocking a door is pending. + */ + final class UnlockDoor extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): UnlockDoor|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\UnlockDoor\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\UnlockDoor\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Reading credential data from the physical encoder is pending. + */ + final class ScanCredential extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): ScanCredential|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\ScanCredential\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of scanning a card. If the attempt was successful, includes a snapshot of credential data read from the physical encoder, the corresponding data stored on Seam and the access system, and any associated warnings. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\ScanCredential\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Encoding credential data from the physical encoder onto a card is pending. + */ + final class EncodeCredential extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): EncodeCredential|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\EncodeCredential\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\EncodeCredential\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Scanning a physical card and assigning the credential is pending. + */ + final class ScanToAssignCredential extends \Seam\Resources\ActionAttempt + { + public static function from_json( + mixed $json, + ): ScanToAssignCredential|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Assigning a credential to an access method is pending. + */ + final class AssignCredential extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): AssignCredential|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\AssignCredential\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of assigning a credential. If successful, includes the updated access method with the assigned credential. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\AssignCredential\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Resetting a sandbox workspace is pending. + */ + final class ResetSandboxWorkspace extends \Seam\Resources\ActionAttempt + { + public static function from_json( + mixed $json, + ): ResetSandboxWorkspace|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Setting the fan mode is pending. + */ + final class SetFanMode extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): SetFanMode|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Setting the HVAC mode is pending. + */ + final class SetHvacMode extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): SetHvacMode|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Activating a climate preset is pending. + */ + final class ActivateClimatePreset extends \Seam\Resources\ActionAttempt + { + public static function from_json( + mixed $json, + ): ActivateClimatePreset|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Simulating a keypad code entry is pending. + */ + final class SimulateKeypadCodeEntry extends \Seam\Resources\ActionAttempt + { + public static function from_json( + mixed $json, + ): SimulateKeypadCodeEntry|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Simulating a manual lock action using a keypad is pending. + */ + final class SimulateManualLockViaKeypad extends + \Seam\Resources\ActionAttempt + { + public static function from_json( + mixed $json, + ): SimulateManualLockViaKeypad|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Pushing thermostat weekly programs is pending. + */ + final class PushThermostatPrograms extends \Seam\Resources\ActionAttempt + { + public static function from_json( + mixed $json, + ): PushThermostatPrograms|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + /** + * Configuring the auto-lock is pending. + */ + final class ConfigureAutoLock extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): ConfigureAutoLock|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + final class SyncAccessCodes extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): SyncAccessCodes|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + final class CreateAccessCode extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): CreateAccessCode|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\CreateAccessCode\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\CreateAccessCode\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + final class DeleteAccessCode extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): DeleteAccessCode|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + final class UpdateAccessCode extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): UpdateAccessCode|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\UpdateAccessCode\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\UpdateAccessCode\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + final class CreateNoiseThreshold extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): CreateNoiseThreshold|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\CreateNoiseThreshold\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\CreateNoiseThreshold\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + final class DeleteNoiseThreshold extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): DeleteNoiseThreshold|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: $json->result ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public mixed $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + final class UpdateNoiseThreshold extends \Seam\Resources\ActionAttempt + { + public static function from_json(mixed $json): UpdateNoiseThreshold|null + { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + error: isset($json->error) + ? \Seam\Resources\ActionAttempt\Error::from_json( + $json->error, + ) + : null, + result: isset($json->result) + ? \Seam\Resources\ActionAttempt\UpdateNoiseThreshold\Result::from_json( + $json->result, + ) + : null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * ID of the action attempt. + */ + string|null $action_attempt_id, + /** + * Action attempt to track the status of locking a door. + * + * @var value-of<\Seam\Resources\ActionAttempt\ActionType>|string|null + */ + string|null $action_type, + /** + * Error associated with the action. Null while the action attempt is pending or when this value does not apply. + */ + \Seam\Resources\ActionAttempt\Error|null $error, + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + public \Seam\Resources\ActionAttempt\UpdateNoiseThreshold\Result|null $result, + /** + * @var value-of<\Seam\Resources\ActionAttempt\Status>|string|null + */ + string|null $status, + ) { + parent::__construct( + action_attempt_id: $action_attempt_id, + action_type: $action_type, + error: $error, + status: $status, + ); + } + } + + enum ActionType: string + { + case LOCK_DOOR = "LOCK_DOOR"; + case UNLOCK_DOOR = "UNLOCK_DOOR"; + case SCAN_CREDENTIAL = "SCAN_CREDENTIAL"; + case ENCODE_CREDENTIAL = "ENCODE_CREDENTIAL"; + case SCAN_TO_ASSIGN_CREDENTIAL = "SCAN_TO_ASSIGN_CREDENTIAL"; + case ASSIGN_CREDENTIAL = "ASSIGN_CREDENTIAL"; + case RESET_SANDBOX_WORKSPACE = "RESET_SANDBOX_WORKSPACE"; + case SET_FAN_MODE = "SET_FAN_MODE"; + case SET_HVAC_MODE = "SET_HVAC_MODE"; + case ACTIVATE_CLIMATE_PRESET = "ACTIVATE_CLIMATE_PRESET"; + case SIMULATE_KEYPAD_CODE_ENTRY = "SIMULATE_KEYPAD_CODE_ENTRY"; + case SIMULATE_MANUAL_LOCK_VIA_KEYPAD = "SIMULATE_MANUAL_LOCK_VIA_KEYPAD"; + case PUSH_THERMOSTAT_PROGRAMS = "PUSH_THERMOSTAT_PROGRAMS"; + case CONFIGURE_AUTO_LOCK = "CONFIGURE_AUTO_LOCK"; + case SYNC_ACCESS_CODES = "SYNC_ACCESS_CODES"; + case CREATE_ACCESS_CODE = "CREATE_ACCESS_CODE"; + case DELETE_ACCESS_CODE = "DELETE_ACCESS_CODE"; + case UPDATE_ACCESS_CODE = "UPDATE_ACCESS_CODE"; + case CREATE_NOISE_THRESHOLD = "CREATE_NOISE_THRESHOLD"; + case DELETE_NOISE_THRESHOLD = "DELETE_NOISE_THRESHOLD"; + case UPDATE_NOISE_THRESHOLD = "UPDATE_NOISE_THRESHOLD"; + } + + enum Status: string + { + case SUCCESS = "success"; + case PENDING = "pending"; + case ERROR = "error"; + } +} + +namespace Seam\Resources\ActionAttempt\LockDoor { + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self( + was_confirmed_by_device: $json->was_confirmed_by_device ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the device confirmed that the lock action occurred. + */ + public bool|null $was_confirmed_by_device = null, + ) {} + } +} + +namespace Seam\Resources\ActionAttempt\UnlockDoor { + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self( + was_confirmed_by_device: $json->was_confirmed_by_device ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the device confirmed that the unlock action occurred. + */ + public bool|null $was_confirmed_by_device = null, + ) {} + } +} + +namespace Seam\Resources\ActionAttempt\ScanCredential { + /** + * Result of scanning a card. If the attempt was successful, includes a snapshot of credential data read from the physical encoder, the corresponding data stored on Seam and the access system, and any associated warnings. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self( + acs_credential_on_encoder: isset( + $json->acs_credential_on_encoder, ) - : null, - ); - } - - public function __construct( - /** - * A number or string that physically identifies the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $card_number, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. - */ - public string|null $created_at, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) will stop being usable. - */ - public string|null $ends_at, - /** - * Indicates whether the credential has been issued (encoded onto a card). - */ - public bool|null $is_issued, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) becomes usable. - */ - public string|null $starts_at, - /** - * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public ActionAttemptVisionlineMetadata|null $visionline_metadata, - ) {} + ? \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnEncoder::from_json( + $json->acs_credential_on_encoder, + ) + : null, + acs_credential_on_seam: isset($json->acs_credential_on_seam) + ? \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam::from_json( + $json->acs_credential_on_seam, + ) + : null, + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\ActionAttempt\ScanCredential\Result\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + ); + } + + public function __construct( + /** + * Snapshot of credential data read from the physical encoder. + */ + public \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnEncoder|null $acs_credential_on_encoder, + /** + * Corresponding credential data as stored on Seam and the access system. + */ + public \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam|null $acs_credential_on_seam, + /** + * Warnings related to scanning the credential, such as mismatches between the credential data currently encoded on the card and the corresponding data stored on Seam and the access system. + * + * @var list<\Seam\Resources\ActionAttempt\ScanCredential\Result\Warnings> + */ + public array $warnings, + ) {} + } } -/** - * Corresponding credential data as stored on Seam and the access system. - */ -class ActionAttemptAcsCredentialOnSeam -{ - public static function from_json( - mixed $json, - ): ActionAttemptAcsCredentialOnSeam|null { - if (!$json) { - return null; - } - return new self( - access_method: $json->access_method ?? null, - acs_credential_id: $json->acs_credential_id ?? null, - acs_credential_pool_id: $json->acs_credential_pool_id ?? null, - acs_system_id: $json->acs_system_id ?? null, - acs_user_id: $json->acs_user_id ?? null, - akiles_metadata: isset($json->akiles_metadata) - ? ActionAttemptAkilesMetadata::from_json($json->akiles_metadata) - : null, - assa_abloy_vostio_metadata: isset($json->assa_abloy_vostio_metadata) - ? ActionAttemptAssaAbloyVostioMetadata::from_json( +namespace Seam\Resources\ActionAttempt\ScanCredential\Result { + /** + * Snapshot of credential data read from the physical encoder. + */ + class AcsCredentialOnEncoder + { + public static function from_json( + mixed $json, + ): AcsCredentialOnEncoder|null { + if (!$json) { + return null; + } + return new self( + card_number: $json->card_number ?? null, + created_at: $json->created_at ?? null, + ends_at: $json->ends_at ?? null, + is_issued: $json->is_issued ?? null, + starts_at: $json->starts_at ?? null, + visionline_metadata: isset($json->visionline_metadata) + ? \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnEncoder\VisionlineMetadata::from_json( + $json->visionline_metadata, + ) + : null, + ); + } + + public function __construct( + /** + * A number or string that physically identifies the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $card_number, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. + */ + public string|null $created_at, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) will stop being usable. + */ + public string|null $ends_at, + /** + * Indicates whether the credential has been issued (encoded onto a card). + */ + public bool|null $is_issued, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) becomes usable. + */ + public string|null $starts_at, + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnEncoder\VisionlineMetadata|null $visionline_metadata = null, + ) {} + } + + /** + * Corresponding credential data as stored on Seam and the access system. + */ + class AcsCredentialOnSeam + { + public static function from_json(mixed $json): AcsCredentialOnSeam|null + { + if (!$json) { + return null; + } + return new self( + access_method: $json->access_method ?? null, + acs_credential_id: $json->acs_credential_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_managed: $json->is_managed ?? null, + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + acs_credential_pool_id: $json->acs_credential_pool_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + akiles_metadata: isset($json->akiles_metadata) + ? \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\AkilesMetadata::from_json( + $json->akiles_metadata, + ) + : null, + assa_abloy_vostio_metadata: isset( $json->assa_abloy_vostio_metadata, ) - : null, - card_number: $json->card_number ?? null, - code: $json->code ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => ActionAttemptErrors::from_json($e), - $json->errors ?? [], - ), - external_type: $json->external_type ?? null, - external_type_display_name: $json->external_type_display_name ?? - null, - is_issued: $json->is_issued ?? null, - is_latest_desired_state_synced_with_provider: $json->is_latest_desired_state_synced_with_provider ?? - null, - is_managed: $json->is_managed ?? null, - is_multi_phone_sync_credential: $json->is_multi_phone_sync_credential ?? - null, - is_one_time_use: $json->is_one_time_use ?? null, - issued_at: $json->issued_at ?? null, - latest_desired_state_synced_with_provider_at: $json->latest_desired_state_synced_with_provider_at ?? - null, - parent_acs_credential_id: $json->parent_acs_credential_id ?? null, - starts_at: $json->starts_at ?? null, - user_identity_id: $json->user_identity_id ?? null, - visionline_metadata: isset($json->visionline_metadata) - ? ActionAttemptVisionlineMetadata::from_json( - $json->visionline_metadata, - ) - : null, - warnings: array_map( - fn($w) => ActionAttemptWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. - */ - public string|null $access_method, - /** - * ID of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $acs_credential_id, - /** - * ID of the credential pool to which the credential belongs. - */ - public string|null $acs_credential_pool_id, - /** - * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $acs_system_id, - /** - * ID of the [ACS user](https://docs.seam.co/low-level-apis/access-systems/user-management) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $acs_user_id, - /** - * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public ActionAttemptAkilesMetadata|null $akiles_metadata, - /** - * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public ActionAttemptAssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata, - /** - * Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $card_number, - /** - * Access (PIN) code for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $code, - /** - * ID of the [connected account](https://docs.seam.co/core-concepts/connected-accounts) to which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $connected_account_id, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. - */ - public string|null $created_at, - /** - * Display name that corresponds to the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. - */ - public string|null $display_name, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. - */ - public string|null $ends_at, - /** - * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public array $errors, - /** - * Brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. Supported values: `pti_card`, `brivo_credential`, `hid_credential`, `visionline_card`. - */ - public string|null $external_type, - /** - * Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. - */ - public string|null $external_type_display_name, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been encoded onto a card. - */ - public bool|null $is_issued, - /** - * Indicates whether the latest state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been synced from Seam to the provider. - */ - public bool|null $is_latest_desired_state_synced_with_provider, - public bool|null $is_managed, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is a [multi-phone sync credential](https://docs.seam.co/capability-guides/mobile-access/issuing-mobile-credentials-from-an-access-control-system#what-are-multi-phone-sync-credentials). - */ - public bool|null $is_multi_phone_sync_credential, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) can only be used once. If `true`, the code becomes invalid after the first use. - */ - public bool|null $is_one_time_use, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was encoded onto a card. - */ - public string|null $issued_at, - /** - * Date and time at which the state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was most recently synced from Seam to the provider. - */ - public string|null $latest_desired_state_synced_with_provider_at, - /** - * ID of the parent [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $parent_acs_credential_id, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at, - /** - * ID of the [user identity](https://docs.seam.co/api/user_identities) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $user_identity_id, - /** - * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public ActionAttemptVisionlineMetadata|null $visionline_metadata, - /** - * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public array $warnings, - /** - * ID of the workspace that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $workspace_id, - ) {} + ? \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\AssaAbloyVostioMetadata::from_json( + $json->assa_abloy_vostio_metadata, + ) + : null, + card_number: $json->card_number ?? null, + code: $json->code ?? null, + ends_at: $json->ends_at ?? null, + external_type: $json->external_type ?? null, + external_type_display_name: $json->external_type_display_name ?? + null, + is_issued: $json->is_issued ?? null, + is_latest_desired_state_synced_with_provider: $json->is_latest_desired_state_synced_with_provider ?? + null, + is_multi_phone_sync_credential: $json->is_multi_phone_sync_credential ?? + null, + is_one_time_use: $json->is_one_time_use ?? null, + issued_at: $json->issued_at ?? null, + latest_desired_state_synced_with_provider_at: $json->latest_desired_state_synced_with_provider_at ?? + null, + parent_acs_credential_id: $json->parent_acs_credential_id ?? + null, + starts_at: $json->starts_at ?? null, + user_identity_id: $json->user_identity_id ?? null, + visionline_metadata: isset($json->visionline_metadata) + ? \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\VisionlineMetadata::from_json( + $json->visionline_metadata, + ) + : null, + ); + } + + public function __construct( + /** + * Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\AccessMethod>|string|null + */ + public string|null $access_method, + /** + * ID of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $acs_credential_id, + /** + * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $acs_system_id, + /** + * ID of the [connected account](https://docs.seam.co/core-concepts/connected-accounts) to which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $connected_account_id, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. + */ + public string|null $created_at, + /** + * Display name that corresponds to the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. + */ + public string|null $display_name, + /** + * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list<\Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\Errors> + */ + public array $errors, + public bool|null $is_managed, + /** + * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list<\Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $workspace_id, + /** + * ID of the credential pool to which the credential belongs. + */ + public string|null $acs_credential_pool_id = null, + /** + * ID of the [ACS user](https://docs.seam.co/low-level-apis/access-systems/user-management) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $acs_user_id = null, + /** + * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\AkilesMetadata|null $akiles_metadata = null, + /** + * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\AssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata = null, + /** + * Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $card_number = null, + /** + * Access (PIN) code for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $code = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. + */ + public string|null $ends_at = null, + /** + * Brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. Supported values: `pti_card`, `brivo_credential`, `hid_credential`, `visionline_card`. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\ExternalType>|string|null + */ + public string|null $external_type = null, + /** + * Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. + */ + public string|null $external_type_display_name = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been encoded onto a card. + */ + public bool|null $is_issued = null, + /** + * Indicates whether the latest state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been synced from Seam to the provider. + */ + public bool|null $is_latest_desired_state_synced_with_provider = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is a [multi-phone sync credential](https://docs.seam.co/capability-guides/mobile-access/issuing-mobile-credentials-from-an-access-control-system#what-are-multi-phone-sync-credentials). + */ + public bool|null $is_multi_phone_sync_credential = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) can only be used once. If `true`, the code becomes invalid after the first use. + */ + public bool|null $is_one_time_use = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was encoded onto a card. + */ + public string|null $issued_at = null, + /** + * Date and time at which the state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was most recently synced from Seam to the provider. + */ + public string|null $latest_desired_state_synced_with_provider_at = null, + /** + * ID of the parent [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $parent_acs_credential_id = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at = null, + /** + * ID of the [user identity](https://docs.seam.co/api/user_identities) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $user_identity_id = null, + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\VisionlineMetadata|null $visionline_metadata = null, + ) {} + } + + /** + * Warnings related to scanning the credential, such as mismatches between the credential data currently encoded on the card and the corresponding data stored on Seam and the access system. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + return new self( + warning_code: $json->warning_code ?? null, + warning_message: $json->warning_message ?? null, + ); + } + + public function __construct( + /** + * Indicates a warning related to scanning a credential. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanCredential\Result\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $warning_message, + ) {} + } } -/** - * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class ActionAttemptAkilesMetadata -{ - public static function from_json( - mixed $json, - ): ActionAttemptAkilesMetadata|null { - if (!$json) { - return null; - } - return new self(member_pin_id: $json->member_pin_id ?? null); - } - - public function __construct( - /** - * ID of the Akiles member PIN. - */ - public string|null $member_pin_id, - ) {} +namespace Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnEncoder { + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class VisionlineMetadata + { + public static function from_json(mixed $json): VisionlineMetadata|null + { + if (!$json) { + return null; + } + return new self( + cancelled: $json->cancelled ?? null, + card_format: $json->card_format ?? null, + card_holder: $json->card_holder ?? null, + card_id: $json->card_id ?? null, + common_acs_entrance_ids: $json->common_acs_entrance_ids ?? null, + discarded: $json->discarded ?? null, + expired: $json->expired ?? null, + guest_acs_entrance_ids: $json->guest_acs_entrance_ids ?? null, + number_of_issued_cards: $json->number_of_issued_cards ?? null, + overridden: $json->overridden ?? null, + overwritten: $json->overwritten ?? null, + pending_auto_update: $json->pending_auto_update ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is cancelled. + */ + public bool|null $cancelled = null, + /** + * Format of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnEncoder\VisionlineMetadata\CardFormat>|string|null + */ + public string|null $card_format = null, + /** + * Holder of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $card_holder = null, + /** + * Card ID for the Visionline card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $card_id = null, + /** + * IDs of the common [entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list|null + */ + public array|null $common_acs_entrance_ids = null, + /** + * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is discarded. + */ + public bool|null $discarded = null, + /** + * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is expired. + */ + public bool|null $expired = null, + /** + * IDs of the guest [entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list|null + */ + public array|null $guest_acs_entrance_ids = null, + /** + * Number of issued cards associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public float|null $number_of_issued_cards = null, + /** + * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is overridden. + */ + public bool|null $overridden = null, + /** + * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is overwritten. + */ + public bool|null $overwritten = null, + /** + * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is pending auto-update. + */ + public bool|null $pending_auto_update = null, + ) {} + } } -/** - * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class ActionAttemptAssaAbloyVostioMetadata -{ - public static function from_json( - mixed $json, - ): ActionAttemptAssaAbloyVostioMetadata|null { - if (!$json) { - return null; - } - return new self( - auto_join: $json->auto_join ?? null, - door_names: $json->door_names ?? null, - endpoint_id: $json->endpoint_id ?? null, - key_id: $json->key_id ?? null, - key_issuing_request_id: $json->key_issuing_request_id ?? null, - override_guest_acs_entrance_ids: $json->override_guest_acs_entrance_ids ?? - null, - ); - } - - public function __construct( - /** - * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. - */ - public bool|null $auto_join, - /** - * Names of the doors to which to grant access in the Vostio access system. - */ - public array|null $door_names, - /** - * Endpoint ID in the Vostio access system. - */ - public string|null $endpoint_id, - /** - * Key ID in the Vostio access system. - */ - public string|null $key_id, - /** - * Key issuing request ID in the Vostio access system. - */ - public string|null $key_issuing_request_id, - /** - * IDs of the guest entrances to override in the Vostio access system. - */ - public array|null $override_guest_acs_entrance_ids, - ) {} +namespace Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnEncoder\VisionlineMetadata { + enum CardFormat: string + { + case TL_CODE = "TLCode"; + case RFID48 = "rfid48"; + } } -/** - * Error associated with the action. - */ -class ActionAttemptError -{ - public static function from_json(mixed $json): ActionAttemptError|null - { - if (!$json) { - return null; - } - return new self( - message: $json->message ?? null, - type: $json->type ?? null, - ); - } - - public function __construct( - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Type of the error. - */ - public string|null $type, - ) {} +namespace Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam { + /** + * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class AkilesMetadata + { + public static function from_json(mixed $json): AkilesMetadata|null + { + if (!$json) { + return null; + } + return new self(member_pin_id: $json->member_pin_id ?? null); + } + + public function __construct( + /** + * ID of the Akiles member PIN. + */ + public string|null $member_pin_id = null, + ) {} + } + + /** + * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class AssaAbloyVostioMetadata + { + public static function from_json( + mixed $json, + ): AssaAbloyVostioMetadata|null { + if (!$json) { + return null; + } + return new self( + auto_join: $json->auto_join ?? null, + door_names: $json->door_names ?? null, + endpoint_id: $json->endpoint_id ?? null, + key_id: $json->key_id ?? null, + key_issuing_request_id: $json->key_issuing_request_id ?? null, + override_guest_acs_entrance_ids: $json->override_guest_acs_entrance_ids ?? + null, + ); + } + + public function __construct( + /** + * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. + */ + public bool|null $auto_join = null, + /** + * Names of the doors to which to grant access in the Vostio access system. + * + * @var list|null + */ + public array|null $door_names = null, + /** + * Endpoint ID in the Vostio access system. + */ + public string|null $endpoint_id = null, + /** + * Key ID in the Vostio access system. + */ + public string|null $key_id = null, + /** + * Key issuing request ID in the Vostio access system. + */ + public string|null $key_issuing_request_id = null, + /** + * IDs of the guest entrances to override in the Vostio access system. + * + * @var list|null + */ + public array|null $override_guest_acs_entrance_ids = null, + ) {} + } + + /** + * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + public string|null $error_code, + public string|null $message, + ) {} + } + + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class VisionlineMetadata + { + public static function from_json(mixed $json): VisionlineMetadata|null + { + if (!$json) { + return null; + } + return new self( + auto_join: $json->auto_join ?? null, + card_function_type: $json->card_function_type ?? null, + card_id: $json->card_id ?? null, + common_acs_entrance_ids: $json->common_acs_entrance_ids ?? null, + credential_id: $json->credential_id ?? null, + guest_acs_entrance_ids: $json->guest_acs_entrance_ids ?? null, + is_valid: $json->is_valid ?? null, + joiner_acs_credential_ids: $json->joiner_acs_credential_ids ?? + null, + ); + } + + public function __construct( + /** + * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. + */ + public bool|null $auto_join = null, + /** + * Card function type in the Visionline access system. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\VisionlineMetadata\CardFunctionType>|string|null + */ + public string|null $card_function_type = null, + /** + * ID of the card in the Visionline access system. + */ + public string|null $card_id = null, + /** + * Common entrance IDs in the Visionline access system. + * + * @var list|null + */ + public array|null $common_acs_entrance_ids = null, + /** + * ID of the credential in the Visionline access system. + */ + public string|null $credential_id = null, + /** + * Guest entrance IDs in the Visionline access system. + * + * @var list|null + */ + public array|null $guest_acs_entrance_ids = null, + /** + * Indicates whether the credential is valid. + */ + public bool|null $is_valid = null, + /** + * IDs of the credentials to which you want to join. + * + * @var list|null + */ + public array|null $joiner_acs_credential_ids = null, + ) {} + } + + /** + * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + new_code: $json->new_code ?? null, + original_code: $json->original_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + /** + * The PIN code that was assigned instead. + */ + public string|null $new_code = null, + /** + * The originally requested PIN code that could not be used. + */ + public string|null $original_code = null, + ) {} + } + + enum AccessMethod: string + { + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; + } + + enum ExternalType: string + { + case PTI_CARD = "pti_card"; + case BRIVO_CREDENTIAL = "brivo_credential"; + case HID_CREDENTIAL = "hid_credential"; + case VISIONLINE_CARD = "visionline_card"; + case SALTO_KS_CREDENTIAL = "salto_ks_credential"; + case ASSA_ABLOY_VOSTIO_KEY = "assa_abloy_vostio_key"; + case SALTO_SPACE_KEY = "salto_space_key"; + case LATCH_ACCESS = "latch_access"; + case DORMAKABA_AMBIANCE_CREDENTIAL = "dormakaba_ambiance_credential"; + case HOTEK_CARD = "hotek_card"; + case SALTO_KS_TAG = "salto_ks_tag"; + case AVIGILON_ALTA_CREDENTIAL = "avigilon_alta_credential"; + case KISI_CREDENTIAL = "kisi_credential"; + case AKILES_CREDENTIAL = "akiles_credential"; + } } -/** - * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class ActionAttemptErrors -{ - public static function from_json(mixed $json): ActionAttemptErrors|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - public string|null $error_code, - public string|null $message, - ) {} +namespace Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\VisionlineMetadata { + enum CardFunctionType: string + { + case GUEST = "guest"; + case STAFF = "staff"; + } } -/** - * Previous access time configuration. - */ -class ActionAttemptFrom -{ - public static function from_json(mixed $json): ActionAttemptFrom|null - { - if (!$json) { - return null; - } - return new self( - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * Previous end time for access. - */ - public string|null $ends_at, - /** - * Previous start time for access. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam\Warnings { + enum WarningCode: string + { + case WAITING_TO_BE_ISSUED = "waiting_to_be_issued"; + case SCHEDULE_EXTERNALLY_MODIFIED = "schedule_externally_modified"; + case SCHEDULE_MODIFIED = "schedule_modified"; + case BEING_DELETED = "being_deleted"; + case UNKNOWN_ISSUE_WITH_ACS_CREDENTIAL = "unknown_issue_with_acs_credential"; + case NEEDS_TO_BE_REISSUED = "needs_to_be_reissued"; + case REQUESTED_CODE_UNAVAILABLE = "requested_code_unavailable"; + } } -/** - * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. - */ -class ActionAttemptPendingMutations -{ - public static function from_json( - mixed $json, - ): ActionAttemptPendingMutations|null { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - from: isset($json->from) - ? ActionAttemptFrom::from_json($json->from) - : null, - message: $json->message ?? null, - mutation_code: $json->mutation_code ?? null, - to: isset($json->to) ? ActionAttemptTo::from_json($json->to) : null, - ); - } - - public function __construct( - /** - * Date and time at which the mutation was created. - */ - public string|null $created_at, - /** - * Previous access time configuration. - */ - public ActionAttemptFrom|null $from, - /** - * Detailed description of the mutation. - */ - public string|null $message, - /** - * Mutation code to indicate that Seam is in the process of updating the access times for this access method. - */ - public string|null $mutation_code, - /** - * New access time configuration. - */ - public ActionAttemptTo|null $to, - ) {} +namespace Seam\Resources\ActionAttempt\ScanCredential\Result\Warnings { + enum WarningCode: string + { + case ACS_CREDENTIAL_ON_ENCODER_OUT_OF_SYNC = "acs_credential_on_encoder_out_of_sync"; + case ACS_CREDENTIAL_ON_SEAM_NOT_FOUND = "acs_credential_on_seam_not_found"; + } } -/** - * Result of the action. - */ -class ActionAttemptResult -{ - public static function from_json(mixed $json): ActionAttemptResult|null - { - if (!$json) { - return null; - } - return new self( - access_code: $json->access_code ?? null, - access_method: $json->access_method ?? null, - access_method_id: $json->access_method_id ?? null, - acs_credential_id: $json->acs_credential_id ?? null, - acs_credential_on_encoder: isset($json->acs_credential_on_encoder) - ? ActionAttemptAcsCredentialOnEncoder::from_json( - $json->acs_credential_on_encoder, - ) - : null, - acs_credential_on_seam: isset($json->acs_credential_on_seam) - ? ActionAttemptAcsCredentialOnSeam::from_json( - $json->acs_credential_on_seam, - ) - : null, - acs_credential_pool_id: $json->acs_credential_pool_id ?? null, - acs_system_id: $json->acs_system_id ?? null, - acs_user_id: $json->acs_user_id ?? null, - akiles_metadata: isset($json->akiles_metadata) - ? ActionAttemptAkilesMetadata::from_json($json->akiles_metadata) - : null, - assa_abloy_vostio_metadata: isset($json->assa_abloy_vostio_metadata) - ? ActionAttemptAssaAbloyVostioMetadata::from_json( +namespace Seam\Resources\ActionAttempt\EncodeCredential { + /** + * Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self( + access_method: $json->access_method ?? null, + acs_credential_id: $json->acs_credential_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\ActionAttempt\EncodeCredential\Result\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_managed: $json->is_managed ?? null, + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\ActionAttempt\EncodeCredential\Result\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + acs_credential_pool_id: $json->acs_credential_pool_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + akiles_metadata: isset($json->akiles_metadata) + ? \Seam\Resources\ActionAttempt\EncodeCredential\Result\AkilesMetadata::from_json( + $json->akiles_metadata, + ) + : null, + assa_abloy_vostio_metadata: isset( $json->assa_abloy_vostio_metadata, ) - : null, - card_number: $json->card_number ?? null, - client_session_token: $json->client_session_token ?? null, - code: $json->code ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - customization_profile_id: $json->customization_profile_id ?? null, - display_name: $json->display_name ?? null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => ActionAttemptErrors::from_json($e), - $json->errors ?? [], - ), - external_type: $json->external_type ?? null, - external_type_display_name: $json->external_type_display_name ?? - null, - instant_key_url: $json->instant_key_url ?? null, - is_assignment_required: $json->is_assignment_required ?? null, - is_encoding_required: $json->is_encoding_required ?? null, - is_issued: $json->is_issued ?? null, - is_latest_desired_state_synced_with_provider: $json->is_latest_desired_state_synced_with_provider ?? - null, - is_managed: $json->is_managed ?? null, - is_multi_phone_sync_credential: $json->is_multi_phone_sync_credential ?? - null, - is_one_time_use: $json->is_one_time_use ?? null, - is_ready_for_assignment: $json->is_ready_for_assignment ?? null, - is_ready_for_encoding: $json->is_ready_for_encoding ?? null, - issued_at: $json->issued_at ?? null, - latest_desired_state_synced_with_provider_at: $json->latest_desired_state_synced_with_provider_at ?? - null, - mode: $json->mode ?? null, - noise_threshold: $json->noise_threshold ?? null, - parent_acs_credential_id: $json->parent_acs_credential_id ?? null, - pending_mutations: array_map( - fn($p) => ActionAttemptPendingMutations::from_json($p), - $json->pending_mutations ?? [], - ), - starts_at: $json->starts_at ?? null, - user_identity_id: $json->user_identity_id ?? null, - visionline_metadata: isset($json->visionline_metadata) - ? ActionAttemptVisionlineMetadata::from_json( - $json->visionline_metadata, + ? \Seam\Resources\ActionAttempt\EncodeCredential\Result\AssaAbloyVostioMetadata::from_json( + $json->assa_abloy_vostio_metadata, + ) + : null, + card_number: $json->card_number ?? null, + code: $json->code ?? null, + ends_at: $json->ends_at ?? null, + external_type: $json->external_type ?? null, + external_type_display_name: $json->external_type_display_name ?? + null, + is_issued: $json->is_issued ?? null, + is_latest_desired_state_synced_with_provider: $json->is_latest_desired_state_synced_with_provider ?? + null, + is_multi_phone_sync_credential: $json->is_multi_phone_sync_credential ?? + null, + is_one_time_use: $json->is_one_time_use ?? null, + issued_at: $json->issued_at ?? null, + latest_desired_state_synced_with_provider_at: $json->latest_desired_state_synced_with_provider_at ?? + null, + parent_acs_credential_id: $json->parent_acs_credential_id ?? + null, + starts_at: $json->starts_at ?? null, + user_identity_id: $json->user_identity_id ?? null, + visionline_metadata: isset($json->visionline_metadata) + ? \Seam\Resources\ActionAttempt\EncodeCredential\Result\VisionlineMetadata::from_json( + $json->visionline_metadata, + ) + : null, + ); + } + + public function __construct( + /** + * Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\ActionAttempt\EncodeCredential\Result\AccessMethod>|string|null + */ + public string|null $access_method, + /** + * ID of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $acs_credential_id, + /** + * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $acs_system_id, + /** + * ID of the [connected account](https://docs.seam.co/core-concepts/connected-accounts) to which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $connected_account_id, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. + */ + public string|null $created_at, + /** + * Display name that corresponds to the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. + */ + public string|null $display_name, + /** + * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list<\Seam\Resources\ActionAttempt\EncodeCredential\Result\Errors> + */ + public array $errors, + public bool|null $is_managed, + /** + * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list<\Seam\Resources\ActionAttempt\EncodeCredential\Result\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $workspace_id, + /** + * ID of the credential pool to which the credential belongs. + */ + public string|null $acs_credential_pool_id = null, + /** + * ID of the [ACS user](https://docs.seam.co/low-level-apis/access-systems/user-management) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $acs_user_id = null, + /** + * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\EncodeCredential\Result\AkilesMetadata|null $akiles_metadata = null, + /** + * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\EncodeCredential\Result\AssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata = null, + /** + * Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $card_number = null, + /** + * Access (PIN) code for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $code = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. + */ + public string|null $ends_at = null, + /** + * Brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. Supported values: `pti_card`, `brivo_credential`, `hid_credential`, `visionline_card`. + * + * @var value-of<\Seam\Resources\ActionAttempt\EncodeCredential\Result\ExternalType>|string|null + */ + public string|null $external_type = null, + /** + * Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. + */ + public string|null $external_type_display_name = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been encoded onto a card. + */ + public bool|null $is_issued = null, + /** + * Indicates whether the latest state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been synced from Seam to the provider. + */ + public bool|null $is_latest_desired_state_synced_with_provider = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is a [multi-phone sync credential](https://docs.seam.co/capability-guides/mobile-access/issuing-mobile-credentials-from-an-access-control-system#what-are-multi-phone-sync-credentials). + */ + public bool|null $is_multi_phone_sync_credential = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) can only be used once. If `true`, the code becomes invalid after the first use. + */ + public bool|null $is_one_time_use = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was encoded onto a card. + */ + public string|null $issued_at = null, + /** + * Date and time at which the state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was most recently synced from Seam to the provider. + */ + public string|null $latest_desired_state_synced_with_provider_at = null, + /** + * ID of the parent [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $parent_acs_credential_id = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at = null, + /** + * ID of the [user identity](https://docs.seam.co/api/user_identities) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $user_identity_id = null, + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\EncodeCredential\Result\VisionlineMetadata|null $visionline_metadata = null, + ) {} + } +} + +namespace Seam\Resources\ActionAttempt\EncodeCredential\Result { + /** + * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class AkilesMetadata + { + public static function from_json(mixed $json): AkilesMetadata|null + { + if (!$json) { + return null; + } + return new self(member_pin_id: $json->member_pin_id ?? null); + } + + public function __construct( + /** + * ID of the Akiles member PIN. + */ + public string|null $member_pin_id = null, + ) {} + } + + /** + * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class AssaAbloyVostioMetadata + { + public static function from_json( + mixed $json, + ): AssaAbloyVostioMetadata|null { + if (!$json) { + return null; + } + return new self( + auto_join: $json->auto_join ?? null, + door_names: $json->door_names ?? null, + endpoint_id: $json->endpoint_id ?? null, + key_id: $json->key_id ?? null, + key_issuing_request_id: $json->key_issuing_request_id ?? null, + override_guest_acs_entrance_ids: $json->override_guest_acs_entrance_ids ?? + null, + ); + } + + public function __construct( + /** + * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. + */ + public bool|null $auto_join = null, + /** + * Names of the doors to which to grant access in the Vostio access system. + * + * @var list|null + */ + public array|null $door_names = null, + /** + * Endpoint ID in the Vostio access system. + */ + public string|null $endpoint_id = null, + /** + * Key ID in the Vostio access system. + */ + public string|null $key_id = null, + /** + * Key issuing request ID in the Vostio access system. + */ + public string|null $key_issuing_request_id = null, + /** + * IDs of the guest entrances to override in the Vostio access system. + * + * @var list|null + */ + public array|null $override_guest_acs_entrance_ids = null, + ) {} + } + + /** + * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + public string|null $error_code, + public string|null $message, + ) {} + } + + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class VisionlineMetadata + { + public static function from_json(mixed $json): VisionlineMetadata|null + { + if (!$json) { + return null; + } + return new self( + auto_join: $json->auto_join ?? null, + card_function_type: $json->card_function_type ?? null, + card_id: $json->card_id ?? null, + common_acs_entrance_ids: $json->common_acs_entrance_ids ?? null, + credential_id: $json->credential_id ?? null, + guest_acs_entrance_ids: $json->guest_acs_entrance_ids ?? null, + is_valid: $json->is_valid ?? null, + joiner_acs_credential_ids: $json->joiner_acs_credential_ids ?? + null, + ); + } + + public function __construct( + /** + * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. + */ + public bool|null $auto_join = null, + /** + * Card function type in the Visionline access system. + * + * @var value-of<\Seam\Resources\ActionAttempt\EncodeCredential\Result\VisionlineMetadata\CardFunctionType>|string|null + */ + public string|null $card_function_type = null, + /** + * ID of the card in the Visionline access system. + */ + public string|null $card_id = null, + /** + * Common entrance IDs in the Visionline access system. + * + * @var list|null + */ + public array|null $common_acs_entrance_ids = null, + /** + * ID of the credential in the Visionline access system. + */ + public string|null $credential_id = null, + /** + * Guest entrance IDs in the Visionline access system. + * + * @var list|null + */ + public array|null $guest_acs_entrance_ids = null, + /** + * Indicates whether the credential is valid. + */ + public bool|null $is_valid = null, + /** + * IDs of the credentials to which you want to join. + * + * @var list|null + */ + public array|null $joiner_acs_credential_ids = null, + ) {} + } + + /** + * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + new_code: $json->new_code ?? null, + original_code: $json->original_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ActionAttempt\EncodeCredential\Result\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + /** + * The PIN code that was assigned instead. + */ + public string|null $new_code = null, + /** + * The originally requested PIN code that could not be used. + */ + public string|null $original_code = null, + ) {} + } + + enum AccessMethod: string + { + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; + } + + enum ExternalType: string + { + case PTI_CARD = "pti_card"; + case BRIVO_CREDENTIAL = "brivo_credential"; + case HID_CREDENTIAL = "hid_credential"; + case VISIONLINE_CARD = "visionline_card"; + case SALTO_KS_CREDENTIAL = "salto_ks_credential"; + case ASSA_ABLOY_VOSTIO_KEY = "assa_abloy_vostio_key"; + case SALTO_SPACE_KEY = "salto_space_key"; + case LATCH_ACCESS = "latch_access"; + case DORMAKABA_AMBIANCE_CREDENTIAL = "dormakaba_ambiance_credential"; + case HOTEK_CARD = "hotek_card"; + case SALTO_KS_TAG = "salto_ks_tag"; + case AVIGILON_ALTA_CREDENTIAL = "avigilon_alta_credential"; + case KISI_CREDENTIAL = "kisi_credential"; + case AKILES_CREDENTIAL = "akiles_credential"; + } +} + +namespace Seam\Resources\ActionAttempt\EncodeCredential\Result\VisionlineMetadata { + enum CardFunctionType: string + { + case GUEST = "guest"; + case STAFF = "staff"; + } +} + +namespace Seam\Resources\ActionAttempt\EncodeCredential\Result\Warnings { + enum WarningCode: string + { + case WAITING_TO_BE_ISSUED = "waiting_to_be_issued"; + case SCHEDULE_EXTERNALLY_MODIFIED = "schedule_externally_modified"; + case SCHEDULE_MODIFIED = "schedule_modified"; + case BEING_DELETED = "being_deleted"; + case UNKNOWN_ISSUE_WITH_ACS_CREDENTIAL = "unknown_issue_with_acs_credential"; + case NEEDS_TO_BE_REISSUED = "needs_to_be_reissued"; + case REQUESTED_CODE_UNAVAILABLE = "requested_code_unavailable"; + } +} + +namespace Seam\Resources\ActionAttempt\ScanToAssignCredential { + /** + * Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self( + access_method: $json->access_method ?? null, + acs_credential_id: $json->acs_credential_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_managed: $json->is_managed ?? null, + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + acs_credential_pool_id: $json->acs_credential_pool_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + akiles_metadata: isset($json->akiles_metadata) + ? \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\AkilesMetadata::from_json( + $json->akiles_metadata, + ) + : null, + assa_abloy_vostio_metadata: isset( + $json->assa_abloy_vostio_metadata, ) - : null, - warnings: array_map( - fn($w) => ActionAttemptWarnings::from_json($w), - $json->warnings ?? [], - ), - was_confirmed_by_device: $json->was_confirmed_by_device ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Created access code. - */ - public mixed $access_code, - /** - * Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. - */ - public string|null $access_method, - /** - * ID of the access method. - */ - public string|null $access_method_id, - /** - * ID of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $acs_credential_id, - /** - * Snapshot of credential data read from the physical encoder. - */ - public ActionAttemptAcsCredentialOnEncoder|null $acs_credential_on_encoder, - /** - * Corresponding credential data as stored on Seam and the access system. - */ - public ActionAttemptAcsCredentialOnSeam|null $acs_credential_on_seam, - /** - * ID of the credential pool to which the credential belongs. - */ - public string|null $acs_credential_pool_id, - /** - * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $acs_system_id, - /** - * ID of the [ACS user](https://docs.seam.co/low-level-apis/access-systems/user-management) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $acs_user_id, - /** - * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public ActionAttemptAkilesMetadata|null $akiles_metadata, - /** - * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public ActionAttemptAssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata, - /** - * Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $card_number, - /** - * Token of the client session associated with the access method. - */ - public string|null $client_session_token, - /** - * Access (PIN) code for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $code, - /** - * ID of the [connected account](https://docs.seam.co/core-concepts/connected-accounts) to which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $connected_account_id, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. - */ - public string|null $created_at, - /** - * ID of the customization profile associated with the access method. - */ - public string|null $customization_profile_id, - /** - * Display name that corresponds to the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. - */ - public string|null $display_name, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. - */ - public string|null $ends_at, - /** - * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public array $errors, - /** - * Brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. Supported values: `pti_card`, `brivo_credential`, `hid_credential`, `visionline_card`. - */ - public string|null $external_type, - /** - * Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. - */ - public string|null $external_type_display_name, - /** - * URL of the Instant Key for mobile key access methods. - */ - public string|null $instant_key_url, - /** - * Indicates whether an existing card credential must be assigned to this access method before it can be issued. Only applies to card-mode access methods on systems that support credential assignment. - */ - public bool|null $is_assignment_required, - /** - * Indicates whether encoding with an card encoder is required to issue or reissue the plastic card associated with the access method. - */ - public bool|null $is_encoding_required, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been encoded onto a card. - */ - public bool|null $is_issued, - /** - * Indicates whether the latest state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been synced from Seam to the provider. - */ - public bool|null $is_latest_desired_state_synced_with_provider, - public bool|null $is_managed, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is a [multi-phone sync credential](https://docs.seam.co/capability-guides/mobile-access/issuing-mobile-credentials-from-an-access-control-system#what-are-multi-phone-sync-credentials). - */ - public bool|null $is_multi_phone_sync_credential, - /** - * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) can only be used once. If `true`, the code becomes invalid after the first use. - */ - public bool|null $is_one_time_use, - /** - * Indicates whether the access method is ready for card assignment. This is true when the access method is in card mode, has not yet been issued, and the system supports credential assignment. - */ - public bool|null $is_ready_for_assignment, - /** - * Indicates whether the access method is ready to be encoded. This is true when the credential has been created and the card has not yet been issued. - */ - public bool|null $is_ready_for_encoding, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was encoded onto a card. - */ - public string|null $issued_at, - /** - * Date and time at which the state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was most recently synced from Seam to the provider. - */ - public string|null $latest_desired_state_synced_with_provider_at, - /** - * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. - */ - public string|null $mode, - /** - * Created noise threshold. - */ - public mixed $noise_threshold, - /** - * ID of the parent [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $parent_acs_credential_id, - /** - * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. - */ - public array $pending_mutations, - /** - * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at, - /** - * ID of the [user identity](https://docs.seam.co/api/user_identities) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. - */ - public string|null $user_identity_id, - /** - * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public ActionAttemptVisionlineMetadata|null $visionline_metadata, - /** - * Warnings related to scanning the credential, such as mismatches between the credential data currently encoded on the card and the corresponding data stored on Seam and the access system. - */ - public array $warnings, - /** - * Indicates whether the device confirmed that the lock action occurred. - */ - public bool|null $was_confirmed_by_device, - /** - * ID of the workspace that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $workspace_id, - ) {} + ? \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\AssaAbloyVostioMetadata::from_json( + $json->assa_abloy_vostio_metadata, + ) + : null, + card_number: $json->card_number ?? null, + code: $json->code ?? null, + ends_at: $json->ends_at ?? null, + external_type: $json->external_type ?? null, + external_type_display_name: $json->external_type_display_name ?? + null, + is_issued: $json->is_issued ?? null, + is_latest_desired_state_synced_with_provider: $json->is_latest_desired_state_synced_with_provider ?? + null, + is_multi_phone_sync_credential: $json->is_multi_phone_sync_credential ?? + null, + is_one_time_use: $json->is_one_time_use ?? null, + issued_at: $json->issued_at ?? null, + latest_desired_state_synced_with_provider_at: $json->latest_desired_state_synced_with_provider_at ?? + null, + parent_acs_credential_id: $json->parent_acs_credential_id ?? + null, + starts_at: $json->starts_at ?? null, + user_identity_id: $json->user_identity_id ?? null, + visionline_metadata: isset($json->visionline_metadata) + ? \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\VisionlineMetadata::from_json( + $json->visionline_metadata, + ) + : null, + ); + } + + public function __construct( + /** + * Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\AccessMethod>|string|null + */ + public string|null $access_method, + /** + * ID of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $acs_credential_id, + /** + * ID of the [access control system](https://docs.seam.co/low-level-apis/access-systems) that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $acs_system_id, + /** + * ID of the [connected account](https://docs.seam.co/core-concepts/connected-accounts) to which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $connected_account_id, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was created. + */ + public string|null $created_at, + /** + * Display name that corresponds to the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. + */ + public string|null $display_name, + /** + * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list<\Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\Errors> + */ + public array $errors, + /** + * Indicates whether Seam manages the credential. + */ + public true|null $is_managed, + /** + * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + * + * @var list<\Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $workspace_id, + /** + * ID of the credential pool to which the credential belongs. + */ + public string|null $acs_credential_pool_id = null, + /** + * ID of the [ACS user](https://docs.seam.co/low-level-apis/access-systems/user-management) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $acs_user_id = null, + /** + * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\AkilesMetadata|null $akiles_metadata = null, + /** + * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\AssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata = null, + /** + * Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $card_number = null, + /** + * Access (PIN) code for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $code = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. + */ + public string|null $ends_at = null, + /** + * Brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. Supported values: `pti_card`, `brivo_credential`, `hid_credential`, `visionline_card`. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\ExternalType>|string|null + */ + public string|null $external_type = null, + /** + * Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. + */ + public string|null $external_type_display_name = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been encoded onto a card. + */ + public bool|null $is_issued = null, + /** + * Indicates whether the latest state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) has been synced from Seam to the provider. + */ + public bool|null $is_latest_desired_state_synced_with_provider = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is a [multi-phone sync credential](https://docs.seam.co/capability-guides/mobile-access/issuing-mobile-credentials-from-an-access-control-system#what-are-multi-phone-sync-credentials). + */ + public bool|null $is_multi_phone_sync_credential = null, + /** + * Indicates whether the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) can only be used once. If `true`, the code becomes invalid after the first use. + */ + public bool|null $is_one_time_use = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was encoded onto a card. + */ + public string|null $issued_at = null, + /** + * Date and time at which the state of the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was most recently synced from Seam to the provider. + */ + public string|null $latest_desired_state_synced_with_provider_at = null, + /** + * ID of the parent [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public string|null $parent_acs_credential_id = null, + /** + * Date and time at which the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) validity starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at = null, + /** + * ID of the [user identity](https://docs.seam.co/api/user_identities) to whom the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) belongs. + */ + public string|null $user_identity_id = null, + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + public \Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\VisionlineMetadata|null $visionline_metadata = null, + ) {} + } +} + +namespace Seam\Resources\ActionAttempt\ScanToAssignCredential\Result { + /** + * Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class AkilesMetadata + { + public static function from_json(mixed $json): AkilesMetadata|null + { + if (!$json) { + return null; + } + return new self(member_pin_id: $json->member_pin_id ?? null); + } + + public function __construct( + /** + * ID of the Akiles member PIN. + */ + public string|null $member_pin_id = null, + ) {} + } + + /** + * Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class AssaAbloyVostioMetadata + { + public static function from_json( + mixed $json, + ): AssaAbloyVostioMetadata|null { + if (!$json) { + return null; + } + return new self( + auto_join: $json->auto_join ?? null, + door_names: $json->door_names ?? null, + endpoint_id: $json->endpoint_id ?? null, + key_id: $json->key_id ?? null, + key_issuing_request_id: $json->key_issuing_request_id ?? null, + override_guest_acs_entrance_ids: $json->override_guest_acs_entrance_ids ?? + null, + ); + } + + public function __construct( + /** + * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. + */ + public bool|null $auto_join = null, + /** + * Names of the doors to which to grant access in the Vostio access system. + * + * @var list|null + */ + public array|null $door_names = null, + /** + * Endpoint ID in the Vostio access system. + */ + public string|null $endpoint_id = null, + /** + * Key ID in the Vostio access system. + */ + public string|null $key_id = null, + /** + * Key issuing request ID in the Vostio access system. + */ + public string|null $key_issuing_request_id = null, + /** + * IDs of the guest entrances to override in the Vostio access system. + * + * @var list|null + */ + public array|null $override_guest_acs_entrance_ids = null, + ) {} + } + + /** + * Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + public string|null $error_code, + public string|null $message, + ) {} + } + + /** + * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class VisionlineMetadata + { + public static function from_json(mixed $json): VisionlineMetadata|null + { + if (!$json) { + return null; + } + return new self( + auto_join: $json->auto_join ?? null, + card_function_type: $json->card_function_type ?? null, + card_id: $json->card_id ?? null, + common_acs_entrance_ids: $json->common_acs_entrance_ids ?? null, + credential_id: $json->credential_id ?? null, + guest_acs_entrance_ids: $json->guest_acs_entrance_ids ?? null, + is_valid: $json->is_valid ?? null, + joiner_acs_credential_ids: $json->joiner_acs_credential_ids ?? + null, + ); + } + + public function __construct( + /** + * Indicates whether the credential should auto-join. For an auto-join credential, Seam automatically issues an override card if there are no other cards and a joiner card if there are existing cards on the doors. + */ + public bool|null $auto_join = null, + /** + * Card function type in the Visionline access system. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\VisionlineMetadata\CardFunctionType>|string|null + */ + public string|null $card_function_type = null, + /** + * ID of the card in the Visionline access system. + */ + public string|null $card_id = null, + /** + * Common entrance IDs in the Visionline access system. + * + * @var list|null + */ + public array|null $common_acs_entrance_ids = null, + /** + * ID of the credential in the Visionline access system. + */ + public string|null $credential_id = null, + /** + * Guest entrance IDs in the Visionline access system. + * + * @var list|null + */ + public array|null $guest_acs_entrance_ids = null, + /** + * Indicates whether the credential is valid. + */ + public bool|null $is_valid = null, + /** + * IDs of the credentials to which you want to join. + * + * @var list|null + */ + public array|null $joiner_acs_credential_ids = null, + ) {} + } + + /** + * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + new_code: $json->new_code ?? null, + original_code: $json->original_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + /** + * The PIN code that was assigned instead. + */ + public string|null $new_code = null, + /** + * The originally requested PIN code that could not be used. + */ + public string|null $original_code = null, + ) {} + } + + enum AccessMethod: string + { + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; + } + + enum ExternalType: string + { + case PTI_CARD = "pti_card"; + case BRIVO_CREDENTIAL = "brivo_credential"; + case HID_CREDENTIAL = "hid_credential"; + case VISIONLINE_CARD = "visionline_card"; + case SALTO_KS_CREDENTIAL = "salto_ks_credential"; + case ASSA_ABLOY_VOSTIO_KEY = "assa_abloy_vostio_key"; + case SALTO_SPACE_KEY = "salto_space_key"; + case LATCH_ACCESS = "latch_access"; + case DORMAKABA_AMBIANCE_CREDENTIAL = "dormakaba_ambiance_credential"; + case HOTEK_CARD = "hotek_card"; + case SALTO_KS_TAG = "salto_ks_tag"; + case AVIGILON_ALTA_CREDENTIAL = "avigilon_alta_credential"; + case KISI_CREDENTIAL = "kisi_credential"; + case AKILES_CREDENTIAL = "akiles_credential"; + } +} + +namespace Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\VisionlineMetadata { + enum CardFunctionType: string + { + case GUEST = "guest"; + case STAFF = "staff"; + } +} + +namespace Seam\Resources\ActionAttempt\ScanToAssignCredential\Result\Warnings { + enum WarningCode: string + { + case WAITING_TO_BE_ISSUED = "waiting_to_be_issued"; + case SCHEDULE_EXTERNALLY_MODIFIED = "schedule_externally_modified"; + case SCHEDULE_MODIFIED = "schedule_modified"; + case BEING_DELETED = "being_deleted"; + case UNKNOWN_ISSUE_WITH_ACS_CREDENTIAL = "unknown_issue_with_acs_credential"; + case NEEDS_TO_BE_REISSUED = "needs_to_be_reissued"; + case REQUESTED_CODE_UNAVAILABLE = "requested_code_unavailable"; + } +} + +namespace Seam\Resources\ActionAttempt\AssignCredential { + /** + * Result of assigning a credential. If successful, includes the updated access method with the assigned credential. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self( + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\ActionAttempt\AssignCredential\Result\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_issued: $json->is_issued ?? null, + issued_at: $json->issued_at ?? null, + mode: $json->mode ?? null, + pending_mutations: array_map( + fn( + $p, + ) => \Seam\Resources\ActionAttempt\AssignCredential\Result\PendingMutations::from_json( + $p, + ), + $json->pending_mutations ?? [], + ), + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\ActionAttempt\AssignCredential\Result\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + client_session_token: $json->client_session_token ?? null, + code: $json->code ?? null, + customization_profile_id: $json->customization_profile_id ?? + null, + instant_key_url: $json->instant_key_url ?? null, + is_assignment_required: $json->is_assignment_required ?? null, + is_encoding_required: $json->is_encoding_required ?? null, + is_ready_for_assignment: $json->is_ready_for_assignment ?? null, + is_ready_for_encoding: $json->is_ready_for_encoding ?? null, + ); + } + + public function __construct( + /** + * ID of the access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the access method was created. + */ + public string|null $created_at, + /** + * Display name of the access method. + */ + public string|null $display_name, + /** + * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). + * + * @var list<\Seam\Resources\ActionAttempt\AssignCredential\Result\Errors> + */ + public array $errors, + /** + * Indicates whether the access method has been issued. + */ + public bool|null $is_issued, + /** + * Date and time at which the access method was issued. + */ + public string|null $issued_at, + /** + * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\ActionAttempt\AssignCredential\Result\Mode>|string|null + */ + public string|null $mode, + /** + * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. + * + * @var list<\Seam\Resources\ActionAttempt\AssignCredential\Result\PendingMutations> + */ + public array $pending_mutations, + /** + * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). + * + * @var list<\Seam\Resources\ActionAttempt\AssignCredential\Result\Warnings> + */ + public array $warnings, + /** + * ID of the Seam workspace associated with the access method. + */ + public string|null $workspace_id, + /** + * Token of the client session associated with the access method. + */ + public string|null $client_session_token = null, + /** + * The actual PIN code for code access methods. + */ + public string|null $code = null, + /** + * ID of the customization profile associated with the access method. + */ + public string|null $customization_profile_id = null, + /** + * URL of the Instant Key for mobile key access methods. + */ + public string|null $instant_key_url = null, + /** + * Indicates whether an existing card credential must be assigned to this access method before it can be issued. Only applies to card-mode access methods on systems that support credential assignment. + */ + public bool|null $is_assignment_required = null, + /** + * Indicates whether encoding with an card encoder is required to issue or reissue the plastic card associated with the access method. + */ + public bool|null $is_encoding_required = null, + /** + * Indicates whether the access method is ready for card assignment. This is true when the access method is in card mode, has not yet been issued, and the system supports credential assignment. + */ + public bool|null $is_ready_for_assignment = null, + /** + * Indicates whether the access method is ready to be encoded. This is true when the credential has been created and the card has not yet been issued. + */ + public bool|null $is_ready_for_encoding = null, + ) {} + } +} + +namespace Seam\Resources\ActionAttempt\AssignCredential\Result { + /** + * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ActionAttempt\AssignCredential\Result\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. + */ + class PendingMutations + { + public static function from_json(mixed $json): PendingMutations|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\ActionAttempt\AssignCredential\Result\PendingMutations\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\ActionAttempt\AssignCredential\Result\PendingMutations\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + public string|null $created_at, + /** + * Previous access time configuration. + */ + public \Seam\Resources\ActionAttempt\AssignCredential\Result\PendingMutations\From|null $from, + /** + * Detailed description of the mutation. + */ + public string|null $message, + /** + * Mutation code to indicate that Seam is in the process of updating the access times for this access method. + * + * @var value-of<\Seam\Resources\ActionAttempt\AssignCredential\Result\PendingMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + /** + * New access time configuration. + */ + public \Seam\Resources\ActionAttempt\AssignCredential\Result\PendingMutations\To|null $to, + ) {} + } + + /** + * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + original_access_method_id: $json->original_access_method_id ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ActionAttempt\AssignCredential\Result\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + /** + * ID of the original access method from which this backup access method was split, if applicable. + */ + public string|null $original_access_method_id = null, + ) {} + } + + enum Mode: string + { + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; + } } -/** - * New access time configuration. - */ -class ActionAttemptTo -{ - public static function from_json(mixed $json): ActionAttemptTo|null - { - if (!$json) { - return null; - } - return new self( - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * New end time for access. - */ - public string|null $ends_at, - /** - * New start time for access. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\ActionAttempt\AssignCredential\Result\Errors { + enum ErrorCode: string + { + case FAILED_TO_ISSUE = "failed_to_issue"; + } } -/** - * Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class ActionAttemptVisionlineMetadata -{ - public static function from_json( - mixed $json, - ): ActionAttemptVisionlineMetadata|null { - if (!$json) { - return null; - } - return new self( - cancelled: $json->cancelled ?? null, - card_format: $json->card_format ?? null, - card_holder: $json->card_holder ?? null, - card_id: $json->card_id ?? null, - common_acs_entrance_ids: $json->common_acs_entrance_ids ?? null, - discarded: $json->discarded ?? null, - expired: $json->expired ?? null, - guest_acs_entrance_ids: $json->guest_acs_entrance_ids ?? null, - number_of_issued_cards: $json->number_of_issued_cards ?? null, - overridden: $json->overridden ?? null, - overwritten: $json->overwritten ?? null, - pending_auto_update: $json->pending_auto_update ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is cancelled. - */ - public bool|null $cancelled, - /** - * Format of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $card_format, - /** - * Holder of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $card_holder, - /** - * Card ID for the Visionline card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public string|null $card_id, - /** - * IDs of the common [entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public array|null $common_acs_entrance_ids, - /** - * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is discarded. - */ - public bool|null $discarded, - /** - * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is expired. - */ - public bool|null $expired, - /** - * IDs of the guest [entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public array|null $guest_acs_entrance_ids, - /** - * Number of issued cards associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ - public float|null $number_of_issued_cards, - /** - * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is overridden. - */ - public bool|null $overridden, - /** - * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is overwritten. - */ - public bool|null $overwritten, - /** - * Indicates whether the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) is pending auto-update. - */ - public bool|null $pending_auto_update, - ) {} +namespace Seam\Resources\ActionAttempt\AssignCredential\Result\PendingMutations { + /** + * Previous access time configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Previous end time for access. + */ + public string|null $ends_at, + /** + * Previous start time for access. + */ + public string|null $starts_at, + ) {} + } + + /** + * New access time configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * New end time for access. + */ + public string|null $ends_at, + /** + * New start time for access. + */ + public string|null $starts_at, + ) {} + } + + enum MutationCode: string + { + case PROVISIONING_ACCESS = "provisioning_access"; + case REVOKING_ACCESS = "revoking_access"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + } } -/** - * Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). - */ -class ActionAttemptWarnings -{ - public static function from_json(mixed $json): ActionAttemptWarnings|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\ActionAttempt\AssignCredential\Result\Warnings { + enum WarningCode: string + { + case BEING_DELETED = "being_deleted"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + case PULLED_BACKUP_ACCESS_CODE = "pulled_backup_access_code"; + case DELAY_IN_ISSUING = "delay_in_issuing"; + } +} + +namespace Seam\Resources\ActionAttempt\CreateAccessCode { + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self(access_code: $json->access_code ?? null); + } + + public function __construct( + /** + * Created access code. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $access_code, + ) {} + } +} + +namespace Seam\Resources\ActionAttempt\UpdateAccessCode { + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self(access_code: $json->access_code ?? null); + } + + public function __construct( + /** + * Updated access code. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $access_code = null, + ) {} + } +} + +namespace Seam\Resources\ActionAttempt\CreateNoiseThreshold { + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self(noise_threshold: $json->noise_threshold ?? null); + } + + public function __construct( + /** + * Created noise threshold. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $noise_threshold, + ) {} + } +} + +namespace Seam\Resources\ActionAttempt\UpdateNoiseThreshold { + /** + * Result of the action. Null while the action attempt is pending or when this value does not apply. + */ + class Result + { + public static function from_json(mixed $json): Result|null + { + if (!$json) { + return null; + } + return new self(noise_threshold: $json->noise_threshold ?? null); + } + + public function __construct( + /** + * Updated noise threshold. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $noise_threshold, + ) {} + } } diff --git a/src/Resources/Batch.php b/src/Resources/Batch.php index 1b32ca77..7f5c92a0 100644 --- a/src/Resources/Batch.php +++ b/src/Resources/Batch.php @@ -1,218 +1,219 @@ access_codes ?? null, + access_grants: $json->access_grants ?? null, + access_methods: $json->access_methods ?? null, + acs_access_groups: $json->acs_access_groups ?? null, + acs_credentials: $json->acs_credentials ?? null, + acs_encoders: $json->acs_encoders ?? null, + acs_entrances: $json->acs_entrances ?? null, + acs_systems: $json->acs_systems ?? null, + acs_users: $json->acs_users ?? null, + action_attempts: $json->action_attempts ?? null, + client_sessions: $json->client_sessions ?? null, + connect_webviews: $json->connect_webviews ?? null, + connected_accounts: $json->connected_accounts ?? null, + devices: $json->devices ?? null, + events: $json->events ?? null, + instant_keys: $json->instant_keys ?? null, + noise_thresholds: $json->noise_thresholds ?? null, + spaces: $json->spaces ?? null, + thermostat_daily_programs: $json->thermostat_daily_programs ?? + null, + thermostat_schedules: $json->thermostat_schedules ?? null, + unmanaged_access_codes: $json->unmanaged_access_codes ?? null, + unmanaged_devices: $json->unmanaged_devices ?? null, + user_identities: $json->user_identities ?? null, + workspaces: $json->workspaces ?? null, + ); } - return new self( - access_codes: $json->access_codes ?? null, - access_grants: $json->access_grants ?? null, - access_methods: $json->access_methods ?? null, - acs_access_groups: $json->acs_access_groups ?? null, - acs_credentials: $json->acs_credentials ?? null, - acs_encoders: $json->acs_encoders ?? null, - acs_entrances: $json->acs_entrances ?? null, - acs_systems: $json->acs_systems ?? null, - acs_users: $json->acs_users ?? null, - action_attempts: $json->action_attempts ?? null, - client_sessions: $json->client_sessions ?? null, - connect_webviews: $json->connect_webviews ?? null, - connected_accounts: $json->connected_accounts ?? null, - devices: $json->devices ?? null, - events: $json->events ?? null, - instant_keys: $json->instant_keys ?? null, - noise_thresholds: $json->noise_thresholds ?? null, - spaces: $json->spaces ?? null, - thermostat_daily_programs: $json->thermostat_daily_programs ?? null, - thermostat_schedules: $json->thermostat_schedules ?? null, - unmanaged_access_codes: $json->unmanaged_access_codes ?? null, - unmanaged_devices: $json->unmanaged_devices ?? null, - user_identities: $json->user_identities ?? null, - workspaces: $json->workspaces ?? null, - ); - } - public function __construct( - /** - * Represents a smart lock [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - * - * An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. Using the Seam Access Code API, you can easily generate access codes on the hundreds of door lock models with which we integrate. - * - * Seam supports programming two types of access codes: [ongoing](https://docs.seam.co/low-level-apis/smart-locks/access-codes#ongoing-access-codes) and [time-bound](https://docs.seam.co/low-level-apis/smart-locks/access-codes#time-bound-access-codes). To differentiate between the two, refer to the `type` property of the access code. Ongoing codes display as `ongoing`, whereas time-bound codes are labeled `time_bound`. An ongoing access code is active, until it has been removed from the device. To specify an ongoing access code, leave both `starts_at` and `ends_at` empty. A time-bound access code will be programmed at the `starts_at` time and removed at the `ends_at` time. - * - * In addition, for certain devices, Seam also supports [offline access codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes#offline-access-codes). Offline access (PIN) codes are designed for door locks that might not always maintain an internet connection. For this type of access code, the device manufacturer uses encryption keys (tokens) to create server-based registries of algorithmically-generated offline PIN codes. Because the tokens remain synchronized with the managed devices, the locks do not require an active internet connection—and you do not need to be near the locks—to create an offline access code. Then, owners or managers can share these offline codes with users through a variety of mechanisms, such as messaging applications. That is, lock users do not need to install a smartphone application to receive an offline access code. - * - * For granting a person access to a space, [Access Grants](https://docs.seam.co/use-cases/granting-access) are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. - */ - public mixed $access_codes, - /** - * Represents an Access Grant. Access Grants enable you to grant a user identity access to spaces, entrances, and devices through one or more access methods, such as mobile keys, plastic cards, and PIN codes. You can create an Access Grant for an existing user identity, or you can create a new user identity *while* creating the new Access Grant. - */ - public mixed $access_grants, - /** - * Represents an access method for an Access Grant. Access methods describe the modes of access, such as PIN codes, plastic cards, and mobile keys. For a mobile key, the access method also stores the URL for the associated Instant Key. - */ - public mixed $access_methods, - /** - * Group that defines the entrances to which a set of users has access and, in some cases, the access schedule for these entrances and users. - * - * Some access control systems use [access group](https://docs.seam.co/low-level-apis/access-systems/user-management/assigning-users-to-access-groups), which are sets of users, combined with sets of permissions. These permissions include both the set of areas or assets that the users can access and the schedule during which the users can access these areas or assets. Instead of assigning access rights individually to each access control system user, which can be time-consuming and error-prone, administrators can assign users to an access group, thereby ensuring that the users inherit all the permissions associated with the access group. Using access groups streamlines the process of managing large numbers of access control system users, especially in bigger organizations or complexes. - * - * To learn whether your access control system supports access groups, see the corresponding [system integration guide](https://docs.seam.co/device-and-system-integration-guides#access-control-systems). - */ - public mixed $acs_access_groups, - /** - * Means by which an [access control system user](https://docs.seam.co/low-level-apis/access-systems/user-management) gains access at an [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). The `acs_credential` object represents a [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) that provides an ACS user access within an [access control system](https://docs.seam.co/low-level-apis/access-systems). - * - * An access control system generally uses digital means of access to authorize a user trying to get through a specific entrance. Examples of credentials include plastic key cards, mobile keys, biometric identifiers, and PIN codes. The electronic nature of these credentials, as well as the fact that access is centralized, enables both the rapid provisioning and rescinding of access and the ability to compile access audit logs. - * - * For each `acs_credential`, you define the access method. You can also specify additional properties, such as a PIN code, depending on the credential type. - * - * For granting a person access to a space, [Access Grants](https://docs.seam.co/use-cases/granting-access) are the default and recommended approach. Use the lower-level ACS credential API directly only when you specifically need to manage individual credentials. - */ - public mixed $acs_credentials, - /** - * Represents a hardware device that encodes [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) data onto physical cards within an [access control system](https://docs.seam.co/low-level-apis/access-systems). - * - * Some access control systems require credentials to be encoded onto plastic key cards using a card encoder. This process involves the following two key steps: - * - * 1. Credential creation - * Configure the access parameters for the credential. - * 2. Card encoding - * Write the credential data onto the card using a compatible card encoder. - * - * Separately, the Seam API also supports card scanning, which enables you to scan and read the encoded data on a card. You can use this action to confirm consistency with access control system records or diagnose discrepancies if needed. - * - * See [Working with Card Encoders and Scanners](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). - * - * To verify if your access control system requires a card encoder, see the corresponding [system integration guide](https://docs.seam.co/device-and-system-integration-guides#access-control-systems). - */ - public mixed $acs_encoders, - /** - * Represents an [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) within an [access control system](https://docs.seam.co/low-level-apis/access-systems). - * - * In an access control system, an entrance is a secured door, gate, zone, or other method of entry. You can list details for all the `acs_entrance` resources in your workspace or get these details for a specific `acs_entrance`. You can also list all entrances associated with a specific credential, and you can list all credentials associated with a specific entrance. - */ - public mixed $acs_entrances, - /** - * Represents an [access control system](https://docs.seam.co/low-level-apis/access-systems). - * - * Within an `acs_system`, create [`acs_user`s](https://docs.seam.co/api/acs/users/object) and [`acs_credential`s](https://docs.seam.co/api/acs/credentials/object) to grant access to the `acs_user`s. - * - * For details about the resources associated with an access control system, see the [access control systems namespace](https://docs.seam.co/api/acs). - */ - public mixed $acs_systems, - /** - * Represents a [user](https://docs.seam.co/low-level-apis/access-systems/user-management) in an [access system](https://docs.seam.co/low-level-apis/access-systems). - * - * An access system user typically refers to an individual who requires access, like an employee or resident. Each user can possess multiple credentials that serve as their keys or identifiers for access. The type of credential can vary widely. For example, in the Salto system, a user can have a PIN code, a mobile app account, and a fob. In other platforms, it is not uncommon for a user to have more than one of the same credential type, such as multiple key cards. Additionally, these credentials can have a schedule or validity period. - * - * For details about how to configure users in your access system, see the corresponding [system integration guide](https://docs.seam.co/device-and-system-integration-guides#access-control-systems). - */ - public mixed $acs_users, - /** - * Represents an action attempt that enables you to keep track of the progress of your action that affects a physical device or system.actions against a device. Action attempts are useful because the physical world is intrinsically asynchronous. - * - * When you request for a device to perform an action, the Seam API immediately returns an action attempt object. In the background, the Seam API performs the action. - * - * See also [Action Attempts](https://docs.seam.co/core-concepts/action-attempts). - */ - public mixed $action_attempts, - /** - * Represents a [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). If you want to restrict your users' access to their own devices, use client sessions. - * - * You create each client session with a custom `user_identifier_key`. Normally, the `user_identifier_key` is a user ID that your application provides. - * - * When calling the Seam API from your backend using an API key, you can pass the `user_identifier_key` as a parameter to limit results to the associated client session. For example, `/devices/list?user_identifier_key=123` only returns devices associated with the client session created with the `user_identifier_key` `123`. - * - * A client session has a token that you can use with the Seam JavaScript SDK to make requests from the client (browser) directly to the Seam API. The token restricts the user's access to only the devices that they own. - * - * See also [Get Started with React](https://docs.seam.co/ui-components/overview/getting-started-with-seam-components/get-started-with-react-components-and-client-session-tokens). - */ - public mixed $client_sessions, - /** - * Represents a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews). - * - * Connect Webviews are fully-embedded client-side components that you add to your app. Your users interact with your embedded Connect Webviews to link their IoT device or system accounts to Seam. That is, Connect Webviews walk your users through the process of logging in to their device or system accounts. Seam handles all the authentication steps, and—once your user has completed the authorization through your app—you can access and control their devices or systems using the Seam API. - * - * Connect Webviews perform credential validation, multifactor authentication (when applicable), and error handling for each brand that Seam supports. Further, Connect Webviews work across all modern browsers and platforms, including Chrome, Safari, and Firefox. - * - * To enable a user to connect their device or system account to Seam through your app, first create a `connect_webview`. Once created, this `connect_webview` includes a URL that you can use to open an [iframe](https://www.w3schools.com/html/html_iframe.asp) or new window containing the Connect Webview for your user. - * - * When you create a Connect Webview, specify the desired provider category key in the `provider_category` parameter. Alternately, to specify a list of providers explicitly, use the `accepted_providers` parameter with a list of device provider keys. - * - * To list all providers within a category, use `/devices/list_device_providers` with the desired `provider_category` filter. To list all provider keys, use `/devices/list_device_providers` with no filters. - */ - public mixed $connect_webviews, - /** - * Represents a [connected account](https://docs.seam.co/core-concepts/connected-accounts). A connected account is an external third-party account to which your user has authorized Seam to get access, for example, an August account with a list of door locks. - */ - public mixed $connected_accounts, - /** - * Represents a [device](https://docs.seam.co/core-concepts/devices) that has been connected to Seam. - */ - public mixed $devices, - /** - * Represents an event. Events let you know when something interesting happens in your workspace. For example, when a lock is unlocked, Seam creates a `lock.unlocked` event. When a device's battery level is low, Seam creates a `device.battery_low` event. - * - * As with other API resources, you can retrieve an individual event or a list of events. Seam also provides a separate webhook system for sending the event objects directly to an endpoint on your sever. Manage webhooks through [Seam Console](https://console.seam.co). You can also use the webhooks sandbox in Seam Console to see the different payloads for each event and test them against your own endpoints. - */ - public mixed $events, - /** - * Represents a Seam Instant Key. For issuing Bluetooth mobile keys, Instant Keys are the fastest way to share access. With a single API call, you can create a mobile key and send it through text or email or embed it in your own app. - * - * There’s no app to install, nor account to create. Your user just taps a link and gets a lightweight, native-feeling experience using iOS App Clip or Instant Apps on Android. Further, Instant Keys work offline, so even in areas with poor cellular or Wi-Fi, like elevator banks or concrete-walled hallways, the Instant Keys still work. - */ - public mixed $instant_keys, - /** - * Represents a [noise threshold](https://docs.seam.co/capability-guides/noise-sensors/configure-noise-threshold-settings) for a [noise sensor](https://docs.seam.co/capability-guides/noise-sensors). Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them. - */ - public mixed $noise_thresholds, - /** - * Represents a space that is a logical grouping of devices and entrances. You can assign access to an entire space, thereby making granting access more efficient. - */ - public mixed $spaces, - /** - * Represents a thermostat daily program, consisting of a set of periods, each of which has a starting time and the key that identifies the climate preset to apply at the starting time. - */ - public mixed $thermostat_daily_programs, - /** - * Represents a [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) that activates a configured [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) on a [thermostat](https://docs.seam.co/capability-guides/thermostats) at a specified starting time and deactivates the climate preset at a specified ending time. - */ - public mixed $thermostat_schedules, - /** - * Represents an [unmanaged smart lock access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes). - * - * An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. - * - * When you create an access code on a device in Seam, it is created as a managed access code. Access codes that exist on a device that were not created through Seam are considered unmanaged codes. We strictly limit the operations that can be performed on unmanaged codes. - * - * Prior to using Seam to manage your devices, you may have used another lock management system to manage the access codes on your devices. Where possible, we help you keep any existing access codes on devices and transition those codes to ones managed by your Seam workspace. - * - * Not all providers support unmanaged access codes. The following providers do not support unmanaged access codes: - * - * - [Kwikset](https://docs.seam.co/device-and-system-integration-guides/kwikset-locks) - */ - public mixed $unmanaged_access_codes, - /** - * Represents an [unmanaged device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices). An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any [access codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) on an unmanaged device are unmanaged. To control an unmanaged device with Seam, [convert it to a managed device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices#convert-an-unmanaged-device-to-managed). - */ - public mixed $unmanaged_devices, - /** - * Represents a [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) associated with an application user account. - */ - public mixed $user_identities, - /** - * Represents a Seam [workspace](https://docs.seam.co/core-concepts/workspaces). A workspace is a top-level entity that encompasses all other resources below it, such as devices, connected accounts, and Connect Webviews. Seam provides two types of workspaces. A [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces) is a special type of workspace designed for testing code. Sandbox workspaces offer test device accounts and virtual devices that you can connect and control. This ability to work with virtual devices is quite handy because it removes the need to own physical devices from multiple brands. To connect real devices and systems to Seam, use a [production workspace](https://docs.seam.co/core-concepts/workspaces#production-workspaces). - */ - public mixed $workspaces, - ) {} + public function __construct( + /** + * Represents a smart lock [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). + * + * An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. Using the Seam Access Code API, you can easily generate access codes on the hundreds of door lock models with which we integrate. + * + * Seam supports programming two types of access codes: [ongoing](https://docs.seam.co/low-level-apis/smart-locks/access-codes#ongoing-access-codes) and [time-bound](https://docs.seam.co/low-level-apis/smart-locks/access-codes#time-bound-access-codes). To differentiate between the two, refer to the `type` property of the access code. Ongoing codes display as `ongoing`, whereas time-bound codes are labeled `time_bound`. An ongoing access code is active, until it has been removed from the device. To specify an ongoing access code, leave both `starts_at` and `ends_at` empty. A time-bound access code will be programmed at the `starts_at` time and removed at the `ends_at` time. + * + * In addition, for certain devices, Seam also supports [offline access codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes#offline-access-codes). Offline access (PIN) codes are designed for door locks that might not always maintain an internet connection. For this type of access code, the device manufacturer uses encryption keys (tokens) to create server-based registries of algorithmically-generated offline PIN codes. Because the tokens remain synchronized with the managed devices, the locks do not require an active internet connection—and you do not need to be near the locks—to create an offline access code. Then, owners or managers can share these offline codes with users through a variety of mechanisms, such as messaging applications. That is, lock users do not need to install a smartphone application to receive an offline access code. + * + * For granting a person access to a space, [Access Grants](https://docs.seam.co/use-cases/granting-access) are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. + */ + public mixed $access_codes = null, + /** + * Represents an Access Grant. Access Grants enable you to grant a user identity access to spaces, entrances, and devices through one or more access methods, such as mobile keys, plastic cards, and PIN codes. You can create an Access Grant for an existing user identity, or you can create a new user identity *while* creating the new Access Grant. + */ + public mixed $access_grants = null, + /** + * Represents an access method for an Access Grant. Access methods describe the modes of access, such as PIN codes, plastic cards, and mobile keys. For a mobile key, the access method also stores the URL for the associated Instant Key. + */ + public mixed $access_methods = null, + /** + * Group that defines the entrances to which a set of users has access and, in some cases, the access schedule for these entrances and users. + * + * Some access control systems use [access group](https://docs.seam.co/low-level-apis/access-systems/user-management/assigning-users-to-access-groups), which are sets of users, combined with sets of permissions. These permissions include both the set of areas or assets that the users can access and the schedule during which the users can access these areas or assets. Instead of assigning access rights individually to each access control system user, which can be time-consuming and error-prone, administrators can assign users to an access group, thereby ensuring that the users inherit all the permissions associated with the access group. Using access groups streamlines the process of managing large numbers of access control system users, especially in bigger organizations or complexes. + * + * To learn whether your access control system supports access groups, see the corresponding [system integration guide](https://docs.seam.co/device-and-system-integration-guides#access-control-systems). + */ + public mixed $acs_access_groups = null, + /** + * Means by which an [access control system user](https://docs.seam.co/low-level-apis/access-systems/user-management) gains access at an [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). The `acs_credential` object represents a [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) that provides an ACS user access within an [access control system](https://docs.seam.co/low-level-apis/access-systems). + * + * An access control system generally uses digital means of access to authorize a user trying to get through a specific entrance. Examples of credentials include plastic key cards, mobile keys, biometric identifiers, and PIN codes. The electronic nature of these credentials, as well as the fact that access is centralized, enables both the rapid provisioning and rescinding of access and the ability to compile access audit logs. + * + * For each `acs_credential`, you define the access method. You can also specify additional properties, such as a PIN code, depending on the credential type. + * + * For granting a person access to a space, [Access Grants](https://docs.seam.co/use-cases/granting-access) are the default and recommended approach. Use the lower-level ACS credential API directly only when you specifically need to manage individual credentials. + */ + public mixed $acs_credentials = null, + /** + * Represents a hardware device that encodes [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) data onto physical cards within an [access control system](https://docs.seam.co/low-level-apis/access-systems). + * + * Some access control systems require credentials to be encoded onto plastic key cards using a card encoder. This process involves the following two key steps: + * + * 1. Credential creation + * Configure the access parameters for the credential. + * 2. Card encoding + * Write the credential data onto the card using a compatible card encoder. + * + * Separately, the Seam API also supports card scanning, which enables you to scan and read the encoded data on a card. You can use this action to confirm consistency with access control system records or diagnose discrepancies if needed. + * + * See [Working with Card Encoders and Scanners](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). + * + * To verify if your access control system requires a card encoder, see the corresponding [system integration guide](https://docs.seam.co/device-and-system-integration-guides#access-control-systems). + */ + public mixed $acs_encoders = null, + /** + * Represents an [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) within an [access control system](https://docs.seam.co/low-level-apis/access-systems). + * + * In an access control system, an entrance is a secured door, gate, zone, or other method of entry. You can list details for all the `acs_entrance` resources in your workspace or get these details for a specific `acs_entrance`. You can also list all entrances associated with a specific credential, and you can list all credentials associated with a specific entrance. + */ + public mixed $acs_entrances = null, + /** + * Represents an [access control system](https://docs.seam.co/low-level-apis/access-systems). + * + * Within an `acs_system`, create [`acs_user`s](https://docs.seam.co/api/acs/users/object) and [`acs_credential`s](https://docs.seam.co/api/acs/credentials/object) to grant access to the `acs_user`s. + * + * For details about the resources associated with an access control system, see the [access control systems namespace](https://docs.seam.co/api/acs). + */ + public mixed $acs_systems = null, + /** + * Represents a [user](https://docs.seam.co/low-level-apis/access-systems/user-management) in an [access system](https://docs.seam.co/low-level-apis/access-systems). + * + * An access system user typically refers to an individual who requires access, like an employee or resident. Each user can possess multiple credentials that serve as their keys or identifiers for access. The type of credential can vary widely. For example, in the Salto system, a user can have a PIN code, a mobile app account, and a fob. In other platforms, it is not uncommon for a user to have more than one of the same credential type, such as multiple key cards. Additionally, these credentials can have a schedule or validity period. + * + * For details about how to configure users in your access system, see the corresponding [system integration guide](https://docs.seam.co/device-and-system-integration-guides#access-control-systems). + */ + public mixed $acs_users = null, + /** + * Represents an action attempt that enables you to keep track of the progress of your action that affects a physical device or system.actions against a device. Action attempts are useful because the physical world is intrinsically asynchronous. + * + * When you request for a device to perform an action, the Seam API immediately returns an action attempt object. In the background, the Seam API performs the action. + * + * See also [Action Attempts](https://docs.seam.co/core-concepts/action-attempts). + */ + public mixed $action_attempts = null, + /** + * Represents a [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). If you want to restrict your users' access to their own devices, use client sessions. + * + * You create each client session with a custom `user_identifier_key`. Normally, the `user_identifier_key` is a user ID that your application provides. + * + * When calling the Seam API from your backend using an API key, you can pass the `user_identifier_key` as a parameter to limit results to the associated client session. For example, `/devices/list?user_identifier_key=123` only returns devices associated with the client session created with the `user_identifier_key` `123`. + * + * A client session has a token that you can use with the Seam JavaScript SDK to make requests from the client (browser) directly to the Seam API. The token restricts the user's access to only the devices that they own. + * + * See also [Get Started with React](https://docs.seam.co/ui-components/overview/getting-started-with-seam-components/get-started-with-react-components-and-client-session-tokens). + */ + public mixed $client_sessions = null, + /** + * Represents a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews). + * + * Connect Webviews are fully-embedded client-side components that you add to your app. Your users interact with your embedded Connect Webviews to link their IoT device or system accounts to Seam. That is, Connect Webviews walk your users through the process of logging in to their device or system accounts. Seam handles all the authentication steps, and—once your user has completed the authorization through your app—you can access and control their devices or systems using the Seam API. + * + * Connect Webviews perform credential validation, multifactor authentication (when applicable), and error handling for each brand that Seam supports. Further, Connect Webviews work across all modern browsers and platforms, including Chrome, Safari, and Firefox. + * + * To enable a user to connect their device or system account to Seam through your app, first create a `connect_webview`. Once created, this `connect_webview` includes a URL that you can use to open an [iframe](https://www.w3schools.com/html/html_iframe.asp) or new window containing the Connect Webview for your user. + * + * When you create a Connect Webview, specify the desired provider category key in the `provider_category` parameter. Alternately, to specify a list of providers explicitly, use the `accepted_providers` parameter with a list of device provider keys. + * + * To list all providers within a category, use `/devices/list_device_providers` with the desired `provider_category` filter. To list all provider keys, use `/devices/list_device_providers` with no filters. + */ + public mixed $connect_webviews = null, + /** + * Represents a [connected account](https://docs.seam.co/core-concepts/connected-accounts). A connected account is an external third-party account to which your user has authorized Seam to get access, for example, an August account with a list of door locks. + */ + public mixed $connected_accounts = null, + /** + * Represents a [device](https://docs.seam.co/core-concepts/devices) that has been connected to Seam. + */ + public mixed $devices = null, + /** + * Represents an event. Events let you know when something interesting happens in your workspace. For example, when a lock is unlocked, Seam creates a `lock.unlocked` event. When a device's battery level is low, Seam creates a `device.battery_low` event. + * + * As with other API resources, you can retrieve an individual event or a list of events. Seam also provides a separate webhook system for sending the event objects directly to an endpoint on your sever. Manage webhooks through [Seam Console](https://console.seam.co). You can also use the webhooks sandbox in Seam Console to see the different payloads for each event and test them against your own endpoints. + */ + public mixed $events = null, + /** + * Represents a Seam Instant Key. For issuing Bluetooth mobile keys, Instant Keys are the fastest way to share access. With a single API call, you can create a mobile key and send it through text or email or embed it in your own app. + * + * There’s no app to install, nor account to create. Your user just taps a link and gets a lightweight, native-feeling experience using iOS App Clip or Instant Apps on Android. Further, Instant Keys work offline, so even in areas with poor cellular or Wi-Fi, like elevator banks or concrete-walled hallways, the Instant Keys still work. + */ + public mixed $instant_keys = null, + /** + * Represents a [noise threshold](https://docs.seam.co/capability-guides/noise-sensors/configure-noise-threshold-settings) for a [noise sensor](https://docs.seam.co/capability-guides/noise-sensors). Thresholds represent the limits of noise tolerated at a property, which can be customized for each hour of the day. Each device has its own default thresholds, but you can use the Seam API to modify them. + */ + public mixed $noise_thresholds = null, + /** + * Represents a space that is a logical grouping of devices and entrances. You can assign access to an entire space, thereby making granting access more efficient. + */ + public mixed $spaces = null, + /** + * Represents a thermostat daily program, consisting of a set of periods, each of which has a starting time and the key that identifies the climate preset to apply at the starting time. + */ + public mixed $thermostat_daily_programs = null, + /** + * Represents a [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) that activates a configured [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) on a [thermostat](https://docs.seam.co/capability-guides/thermostats) at a specified starting time and deactivates the climate preset at a specified ending time. + */ + public mixed $thermostat_schedules = null, + /** + * Represents an [unmanaged smart lock access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes). + * + * An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. + * + * When you create an access code on a device in Seam, it is created as a managed access code. Access codes that exist on a device that were not created through Seam are considered unmanaged codes. We strictly limit the operations that can be performed on unmanaged codes. + * + * Prior to using Seam to manage your devices, you may have used another lock management system to manage the access codes on your devices. Where possible, we help you keep any existing access codes on devices and transition those codes to ones managed by your Seam workspace. + * + * Not all providers support unmanaged access codes. The following providers do not support unmanaged access codes: + * + * - [Kwikset](https://docs.seam.co/device-and-system-integration-guides/kwikset-locks) + */ + public mixed $unmanaged_access_codes = null, + /** + * Represents an [unmanaged device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices). An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any [access codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) on an unmanaged device are unmanaged. To control an unmanaged device with Seam, [convert it to a managed device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices#convert-an-unmanaged-device-to-managed). + */ + public mixed $unmanaged_devices = null, + /** + * Represents a [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) associated with an application user account. + */ + public mixed $user_identities = null, + /** + * Represents a Seam [workspace](https://docs.seam.co/core-concepts/workspaces). A workspace is a top-level entity that encompasses all other resources below it, such as devices, connected accounts, and Connect Webviews. Seam provides two types of workspaces. A [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces) is a special type of workspace designed for testing code. Sandbox workspaces offer test device accounts and virtual devices that you can connect and control. This ability to work with virtual devices is quite handy because it removes the need to own physical devices from multiple brands. To connect real devices and systems to Seam, use a [production workspace](https://docs.seam.co/core-concepts/workspaces#production-workspaces). + */ + public mixed $workspaces = null, + ) {} + } } diff --git a/src/Resources/ClientSession.php b/src/Resources/ClientSession.php index 92c77b9d..d9664152 100644 --- a/src/Resources/ClientSession.php +++ b/src/Resources/ClientSession.php @@ -1,91 +1,96 @@ client_session_id ?? null, + connect_webview_ids: $json->connect_webview_ids ?? null, + connected_account_ids: $json->connected_account_ids ?? null, + created_at: $json->created_at ?? null, + device_count: $json->device_count ?? null, + expires_at: $json->expires_at ?? null, + token: $json->token ?? null, + user_identifier_key: $json->user_identifier_key ?? null, + user_identity_ids: $json->user_identity_ids ?? null, + workspace_id: $json->workspace_id ?? null, + customer_key: $json->customer_key ?? null, + user_identity_id: $json->user_identity_id ?? null, + ); } - return new self( - client_session_id: $json->client_session_id ?? null, - connect_webview_ids: $json->connect_webview_ids ?? null, - connected_account_ids: $json->connected_account_ids ?? null, - created_at: $json->created_at ?? null, - customer_key: $json->customer_key ?? null, - device_count: $json->device_count ?? null, - expires_at: $json->expires_at ?? null, - token: $json->token ?? null, - user_identifier_key: $json->user_identifier_key ?? null, - user_identity_id: $json->user_identity_id ?? null, - user_identity_ids: $json->user_identity_ids ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * ID of the client session. - */ - public string|null $client_session_id, - /** - * IDs of the [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). - */ - public array|null $connect_webview_ids, - /** - * IDs of the [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). - */ - public array|null $connected_account_ids, - /** - * Date and time at which the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens) was created. - */ - public string|null $created_at, - /** - * Customer key associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). - */ - public string|null $customer_key, - /** - * Number of devices associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). - */ - public float|null $device_count, - /** - * Date and time at which the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens) expires. - */ - public string|null $expires_at, - /** - * Client session token associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). - */ - public string|null $token, - /** - * Your user ID for the user associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). - */ - public string|null $user_identifier_key, - /** - * ID of the [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) associated with the client session. - */ - public string|null $user_identity_id, - /** - * IDs of the [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) associated with the client session. - * - * @deprecated Use `user_identity_id` instead. - */ - public array|null $user_identity_ids, - /** - * ID of the workspace associated with the client session. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * ID of the client session. + */ + public string|null $client_session_id, + /** + * IDs of the [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). + * + * @var list|null + */ + public array|null $connect_webview_ids, + /** + * IDs of the [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). + * + * @var list|null + */ + public array|null $connected_account_ids, + /** + * Date and time at which the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens) was created. + */ + public string|null $created_at, + /** + * Number of devices associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). + */ + public float|null $device_count, + /** + * Date and time at which the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens) expires. + */ + public string|null $expires_at, + /** + * Client session token associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). + */ + public string|null $token, + /** + * Your user ID for the user associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). + */ + public string|null $user_identifier_key, + /** + * IDs of the [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) associated with the client session. + * + * @var list|null + * @deprecated Use `user_identity_id` instead. + */ + public array|null $user_identity_ids, + /** + * ID of the workspace associated with the client session. + */ + public string|null $workspace_id, + /** + * Customer key associated with the [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). + */ + public string|null $customer_key = null, + /** + * ID of the [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) associated with the client session. + */ + public string|null $user_identity_id = null, + ) {} + } } diff --git a/src/Resources/ConnectWebview.php b/src/Resources/ConnectWebview.php index 55a02bd2..82db3141 100644 --- a/src/Resources/ConnectWebview.php +++ b/src/Resources/ConnectWebview.php @@ -1,128 +1,155 @@ accepted_capabilities ?? null, + accepted_providers: $json->accepted_providers ?? null, + any_provider_allowed: $json->any_provider_allowed ?? null, + authorized_at: $json->authorized_at ?? null, + automatically_manage_new_devices: $json->automatically_manage_new_devices ?? + null, + connect_webview_id: $json->connect_webview_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + custom_metadata: $json->custom_metadata ?? null, + custom_redirect_failure_url: $json->custom_redirect_failure_url ?? + null, + custom_redirect_url: $json->custom_redirect_url ?? null, + device_selection_mode: $json->device_selection_mode ?? null, + login_successful: $json->login_successful ?? null, + selected_provider: $json->selected_provider ?? null, + status: $json->status ?? null, + url: $json->url ?? null, + wait_for_device_creation: $json->wait_for_device_creation ?? + null, + workspace_id: $json->workspace_id ?? null, + customer_key: $json->customer_key ?? null, + ); } - return new self( - accepted_capabilities: $json->accepted_capabilities ?? null, - accepted_providers: $json->accepted_providers ?? null, - any_provider_allowed: $json->any_provider_allowed ?? null, - authorized_at: $json->authorized_at ?? null, - automatically_manage_new_devices: $json->automatically_manage_new_devices ?? - null, - connect_webview_id: $json->connect_webview_id ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - custom_metadata: $json->custom_metadata ?? null, - custom_redirect_failure_url: $json->custom_redirect_failure_url ?? - null, - custom_redirect_url: $json->custom_redirect_url ?? null, - customer_key: $json->customer_key ?? null, - device_selection_mode: $json->device_selection_mode ?? null, - login_successful: $json->login_successful ?? null, - selected_provider: $json->selected_provider ?? null, - status: $json->status ?? null, - url: $json->url ?? null, - wait_for_device_creation: $json->wait_for_device_creation ?? null, - workspace_id: $json->workspace_id ?? null, - ); + + public function __construct( + /** + * High-level device capabilities that the Connect Webview can accept. When creating a Connect Webview, you can specify the types of devices that it can connect to Seam. If you do not set custom `accepted_capabilities`, Seam uses a default set of `accepted_capabilities` for each provider. For example, if you create a Connect Webview that accepts SmartThing devices, without specifying `accepted_capabilities`, Seam accepts only SmartThings locks. To connect SmartThings thermostats and locks to Seam, create a Connect Webview and include both `thermostat` and `lock` in the `accepted_capabilities`. + * + * @var list|null + */ + public array|null $accepted_capabilities, + /** + * List of accepted [provider keys](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-brands-to-display-in-your-connect-webviews). + * + * @var list|null + */ + public array|null $accepted_providers, + /** + * Indicates whether any provider is allowed. + */ + public bool|null $any_provider_allowed, + /** + * Date and time at which the user authorized (through the Connect Webview) the management of their devices. + */ + public string|null $authorized_at, + /** + * Indicates whether Seam should [import all new devices](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#automatically_manage_new_devices) for the connected account to make these devices available for use and management by the Seam API. + */ + public bool|null $automatically_manage_new_devices, + /** + * ID of the Connect Webview. + */ + public string|null $connect_webview_id, + /** + * ID of the connected account associated with the Connect Webview. + */ + public string|null $connected_account_id, + /** + * Date and time at which the Connect Webview was created. + */ + public string|null $created_at, + /** + * Set of key:value pairs. Adding custom metadata to a resource, such as a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview), [connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account), or [device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device), enables you to store custom information, like customer details or internal IDs from your application. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $custom_metadata, + /** + * URL to which the Connect Webview should redirect when an unexpected error occurs. + */ + public string|null $custom_redirect_failure_url, + /** + * URL to which the Connect Webview should redirect when the user successfully pairs a device or system. If you do not set the `custom_redirect_failure_url`, the Connect Webview redirects to the `custom_redirect_url` when an unexpected error occurs. + */ + public string|null $custom_redirect_url, + /** + * Device selection mode of the Connect Webview. Supported values: `none`, `single`, `multiple`. + * + * @var value-of<\Seam\Resources\ConnectWebview\DeviceSelectionMode>|string|null + */ + public string|null $device_selection_mode, + /** + * Indicates whether the user logged in successfully using the Connect Webview. + */ + public bool|null $login_successful, + /** + * Selected provider of the Connect Webview, one of the [provider keys](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-brands-to-display-in-your-connect-webviews). + */ + public string|null $selected_provider, + /** + * Status of the Connect Webview. `authorized` indicates that the user has successfully logged into their device or system account, thereby completing the Connect Webview. + * + * @var value-of<\Seam\Resources\ConnectWebview\Status>|string|null + */ + public string|null $status, + /** + * URL for the Connect Webview. You use the URL to display the Connect Webview flow to your user. + */ + public string|null $url, + /** + * Indicates whether Seam should [finish syncing all devices](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#wait_for_device_creation) in a newly-connected account before completing the associated Connect Webview. + */ + public bool|null $wait_for_device_creation, + /** + * ID of the workspace that contains the Connect Webview. + */ + public string|null $workspace_id, + /** + * The customer key associated with this webview, if any. + */ + public string|null $customer_key = null, + ) {} + } +} + +namespace Seam\Resources\ConnectWebview { + enum DeviceSelectionMode: string + { + case NONE = "none"; + case SINGLE = "single"; + case MULTIPLE = "multiple"; } - public function __construct( - /** - * High-level device capabilities that the Connect Webview can accept. When creating a Connect Webview, you can specify the types of devices that it can connect to Seam. If you do not set custom `accepted_capabilities`, Seam uses a default set of `accepted_capabilities` for each provider. For example, if you create a Connect Webview that accepts SmartThing devices, without specifying `accepted_capabilities`, Seam accepts only SmartThings locks. To connect SmartThings thermostats and locks to Seam, create a Connect Webview and include both `thermostat` and `lock` in the `accepted_capabilities`. - */ - public array|null $accepted_capabilities, - /** - * List of accepted [provider keys](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-brands-to-display-in-your-connect-webviews). - */ - public array|null $accepted_providers, - /** - * Indicates whether any provider is allowed. - */ - public bool|null $any_provider_allowed, - /** - * Date and time at which the user authorized (through the Connect Webview) the management of their devices. - */ - public string|null $authorized_at, - /** - * Indicates whether Seam should [import all new devices](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#automatically_manage_new_devices) for the connected account to make these devices available for use and management by the Seam API. - */ - public bool|null $automatically_manage_new_devices, - /** - * ID of the Connect Webview. - */ - public string|null $connect_webview_id, - /** - * ID of the connected account associated with the Connect Webview. - */ - public string|null $connected_account_id, - /** - * Date and time at which the Connect Webview was created. - */ - public string|null $created_at, - /** - * Set of key:value pairs. Adding custom metadata to a resource, such as a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview), [connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account), or [device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device), enables you to store custom information, like customer details or internal IDs from your application. - */ - public mixed $custom_metadata, - /** - * URL to which the Connect Webview should redirect when an unexpected error occurs. - */ - public string|null $custom_redirect_failure_url, - /** - * URL to which the Connect Webview should redirect when the user successfully pairs a device or system. If you do not set the `custom_redirect_failure_url`, the Connect Webview redirects to the `custom_redirect_url` when an unexpected error occurs. - */ - public string|null $custom_redirect_url, - /** - * The customer key associated with this webview, if any. - */ - public string|null $customer_key, - /** - * Device selection mode of the Connect Webview. Supported values: `none`, `single`, `multiple`. - */ - public string|null $device_selection_mode, - /** - * Indicates whether the user logged in successfully using the Connect Webview. - */ - public bool|null $login_successful, - /** - * Selected provider of the Connect Webview, one of the [provider keys](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-brands-to-display-in-your-connect-webviews). - */ - public string|null $selected_provider, - /** - * Status of the Connect Webview. `authorized` indicates that the user has successfully logged into their device or system account, thereby completing the Connect Webview. - */ - public string|null $status, - /** - * URL for the Connect Webview. You use the URL to display the Connect Webview flow to your user. - */ - public string|null $url, - /** - * Indicates whether Seam should [finish syncing all devices](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#wait_for_device_creation) in a newly-connected account before completing the associated Connect Webview. - */ - public bool|null $wait_for_device_creation, - /** - * ID of the workspace that contains the Connect Webview. - */ - public string|null $workspace_id, - ) {} + enum Status: string + { + case PENDING = "pending"; + case FAILED = "failed"; + case AUTHORIZED = "authorized"; + } } diff --git a/src/Resources/ConnectedAccount.php b/src/Resources/ConnectedAccount.php index 245d3c25..db0ceaf7 100644 --- a/src/Resources/ConnectedAccount.php +++ b/src/Resources/ConnectedAccount.php @@ -1,330 +1,1087 @@ accepted_capabilities ?? null, - account_type: $json->account_type ?? null, - account_type_display_name: $json->account_type_display_name ?? null, - automatically_manage_new_devices: $json->automatically_manage_new_devices ?? - null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - custom_metadata: $json->custom_metadata ?? null, - customer_key: $json->customer_key ?? null, - default_checkin_time: $json->default_checkin_time ?? null, - default_checkout_time: $json->default_checkout_time ?? null, - display_name: $json->display_name ?? null, - errors: array_map( - fn($e) => ConnectedAccountErrors::from_json($e), - $json->errors ?? [], - ), - ical_feed_origin: $json->ical_feed_origin ?? null, - ical_url: $json->ical_url ?? null, - image_url: $json->image_url ?? null, - time_zone: $json->time_zone ?? null, - user_identifier: isset($json->user_identifier) - ? ConnectedAccountUserIdentifier::from_json( - $json->user_identifier, - ) - : null, - warnings: array_map( - fn($w) => ConnectedAccountWarnings::from_json($w), - $json->warnings ?? [], - ), - ); - } + public static function from_json(mixed $json): ConnectedAccount|null + { + if (!$json) { + return null; + } + return new self( + accepted_capabilities: $json->accepted_capabilities ?? null, + account_type_display_name: $json->account_type_display_name ?? + null, + automatically_manage_new_devices: $json->automatically_manage_new_devices ?? + null, + connected_account_id: $json->connected_account_id ?? null, + custom_metadata: $json->custom_metadata ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\ConnectedAccount\Errors::from_json($e), + $json->errors ?? [], + ), + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\ConnectedAccount\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + account_type: $json->account_type ?? null, + created_at: $json->created_at ?? null, + customer_key: $json->customer_key ?? null, + default_checkin_time: $json->default_checkin_time ?? null, + default_checkout_time: $json->default_checkout_time ?? null, + ical_feed_origin: $json->ical_feed_origin ?? null, + ical_url: $json->ical_url ?? null, + image_url: $json->image_url ?? null, + time_zone: $json->time_zone ?? null, + user_identifier: isset($json->user_identifier) + ? \Seam\Resources\ConnectedAccount\UserIdentifier::from_json( + $json->user_identifier, + ) + : null, + ); + } - public function __construct( - /** - * List of capabilities that were accepted during the account connection process. - */ - public array|null $accepted_capabilities, - /** - * Type of connected account. - */ - public string|null $account_type, - /** - * Display name for the connected account type. - */ - public string|null $account_type_display_name, - /** - * Indicates whether Seam should [import all new devices](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#automatically_manage_new_devices) for the connected account to make these devices available for management by the Seam API. - */ - public bool|null $automatically_manage_new_devices, - /** - * ID of the connected account. - */ - public string|null $connected_account_id, - /** - * Date and time at which the connected account was created. - */ - public string|null $created_at, - /** - * Set of key:value pairs. Adding custom metadata to a resource, such as a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview), [connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account), or [device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device), enables you to store custom information, like customer details or internal IDs from your application. - */ - public mixed $custom_metadata, - /** - * Your unique key for the customer associated with this connected account. - */ - public string|null $customer_key, - /** - * Default reservation check-in time for this connected account, as `HH:mm` (24-hour). Sourced from the connector configuration — set during the connect_webview for providers like Lodgify whose API does not expose check-in times. - */ - public string|null $default_checkin_time, - /** - * Default reservation check-out time for this connected account, as `HH:mm` (24-hour). Sourced from the connector configuration. - */ - public string|null $default_checkout_time, - /** - * Display name for the connected account. - */ - public string|null $display_name, - /** - * Errors associated with the connected account. - */ - public array $errors, - /** - * For iCal connected accounts, the platform that produced the feed (for example, `airbnb`, `vrbo`, or `booking`), or `unknown` when it could not be determined. Intended for rendering the source platform's logo. - */ - public string|null $ical_feed_origin, - /** - * For iCal connected accounts, the feed URL for the connection. Sourced from the connector configuration. - */ - public string|null $ical_url, - /** - * Logo URL for the connected account provider. - */ - public string|null $image_url, - /** - * IANA time zone (e.g. America/Los_Angeles) for this connected account. Sourced from the connector configuration. - */ - public string|null $time_zone, - /** - * User identifier associated with the connected account. - * - * @deprecated Use `display_name` instead. - */ - public ConnectedAccountUserIdentifier|null $user_identifier, - /** - * Warnings associated with the connected account. - */ - public array $warnings, - ) {} + public function __construct( + /** + * List of capabilities that were accepted during the account connection process. + * + * @var list|null + */ + public array|null $accepted_capabilities, + /** + * Display name for the connected account type. + */ + public string|null $account_type_display_name, + /** + * Indicates whether Seam should [import all new devices](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#automatically_manage_new_devices) for the connected account to make these devices available for management by the Seam API. + */ + public bool|null $automatically_manage_new_devices, + /** + * ID of the connected account. + */ + public string|null $connected_account_id, + /** + * Set of key:value pairs. Adding custom metadata to a resource, such as a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview), [connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account), or [device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device), enables you to store custom information, like customer details or internal IDs from your application. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $custom_metadata, + /** + * Display name for the connected account. + */ + public string|null $display_name, + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\ConnectedAccount\Errors> + */ + public array $errors, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\ConnectedAccount\Warnings> + */ + public array $warnings, + /** + * Type of connected account. + */ + public string|null $account_type = null, + /** + * Date and time at which the connected account was created. + */ + public string|null $created_at = null, + /** + * Your unique key for the customer associated with this connected account. + */ + public string|null $customer_key = null, + /** + * Default reservation check-in time for this connected account, as `HH:mm` (24-hour). Sourced from the connector configuration — set during the connect_webview for providers like Lodgify whose API does not expose check-in times. + */ + public string|null $default_checkin_time = null, + /** + * Default reservation check-out time for this connected account, as `HH:mm` (24-hour). Sourced from the connector configuration. + */ + public string|null $default_checkout_time = null, + /** + * For iCal connected accounts, the platform that produced the feed (for example, `airbnb`, `vrbo`, or `booking`), or `unknown` when it could not be determined. Intended for rendering the source platform's logo. + */ + public string|null $ical_feed_origin = null, + /** + * For iCal connected accounts, the feed URL for the connection. Sourced from the connector configuration. + */ + public string|null $ical_url = null, + /** + * Logo URL for the connected account provider. + */ + public string|null $image_url = null, + /** + * IANA time zone (e.g. America/Los_Angeles) for this connected account. Sourced from the connector configuration. + */ + public string|null $time_zone = null, + /** + * User identifier associated with the connected account. + * + * @deprecated Use `display_name` instead. + */ + public \Seam\Resources\ConnectedAccount\UserIdentifier|null $user_identifier = null, + ) {} + } } -/** - * Errors associated with the connected account. - */ -class ConnectedAccountErrors -{ - public static function from_json(mixed $json): ConnectedAccountErrors|null +namespace Seam\Resources\ConnectedAccount { + /** + * Errors associated with the connected account. Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - is_bridge_error: $json->is_bridge_error ?? null, - is_connected_account_error: $json->is_connected_account_error ?? - null, - message: $json->message ?? null, - salto_ks_metadata: isset($json->salto_ks_metadata) - ? ConnectedAccountSaltoKsMetadata::from_json( - $json->salto_ks_metadata, + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\ConnectedAccount\Errors\ErrorCode::tryFrom( + $json->error_code, ) - : null, - ); + : null; + + return match ($discriminant) { + \Seam\Resources\ConnectedAccount\Errors\ErrorCode::ACCOUNT_DISCONNECTED + => \Seam\Resources\ConnectedAccount\Errors\AccountDisconnected::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Errors\ErrorCode::BRIDGE_DISCONNECTED + => \Seam\Resources\ConnectedAccount\Errors\BridgeDisconnected::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Errors\ErrorCode::SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED + => \Seam\Resources\ConnectedAccount\Errors\SaltoKsSubscriptionLimitExceeded::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Errors\ErrorCode::DORMAKABA_SITES_DISCONNECTED + => \Seam\Resources\ConnectedAccount\Errors\DormakabaSitesDisconnected::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + public bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + public bool|null $is_connected_account_error = null, + ) {} } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). - */ - public bool|null $is_bridge_error, - /** - * Indicates whether the error is related specifically to the connected account. - */ - public bool|null $is_connected_account_error, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Salto KS metadata associated with the connected account that has an error. - */ - public ConnectedAccountSaltoKsMetadata|null $salto_ks_metadata, - ) {} + /** + * User identifier associated with the connected account. + * + * @deprecated Use `display_name` instead. + */ + class UserIdentifier + { + public static function from_json(mixed $json): UserIdentifier|null + { + if (!$json) { + return null; + } + return new self( + api_url: $json->api_url ?? null, + email: $json->email ?? null, + exclusive: $json->exclusive ?? null, + phone: $json->phone ?? null, + username: $json->username ?? null, + ); + } + + public function __construct( + /** + * API URL for the user identifier associated with the connected account. + */ + public string|null $api_url = null, + /** + * Email address of the user identifier associated with the connected account. + */ + public string|null $email = null, + /** + * Indicates whether the user identifier associated with the connected account is exclusive. + */ + public bool|null $exclusive = null, + /** + * Phone number of the user identifier associated with the connected account. + */ + public string|null $phone = null, + /** + * Username of the user identifier associated with the connected account. + */ + public string|null $username = null, + ) {} + } + + /** + * Warnings associated with the connected account. Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\ConnectedAccount\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\ConnectedAccount\Warnings\WarningCode::SCHEDULED_MAINTENANCE_WINDOW + => \Seam\Resources\ConnectedAccount\Warnings\ScheduledMaintenanceWindow::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Warnings\WarningCode::UNKNOWN_ISSUE_WITH_CONNECTED_ACCOUNT + => \Seam\Resources\ConnectedAccount\Warnings\UnknownIssueWithConnectedAccount::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Warnings\WarningCode::SALTO_KS_SUBSCRIPTION_LIMIT_ALMOST_REACHED + => \Seam\Resources\ConnectedAccount\Warnings\SaltoKsSubscriptionLimitAlmostReached::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Warnings\WarningCode::ACCOUNT_REAUTHORIZATION_REQUESTED + => \Seam\Resources\ConnectedAccount\Warnings\AccountReauthorizationRequested::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\ConnectedAccount\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Warnings\WarningCode::PROVIDER_SERVICE_UNAVAILABLE + => \Seam\Resources\ConnectedAccount\Warnings\ProviderServiceUnavailable::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Warnings\WarningCode::SETUP_REQUIRED + => \Seam\Resources\ConnectedAccount\Warnings\SetupRequired::from_json( + $json, + ), + \Seam\Resources\ConnectedAccount\Warnings\WarningCode::DORMAKABA_SITES_UNAPPROVED + => \Seam\Resources\ConnectedAccount\Warnings\DormakabaSitesUnapproved::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } } -/** - * Salto KS metadata associated with the connected account that has an error. - */ -class ConnectedAccountSaltoKsMetadata -{ - public static function from_json( - mixed $json, - ): ConnectedAccountSaltoKsMetadata|null { - if (!$json) { - return null; - } - return new self( - sites: array_map( - fn($s) => ConnectedAccountSites::from_json($s), - $json->sites ?? [], - ), - ); +namespace Seam\Resources\ConnectedAccount\Errors { + /** + * Indicates that the account is disconnected. + */ + final class AccountDisconnected extends + \Seam\Resources\ConnectedAccount\Errors + { + public static function from_json(mixed $json): AccountDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + bool|null $is_connected_account_error = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + is_bridge_error: $is_bridge_error, + is_connected_account_error: $is_connected_account_error, + message: $message, + ); + } } - public function __construct( - /** - * Salto sites associated with the connected account that has an error. - */ - public array $sites, - ) {} + /** + * Indicates that the Seam API cannot communicate with [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge), for example, if the Seam Bridge executable has stopped or if the computer running the Seam Bridge executable is offline. See also [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system#acs_system-errors-seam_bridge_disconnected). + */ + final class BridgeDisconnected extends + \Seam\Resources\ConnectedAccount\Errors + { + public static function from_json(mixed $json): BridgeDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + bool|null $is_connected_account_error = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + is_bridge_error: $is_bridge_error, + is_connected_account_error: $is_connected_account_error, + message: $message, + ); + } + } + + /** + * Indicates that the maximum number of users allowed for the site has been reached. This means that new access codes cannot be created. Contact Salto support to increase the user limit. + */ + final class SaltoKsSubscriptionLimitExceeded extends + \Seam\Resources\ConnectedAccount\Errors + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + salto_ks_metadata: isset($json->salto_ks_metadata) + ? \Seam\Resources\ConnectedAccount\Errors\SaltoKsSubscriptionLimitExceeded\SaltoKsMetadata::from_json( + $json->salto_ks_metadata, + ) + : null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Salto KS metadata associated with the connected account that has an error. + */ + public \Seam\Resources\ConnectedAccount\Errors\SaltoKsSubscriptionLimitExceeded\SaltoKsMetadata|null $salto_ks_metadata, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + bool|null $is_connected_account_error = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + is_bridge_error: $is_bridge_error, + is_connected_account_error: $is_connected_account_error, + message: $message, + ); + } + } + + /** + * Indicates that one or more dormakaba sites associated with the connected account could not be connected. Contact dormakaba support. + */ + final class DormakabaSitesDisconnected extends + \Seam\Resources\ConnectedAccount\Errors + { + public static function from_json( + mixed $json, + ): DormakabaSitesDisconnected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + bool|null $is_connected_account_error = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + is_bridge_error: $is_bridge_error, + is_connected_account_error: $is_connected_account_error, + message: $message, + ); + } + } + + enum ErrorCode: string + { + case ACCOUNT_DISCONNECTED = "account_disconnected"; + case BRIDGE_DISCONNECTED = "bridge_disconnected"; + case SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED = "salto_ks_subscription_limit_exceeded"; + case DORMAKABA_SITES_DISCONNECTED = "dormakaba_sites_disconnected"; + } } -/** - * Salto sites associated with the connected account that has an error. - */ -class ConnectedAccountSites -{ - public static function from_json(mixed $json): ConnectedAccountSites|null +namespace Seam\Resources\ConnectedAccount\Errors\SaltoKsSubscriptionLimitExceeded { + /** + * Salto KS metadata associated with the connected account that has an error. + */ + class SaltoKsMetadata { - if (!$json) { - return null; - } - return new self( - site_id: $json->site_id ?? null, - site_name: $json->site_name ?? null, - site_user_subscription_limit: $json->site_user_subscription_limit ?? - null, - subscribed_site_user_count: $json->subscribed_site_user_count ?? - null, - ); + public static function from_json(mixed $json): SaltoKsMetadata|null + { + if (!$json) { + return null; + } + return new self( + sites: array_map( + fn( + $s, + ) => \Seam\Resources\ConnectedAccount\Errors\SaltoKsSubscriptionLimitExceeded\SaltoKsMetadata\Sites::from_json( + $s, + ), + $json->sites ?? [], + ), + ); + } + + public function __construct( + /** + * Salto sites associated with the connected account that has an error. + * + * @var list<\Seam\Resources\ConnectedAccount\Errors\SaltoKsSubscriptionLimitExceeded\SaltoKsMetadata\Sites>|null + */ + public array|null $sites = null, + ) {} } +} - public function __construct( - /** - * ID of a Salto site associated with the connected account that has an error. - */ - public string|null $site_id, - /** - * Name of a Salto site associated with the connected account that has an error. - */ - public string|null $site_name, - /** - * Subscription limit of site users for a Salto site associated with the connected account that has an error. - */ - public int|null $site_user_subscription_limit, - /** - * Count of subscribed site users for a Salto site associated with the connected account that has an error. - */ - public int|null $subscribed_site_user_count, - ) {} +namespace Seam\Resources\ConnectedAccount\Errors\SaltoKsSubscriptionLimitExceeded\SaltoKsMetadata { + /** + * Salto sites associated with the connected account that has an error. + */ + class Sites + { + public static function from_json(mixed $json): Sites|null + { + if (!$json) { + return null; + } + return new self( + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + site_user_subscription_limit: $json->site_user_subscription_limit ?? + null, + subscribed_site_user_count: $json->subscribed_site_user_count ?? + null, + ); + } + + public function __construct( + /** + * ID of a Salto site associated with the connected account that has an error. + */ + public string|null $site_id = null, + /** + * Name of a Salto site associated with the connected account that has an error. + */ + public string|null $site_name = null, + /** + * Subscription limit of site users for a Salto site associated with the connected account that has an error. + */ + public int|null $site_user_subscription_limit = null, + /** + * Count of subscribed site users for a Salto site associated with the connected account that has an error. + */ + public int|null $subscribed_site_user_count = null, + ) {} + } } -/** - * User identifier associated with the connected account. - */ -class ConnectedAccountUserIdentifier -{ - public static function from_json( - mixed $json, - ): ConnectedAccountUserIdentifier|null { - if (!$json) { - return null; - } - return new self( - api_url: $json->api_url ?? null, - email: $json->email ?? null, - exclusive: $json->exclusive ?? null, - phone: $json->phone ?? null, - username: $json->username ?? null, - ); +namespace Seam\Resources\ConnectedAccount\Warnings { + /** + * Indicates that scheduled downtime is planned for the connected account. + */ + final class ScheduledMaintenanceWindow extends + \Seam\Resources\ConnectedAccount\Warnings + { + public static function from_json( + mixed $json, + ): ScheduledMaintenanceWindow|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } } - public function __construct( - /** - * API URL for the user identifier associated with the connected account. - */ - public string|null $api_url, - /** - * Email address of the user identifier associated with the connected account. - */ - public string|null $email, - /** - * Indicates whether the user identifier associated with the connected account is exclusive. - */ - public bool|null $exclusive, - /** - * Phone number of the user identifier associated with the connected account. - */ - public string|null $phone, - /** - * Username of the user identifier associated with the connected account. - */ - public string|null $username, - ) {} + /** + * Indicates that an unknown issue occurred while syncing the state of the connected account with the provider. This issue may affect the proper functioning of one or more resources in the account. + */ + final class UnknownIssueWithConnectedAccount extends + \Seam\Resources\ConnectedAccount\Warnings + { + public static function from_json( + mixed $json, + ): UnknownIssueWithConnectedAccount|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the Salto KS site has exceeded 80% of the maximum number of allowed users. Increase your subscription limit or delete some users from your site. + */ + final class SaltoKsSubscriptionLimitAlmostReached extends + \Seam\Resources\ConnectedAccount\Warnings + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitAlmostReached|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + salto_ks_metadata: isset($json->salto_ks_metadata) + ? \Seam\Resources\ConnectedAccount\Warnings\SaltoKsSubscriptionLimitAlmostReached\SaltoKsMetadata::from_json( + $json->salto_ks_metadata, + ) + : null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Salto KS metadata associated with the connected account that has a warning. + */ + public \Seam\Resources\ConnectedAccount\Warnings\SaltoKsSubscriptionLimitAlmostReached\SaltoKsMetadata|null $salto_ks_metadata, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the Connected Account requires reauthorization using a new Connect Webview. The account is still connected, but cannot access new features. Delaying reauthorization too long will eventually cause the Connected Account to become disconnected. + */ + final class AccountReauthorizationRequested extends + \Seam\Resources\ConnectedAccount\Warnings + { + public static function from_json( + mixed $json, + ): AccountReauthorizationRequested|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the connected account is currently being deleted. All devices, access codes, and other resources associated with this account are in the process of being removed from Seam. + */ + final class BeingDeleted extends \Seam\Resources\ConnectedAccount\Warnings + { + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the connected account's provider service is temporarily unavailable. Seam will automatically retry and reconnect when the service becomes available again. + */ + final class ProviderServiceUnavailable extends + \Seam\Resources\ConnectedAccount\Warnings + { + public static function from_json( + mixed $json, + ): ProviderServiceUnavailable|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the connected account requires additional setup before it can be fully operational. Follow the instructions in the warning message to complete the setup. + */ + final class SetupRequired extends \Seam\Resources\ConnectedAccount\Warnings + { + public static function from_json(mixed $json): SetupRequired|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that one or more dormakaba sites associated with the connected account are not approved. Contact support@getseam.com to finish setting up your account. + */ + final class DormakabaSitesUnapproved extends + \Seam\Resources\ConnectedAccount\Warnings + { + public static function from_json( + mixed $json, + ): DormakabaSitesUnapproved|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\ConnectedAccount\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case SCHEDULED_MAINTENANCE_WINDOW = "scheduled_maintenance_window"; + case UNKNOWN_ISSUE_WITH_CONNECTED_ACCOUNT = "unknown_issue_with_connected_account"; + case SALTO_KS_SUBSCRIPTION_LIMIT_ALMOST_REACHED = "salto_ks_subscription_limit_almost_reached"; + case ACCOUNT_REAUTHORIZATION_REQUESTED = "account_reauthorization_requested"; + case BEING_DELETED = "being_deleted"; + case PROVIDER_SERVICE_UNAVAILABLE = "provider_service_unavailable"; + case SETUP_REQUIRED = "setup_required"; + case DORMAKABA_SITES_UNAPPROVED = "dormakaba_sites_unapproved"; + } } -/** - * Warnings associated with the connected account. - */ -class ConnectedAccountWarnings -{ - public static function from_json(mixed $json): ConnectedAccountWarnings|null +namespace Seam\Resources\ConnectedAccount\Warnings\SaltoKsSubscriptionLimitAlmostReached { + /** + * Salto KS metadata associated with the connected account that has a warning. + */ + class SaltoKsMetadata { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - salto_ks_metadata: isset($json->salto_ks_metadata) - ? ConnectedAccountSaltoKsMetadata::from_json( - $json->salto_ks_metadata, - ) - : null, - warning_code: $json->warning_code ?? null, - ); + public static function from_json(mixed $json): SaltoKsMetadata|null + { + if (!$json) { + return null; + } + return new self( + sites: array_map( + fn( + $s, + ) => \Seam\Resources\ConnectedAccount\Warnings\SaltoKsSubscriptionLimitAlmostReached\SaltoKsMetadata\Sites::from_json( + $s, + ), + $json->sites ?? [], + ), + ); + } + + public function __construct( + /** + * Salto sites associated with the connected account that has a warning. + * + * @var list<\Seam\Resources\ConnectedAccount\Warnings\SaltoKsSubscriptionLimitAlmostReached\SaltoKsMetadata\Sites>|null + */ + public array|null $sites = null, + ) {} } +} + +namespace Seam\Resources\ConnectedAccount\Warnings\SaltoKsSubscriptionLimitAlmostReached\SaltoKsMetadata { + /** + * Salto sites associated with the connected account that has a warning. + */ + class Sites + { + public static function from_json(mixed $json): Sites|null + { + if (!$json) { + return null; + } + return new self( + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + site_user_subscription_limit: $json->site_user_subscription_limit ?? + null, + subscribed_site_user_count: $json->subscribed_site_user_count ?? + null, + ); + } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Salto KS metadata associated with the connected account that has a warning. - */ - public ConnectedAccountSaltoKsMetadata|null $salto_ks_metadata, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + public function __construct( + /** + * ID of a Salto site associated with the connected account that has a warning. + */ + public string|null $site_id = null, + /** + * Name of a Salto site associated with the connected account that has a warning. + */ + public string|null $site_name = null, + /** + * Subscription limit of site users for a Salto site associated with the connected account that has a warning. + */ + public int|null $site_user_subscription_limit = null, + /** + * Count of subscribed site users for a Salto site associated with the connected account that has a warning. + */ + public int|null $subscribed_site_user_count = null, + ) {} + } } diff --git a/src/Resources/CustomerPortal.php b/src/Resources/CustomerPortal.php index 89029d27..0822f0ff 100644 --- a/src/Resources/CustomerPortal.php +++ b/src/Resources/CustomerPortal.php @@ -1,50 +1,50 @@ created_at ?? null, + customer_key: $json->customer_key ?? null, + expires_at: $json->expires_at ?? null, + url: $json->url ?? null, + workspace_id: $json->workspace_id ?? null, + ); } - return new self( - created_at: $json->created_at ?? null, - customer_key: $json->customer_key ?? null, - expires_at: $json->expires_at ?? null, - url: $json->url ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * Date and time at which the customer portal link was created. - */ - public string|null $created_at, - /** - * Customer key for the customer portal. - */ - public string|null $customer_key, - /** - * Date and time at which the customer portal link expires. - */ - public string|null $expires_at, - /** - * URL for the customer portal. - */ - public string|null $url, - /** - * ID of the workspace associated with the customer portal. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * Date and time at which the customer portal link was created. + */ + public string|null $created_at, + /** + * Customer key for the customer portal. + */ + public string|null $customer_key, + /** + * Date and time at which the customer portal link expires. + */ + public string|null $expires_at, + /** + * URL for the customer portal. + */ + public string|null $url, + /** + * ID of the workspace associated with the customer portal. + */ + public string|null $workspace_id, + ) {} + } } diff --git a/src/Resources/Device.php b/src/Resources/Device.php index 2b6d428c..8e8da76f 100644 --- a/src/Resources/Device.php +++ b/src/Resources/Device.php @@ -1,4092 +1,6884 @@ can_configure_auto_lock ?? null, - can_hvac_cool: $json->can_hvac_cool ?? null, - can_hvac_heat: $json->can_hvac_heat ?? null, - can_hvac_heat_cool: $json->can_hvac_heat_cool ?? null, - can_program_offline_access_codes: $json->can_program_offline_access_codes ?? - null, - can_program_online_access_codes: $json->can_program_online_access_codes ?? - null, - can_program_thermostat_programs_as_different_each_day: $json->can_program_thermostat_programs_as_different_each_day ?? - null, - can_program_thermostat_programs_as_same_each_day: $json->can_program_thermostat_programs_as_same_each_day ?? - null, - can_program_thermostat_programs_as_weekday_weekend: $json->can_program_thermostat_programs_as_weekday_weekend ?? - null, - can_remotely_lock: $json->can_remotely_lock ?? null, - can_remotely_unlock: $json->can_remotely_unlock ?? null, - can_run_thermostat_programs: $json->can_run_thermostat_programs ?? - null, - can_simulate_connection: $json->can_simulate_connection ?? null, - can_simulate_disconnection: $json->can_simulate_disconnection ?? - null, - can_simulate_hub_connection: $json->can_simulate_hub_connection ?? - null, - can_simulate_hub_disconnection: $json->can_simulate_hub_disconnection ?? - null, - can_simulate_paid_subscription: $json->can_simulate_paid_subscription ?? - null, - can_simulate_removal: $json->can_simulate_removal ?? null, - can_turn_off_hvac: $json->can_turn_off_hvac ?? null, - can_unlock_with_code: $json->can_unlock_with_code ?? null, - capabilities_supported: $json->capabilities_supported ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - custom_metadata: $json->custom_metadata ?? null, - device_id: $json->device_id ?? null, - device_manufacturer: isset($json->device_manufacturer) - ? DeviceDeviceManufacturer::from_json( - $json->device_manufacturer, +namespace Seam\Resources { + /** + * Represents a [device](https://docs.seam.co/core-concepts/devices) that has been connected to Seam. + */ + class Device + { + public static function from_json(mixed $json): Device|null + { + if (!$json) { + return null; + } + return new self( + capabilities_supported: $json->capabilities_supported ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + custom_metadata: $json->custom_metadata ?? null, + device_id: $json->device_id ?? null, + device_type: $json->device_type ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn($e) => \Seam\Resources\Device\Errors::from_json($e), + $json->errors ?? [], + ), + is_managed: $json->is_managed ?? null, + properties: isset($json->properties) + ? \Seam\Resources\Device\Properties::from_json( + $json->properties, + ) + : null, + space_ids: $json->space_ids ?? null, + warnings: array_map( + fn($w) => \Seam\Resources\Device\Warnings::from_json($w), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + can_configure_auto_lock: $json->can_configure_auto_lock ?? null, + can_hvac_cool: $json->can_hvac_cool ?? null, + can_hvac_heat: $json->can_hvac_heat ?? null, + can_hvac_heat_cool: $json->can_hvac_heat_cool ?? null, + can_program_offline_access_codes: $json->can_program_offline_access_codes ?? + null, + can_program_online_access_codes: $json->can_program_online_access_codes ?? + null, + can_program_thermostat_programs_as_different_each_day: $json->can_program_thermostat_programs_as_different_each_day ?? + null, + can_program_thermostat_programs_as_same_each_day: $json->can_program_thermostat_programs_as_same_each_day ?? + null, + can_program_thermostat_programs_as_weekday_weekend: $json->can_program_thermostat_programs_as_weekday_weekend ?? + null, + can_remotely_lock: $json->can_remotely_lock ?? null, + can_remotely_unlock: $json->can_remotely_unlock ?? null, + can_run_thermostat_programs: $json->can_run_thermostat_programs ?? + null, + can_simulate_connection: $json->can_simulate_connection ?? null, + can_simulate_disconnection: $json->can_simulate_disconnection ?? + null, + can_simulate_hub_connection: $json->can_simulate_hub_connection ?? + null, + can_simulate_hub_disconnection: $json->can_simulate_hub_disconnection ?? + null, + can_simulate_paid_subscription: $json->can_simulate_paid_subscription ?? + null, + can_simulate_removal: $json->can_simulate_removal ?? null, + can_turn_off_hvac: $json->can_turn_off_hvac ?? null, + can_unlock_with_code: $json->can_unlock_with_code ?? null, + device_manufacturer: isset($json->device_manufacturer) + ? \Seam\Resources\Device\DeviceManufacturer::from_json( + $json->device_manufacturer, + ) + : null, + device_provider: isset($json->device_provider) + ? \Seam\Resources\Device\DeviceProvider::from_json( + $json->device_provider, + ) + : null, + location: isset($json->location) + ? \Seam\Resources\Device\Location::from_json( + $json->location, + ) + : null, + nickname: $json->nickname ?? null, + ); + } + + public function __construct( + /** + * Collection of capabilities that the device supports when connected to Seam. Values are `access_code`, which indicates that the device can manage and utilize digital PIN codes for secure access; `lock`, which indicates that the device controls a door locking mechanism, enabling the remote opening and closing of doors and other entry points; `noise_detection`, which indicates that the device supports monitoring and responding to ambient noise levels; `thermostat`, which indicates that the device can regulate and adjust indoor temperatures; `battery`, which indicates that the device can manage battery life and health; and `phone`, which indicates that the device is a mobile device, such as a smartphone. **Important:** Superseded by [capability flags](https://docs.seam.co/capability-guides/device-and-system-capabilities#capability-flags). + * + * @var list|null + */ + public array|null $capabilities_supported, + /** + * Unique identifier for the account associated with the device. + */ + public string|null $connected_account_id, + /** + * Date and time at which the device object was created. + */ + public string|null $created_at, + /** + * Set of key:value pairs. Adding custom metadata to a resource, such as a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview), [connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account), or [device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device), enables you to store custom information, like customer details or internal IDs from your application. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $custom_metadata, + /** + * ID of the device. + */ + public string|null $device_id, + /** + * Type of the device. + * + * @var value-of<\Seam\Resources\Device\DeviceType>|string|null + */ + public string|null $device_type, + /** + * Display name of the device, defaults to nickname (if it is set) or `properties.appearance.name`, otherwise. Enables administrators and users to identify the device easily, especially when there are numerous devices. + */ + public string|null $display_name, + /** + * Array of errors associated with the device. Each error object within the array contains two fields: `error_code` and `message`. `error_code` is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. + * + * @var list<\Seam\Resources\Device\Errors> + */ + public array $errors, + /** + * Indicates whether Seam manages the device. See also [Managed and Unmanaged Devices](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices). + */ + public true|null $is_managed, + /** + * Properties of the device. + */ + public \Seam\Resources\Device\Properties|null $properties, + /** + * IDs of the spaces the device is in. + * + * @var list|null + */ + public array|null $space_ids, + /** + * Array of warnings associated with the device. Each warning object within the array contains two fields: `warning_code` and `message`. `warning_code` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. + * + * @var list<\Seam\Resources\Device\Warnings> + */ + public array $warnings, + /** + * Unique identifier for the Seam workspace associated with the device. + */ + public string|null $workspace_id, + /** + * Indicates whether the lock supports configuring automatic locking. + */ + public bool|null $can_configure_auto_lock = null, + /** + * Indicates whether the thermostat supports cooling. + */ + public bool|null $can_hvac_cool = null, + /** + * Indicates whether the thermostat supports heating. + */ + public bool|null $can_hvac_heat = null, + /** + * Indicates whether the thermostat supports simultaneous heating and cooling. + */ + public bool|null $can_hvac_heat_cool = null, + /** + * Indicates whether the device supports programming offline access codes. + */ + public bool|null $can_program_offline_access_codes = null, + /** + * Indicates whether the device supports programming online access codes. + */ + public bool|null $can_program_online_access_codes = null, + /** + * Indicates whether the thermostat supports different climate programs for each day of the week. + */ + public bool|null $can_program_thermostat_programs_as_different_each_day = null, + /** + * Indicates whether the thermostat supports a single climate program applied to every day. + */ + public bool|null $can_program_thermostat_programs_as_same_each_day = null, + /** + * Indicates whether the thermostat supports weekday/weekend climate programs. + */ + public bool|null $can_program_thermostat_programs_as_weekday_weekend = null, + /** + * Indicates whether the device supports remote locking. + */ + public bool|null $can_remotely_lock = null, + /** + * Indicates whether the device supports remote unlocking. + */ + public bool|null $can_remotely_unlock = null, + /** + * Indicates whether the thermostat supports running climate programs. + */ + public bool|null $can_run_thermostat_programs = null, + /** + * Indicates whether the device supports simulating connection in a sandbox. + */ + public bool|null $can_simulate_connection = null, + /** + * Indicates whether the device supports simulating disconnection in a sandbox. + */ + public bool|null $can_simulate_disconnection = null, + /** + * Indicates whether the hub supports simulating connection in a sandbox. + */ + public bool|null $can_simulate_hub_connection = null, + /** + * Indicates whether the hub supports simulating disconnection in a sandbox. + */ + public bool|null $can_simulate_hub_disconnection = null, + /** + * Indicates whether the device supports simulating a paid subscription in a sandbox. + */ + public bool|null $can_simulate_paid_subscription = null, + /** + * Indicates whether the device supports simulating removal in a sandbox. + */ + public bool|null $can_simulate_removal = null, + /** + * Indicates whether the thermostat can be turned off. + */ + public bool|null $can_turn_off_hvac = null, + /** + * Indicates whether the lock supports unlocking with an access code. + */ + public bool|null $can_unlock_with_code = null, + /** + * Manufacturer of the device. Represents the hardware brand, which may differ from the provider. + */ + public \Seam\Resources\Device\DeviceManufacturer|null $device_manufacturer = null, + /** + * Provider of the device. Represents the third-party service through which the device is controlled. + */ + public \Seam\Resources\Device\DeviceProvider|null $device_provider = null, + /** + * Location information for the device. + */ + public \Seam\Resources\Device\Location|null $location = null, + /** + * Optional nickname to describe the device, settable through Seam. + */ + public string|null $nickname = null, + ) {} + } +} + +namespace Seam\Resources\Device { + /** + * Manufacturer of the device. Represents the hardware brand, which may differ from the provider. + */ + class DeviceManufacturer + { + public static function from_json(mixed $json): DeviceManufacturer|null + { + if (!$json) { + return null; + } + return new self( + display_name: $json->display_name ?? null, + manufacturer: $json->manufacturer ?? null, + image_url: $json->image_url ?? null, + ); + } + + public function __construct( + /** + * Display name for the manufacturer, such as `August`, `Yale`, `Salto`, and so on. + */ + public string|null $display_name, + /** + * Manufacturer identifier, such as `august`, `yale`, `salto`, and so on. + */ + public string|null $manufacturer, + /** + * Image URL for the manufacturer logo. + */ + public string|null $image_url = null, + ) {} + } + + /** + * Provider of the device. Represents the third-party service through which the device is controlled. + */ + class DeviceProvider + { + public static function from_json(mixed $json): DeviceProvider|null + { + if (!$json) { + return null; + } + return new self( + device_provider_name: $json->device_provider_name ?? null, + display_name: $json->display_name ?? null, + provider_category: $json->provider_category ?? null, + image_url: $json->image_url ?? null, + ); + } + + public function __construct( + /** + * Device provider name. Corresponds to the integration type, such as `august`, `schlage`, `yale_access`, and so on. + */ + public string|null $device_provider_name, + /** + * Display name for the device provider type. + */ + public string|null $display_name, + /** + * Provider category. Indicates the third-party provider type, such as `stable`, for stable integrations, or `internal`, for internal integrations. + */ + public string|null $provider_category, + /** + * Image URL for the device provider. + */ + public string|null $image_url = null, + ) {} + } + + /** + * Array of errors associated with the device. Each error object within the array contains two fields: `error_code` and `message`. `error_code` is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\Device\Errors\ErrorCode::tryFrom( + $json->error_code, ) - : null, - device_provider: isset($json->device_provider) - ? DeviceDeviceProvider::from_json($json->device_provider) - : null, - device_type: $json->device_type ?? null, - display_name: $json->display_name ?? null, - errors: array_map( - fn($e) => DeviceErrors::from_json($e), - $json->errors ?? [], - ), - is_managed: $json->is_managed ?? null, - location: isset($json->location) - ? DeviceLocation::from_json($json->location) - : null, - nickname: $json->nickname ?? null, - properties: isset($json->properties) - ? DeviceProperties::from_json($json->properties) - : null, - space_ids: $json->space_ids ?? null, - warnings: array_map( - fn($w) => DeviceWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the lock supports configuring automatic locking. - */ - public bool|null $can_configure_auto_lock, - /** - * Indicates whether the thermostat supports cooling. - */ - public bool|null $can_hvac_cool, - /** - * Indicates whether the thermostat supports heating. - */ - public bool|null $can_hvac_heat, - /** - * Indicates whether the thermostat supports simultaneous heating and cooling. - */ - public bool|null $can_hvac_heat_cool, - /** - * Indicates whether the device supports programming offline access codes. - */ - public bool|null $can_program_offline_access_codes, - /** - * Indicates whether the device supports programming online access codes. - */ - public bool|null $can_program_online_access_codes, - /** - * Indicates whether the thermostat supports different climate programs for each day of the week. - */ - public bool|null $can_program_thermostat_programs_as_different_each_day, - /** - * Indicates whether the thermostat supports a single climate program applied to every day. - */ - public bool|null $can_program_thermostat_programs_as_same_each_day, - /** - * Indicates whether the thermostat supports weekday/weekend climate programs. - */ - public bool|null $can_program_thermostat_programs_as_weekday_weekend, - /** - * Indicates whether the device supports remote locking. - */ - public bool|null $can_remotely_lock, - /** - * Indicates whether the device supports remote unlocking. - */ - public bool|null $can_remotely_unlock, - /** - * Indicates whether the thermostat supports running climate programs. - */ - public bool|null $can_run_thermostat_programs, - /** - * Indicates whether the device supports simulating connection in a sandbox. - */ - public bool|null $can_simulate_connection, - /** - * Indicates whether the device supports simulating disconnection in a sandbox. - */ - public bool|null $can_simulate_disconnection, - /** - * Indicates whether the hub supports simulating connection in a sandbox. - */ - public bool|null $can_simulate_hub_connection, - /** - * Indicates whether the hub supports simulating disconnection in a sandbox. - */ - public bool|null $can_simulate_hub_disconnection, - /** - * Indicates whether the device supports simulating a paid subscription in a sandbox. - */ - public bool|null $can_simulate_paid_subscription, - /** - * Indicates whether the device supports simulating removal in a sandbox. - */ - public bool|null $can_simulate_removal, - /** - * Indicates whether the thermostat can be turned off. - */ - public bool|null $can_turn_off_hvac, - /** - * Indicates whether the lock supports unlocking with an access code. - */ - public bool|null $can_unlock_with_code, - /** - * Collection of capabilities that the device supports when connected to Seam. Values are `access_code`, which indicates that the device can manage and utilize digital PIN codes for secure access; `lock`, which indicates that the device controls a door locking mechanism, enabling the remote opening and closing of doors and other entry points; `noise_detection`, which indicates that the device supports monitoring and responding to ambient noise levels; `thermostat`, which indicates that the device can regulate and adjust indoor temperatures; `battery`, which indicates that the device can manage battery life and health; and `phone`, which indicates that the device is a mobile device, such as a smartphone. **Important:** Superseded by [capability flags](https://docs.seam.co/capability-guides/device-and-system-capabilities#capability-flags). - */ - public array|null $capabilities_supported, - /** - * Unique identifier for the account associated with the device. - */ - public string|null $connected_account_id, - /** - * Date and time at which the device object was created. - */ - public string|null $created_at, - /** - * Set of key:value pairs. Adding custom metadata to a resource, such as a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview), [connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account), or [device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device), enables you to store custom information, like customer details or internal IDs from your application. - */ - public mixed $custom_metadata, - /** - * ID of the device. - */ - public string|null $device_id, - /** - * Manufacturer of the device. Represents the hardware brand, which may differ from the provider. - */ - public DeviceDeviceManufacturer|null $device_manufacturer, - /** - * Provider of the device. Represents the third-party service through which the device is controlled. - */ - public DeviceDeviceProvider|null $device_provider, - /** - * Type of the device. - */ - public string|null $device_type, - /** - * Display name of the device, defaults to nickname (if it is set) or `properties.appearance.name`, otherwise. Enables administrators and users to identify the device easily, especially when there are numerous devices. - */ - public string|null $display_name, - /** - * Array of errors associated with the device. Each error object within the array contains two fields: `error_code` and `message`. `error_code` is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. - */ - public array $errors, - /** - * Indicates whether Seam manages the device. See also [Managed and Unmanaged Devices](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices). - */ - public bool|null $is_managed, - /** - * Location information for the device. - */ - public DeviceLocation|null $location, - /** - * Optional nickname to describe the device, settable through Seam. - */ - public string|null $nickname, - /** - * Properties of the device. - */ - public DeviceProperties|null $properties, - /** - * IDs of the spaces the device is in. - */ - public array|null $space_ids, - /** - * Array of warnings associated with the device. Each warning object within the array contains two fields: `warning_code` and `message`. `warning_code` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - */ - public array $warnings, - /** - * Unique identifier for the Seam workspace associated with the device. - */ - public string|null $workspace_id, - ) {} + : null; + + return match ($discriminant) { + \Seam\Resources\Device\Errors\ErrorCode::ACCOUNT_DISCONNECTED + => \Seam\Resources\Device\Errors\AccountDisconnected::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED + => \Seam\Resources\Device\Errors\SaltoKsSubscriptionLimitExceeded::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::INSUFFICIENT_PERMISSIONS + => \Seam\Resources\Device\Errors\InsufficientPermissions::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::DORMAKABA_SITES_DISCONNECTED + => \Seam\Resources\Device\Errors\DormakabaSitesDisconnected::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::DEVICE_OFFLINE + => \Seam\Resources\Device\Errors\DeviceOffline::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::DEVICE_REMOVED + => \Seam\Resources\Device\Errors\DeviceRemoved::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::HUB_DISCONNECTED + => \Seam\Resources\Device\Errors\HubDisconnected::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::DEVICE_DISCONNECTED + => \Seam\Resources\Device\Errors\DeviceDisconnected::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::EMPTY_BACKUP_ACCESS_CODE_POOL + => \Seam\Resources\Device\Errors\EmptyBackupAccessCodePool::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::AUGUST_LOCK_NOT_AUTHORIZED + => \Seam\Resources\Device\Errors\AugustLockNotAuthorized::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::MISSING_DEVICE_CREDENTIALS + => \Seam\Resources\Device\Errors\MissingDeviceCredentials::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::AUXILIARY_HEAT_RUNNING + => \Seam\Resources\Device\Errors\AuxiliaryHeatRunning::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::SUBSCRIPTION_REQUIRED + => \Seam\Resources\Device\Errors\SubscriptionRequired::from_json( + $json, + ), + \Seam\Resources\Device\Errors\ErrorCode::BRIDGE_DISCONNECTED + => \Seam\Resources\Device\Errors\BridgeDisconnected::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Location information for the device. + */ + class Location + { + public static function from_json(mixed $json): Location|null + { + if (!$json) { + return null; + } + return new self( + location_name: $json->location_name ?? null, + room_name: $json->room_name ?? null, + time_zone: $json->time_zone ?? null, + timezone: $json->timezone ?? null, + ); + } + + public function __construct( + /** + * Name of the device location. + */ + public string|null $location_name = null, + /** + * Name of the room within the device location, when the provider reports one. + */ + public string|null $room_name = null, + /** + * Time zone of the device location. + */ + public string|null $time_zone = null, + /** + * Time zone of the device location. + * + * @deprecated Use `time_zone` instead. + */ + public string|null $timezone = null, + ) {} + } + + /** + * Properties of the device. + */ + class Properties + { + public static function from_json(mixed $json): Properties|null + { + if (!$json) { + return null; + } + return new self( + appearance: isset($json->appearance) + ? \Seam\Resources\Device\Properties\Appearance::from_json( + $json->appearance, + ) + : null, + model: isset($json->model) + ? \Seam\Resources\Device\Properties\Model::from_json( + $json->model, + ) + : null, + name: $json->name ?? null, + online: $json->online ?? null, + accessory_keypad: isset($json->accessory_keypad) + ? \Seam\Resources\Device\Properties\AccessoryKeypad::from_json( + $json->accessory_keypad, + ) + : null, + active_thermostat_schedule: isset( + $json->active_thermostat_schedule, + ) + ? \Seam\Resources\Device\Properties\ActiveThermostatSchedule::from_json( + $json->active_thermostat_schedule, + ) + : null, + active_thermostat_schedule_id: $json->active_thermostat_schedule_id ?? + null, + akiles_metadata: isset($json->akiles_metadata) + ? \Seam\Resources\Device\Properties\AkilesMetadata::from_json( + $json->akiles_metadata, + ) + : null, + aqara_metadata: isset($json->aqara_metadata) + ? \Seam\Resources\Device\Properties\AqaraMetadata::from_json( + $json->aqara_metadata, + ) + : null, + assa_abloy_credential_service_metadata: isset( + $json->assa_abloy_credential_service_metadata, + ) + ? \Seam\Resources\Device\Properties\AssaAbloyCredentialServiceMetadata::from_json( + $json->assa_abloy_credential_service_metadata, + ) + : null, + assa_abloy_vostio_metadata: isset( + $json->assa_abloy_vostio_metadata, + ) + ? \Seam\Resources\Device\Properties\AssaAbloyVostioMetadata::from_json( + $json->assa_abloy_vostio_metadata, + ) + : null, + august_metadata: isset($json->august_metadata) + ? \Seam\Resources\Device\Properties\AugustMetadata::from_json( + $json->august_metadata, + ) + : null, + auto_lock_delay_seconds: $json->auto_lock_delay_seconds ?? null, + auto_lock_enabled: $json->auto_lock_enabled ?? null, + available_climate_preset_modes: $json->available_climate_preset_modes ?? + null, + available_climate_presets: array_map( + fn( + $a, + ) => \Seam\Resources\Device\Properties\AvailableClimatePresets::from_json( + $a, + ), + $json->available_climate_presets ?? [], + ), + available_fan_mode_settings: $json->available_fan_mode_settings ?? + null, + available_hvac_mode_settings: $json->available_hvac_mode_settings ?? + null, + avigilon_alta_metadata: isset($json->avigilon_alta_metadata) + ? \Seam\Resources\Device\Properties\AvigilonAltaMetadata::from_json( + $json->avigilon_alta_metadata, + ) + : null, + backup_access_code_pool_enabled: $json->backup_access_code_pool_enabled ?? + null, + battery: isset($json->battery) + ? \Seam\Resources\Device\Properties\Battery::from_json( + $json->battery, + ) + : null, + battery_level: $json->battery_level ?? null, + brivo_metadata: isset($json->brivo_metadata) + ? \Seam\Resources\Device\Properties\BrivoMetadata::from_json( + $json->brivo_metadata, + ) + : null, + code_constraints: array_map( + fn( + $c, + ) => \Seam\Resources\Device\Properties\CodeConstraints::from_json( + $c, + ), + $json->code_constraints ?? [], + ), + controlbyweb_metadata: isset($json->controlbyweb_metadata) + ? \Seam\Resources\Device\Properties\ControlbywebMetadata::from_json( + $json->controlbyweb_metadata, + ) + : null, + current_climate_setting: isset($json->current_climate_setting) + ? \Seam\Resources\Device\Properties\CurrentClimateSetting::from_json( + $json->current_climate_setting, + ) + : null, + currently_triggering_noise_threshold_ids: $json->currently_triggering_noise_threshold_ids ?? + null, + default_climate_setting: isset($json->default_climate_setting) + ? \Seam\Resources\Device\Properties\DefaultClimateSetting::from_json( + $json->default_climate_setting, + ) + : null, + door_open: $json->door_open ?? null, + dormakaba_oracode_metadata: isset( + $json->dormakaba_oracode_metadata, + ) + ? \Seam\Resources\Device\Properties\DormakabaOracodeMetadata::from_json( + $json->dormakaba_oracode_metadata, + ) + : null, + ecobee_metadata: isset($json->ecobee_metadata) + ? \Seam\Resources\Device\Properties\EcobeeMetadata::from_json( + $json->ecobee_metadata, + ) + : null, + fallback_climate_preset_key: $json->fallback_climate_preset_key ?? + null, + fan_mode_setting: $json->fan_mode_setting ?? null, + four_suites_metadata: isset($json->four_suites_metadata) + ? \Seam\Resources\Device\Properties\FourSuitesMetadata::from_json( + $json->four_suites_metadata, + ) + : null, + genie_metadata: isset($json->genie_metadata) + ? \Seam\Resources\Device\Properties\GenieMetadata::from_json( + $json->genie_metadata, + ) + : null, + has_direct_power: $json->has_direct_power ?? null, + has_native_entry_events: $json->has_native_entry_events ?? null, + honeywell_resideo_metadata: isset( + $json->honeywell_resideo_metadata, + ) + ? \Seam\Resources\Device\Properties\HoneywellResideoMetadata::from_json( + $json->honeywell_resideo_metadata, + ) + : null, + igloo_metadata: isset($json->igloo_metadata) + ? \Seam\Resources\Device\Properties\IglooMetadata::from_json( + $json->igloo_metadata, + ) + : null, + igloohome_metadata: isset($json->igloohome_metadata) + ? \Seam\Resources\Device\Properties\IgloohomeMetadata::from_json( + $json->igloohome_metadata, + ) + : null, + image_alt_text: $json->image_alt_text ?? null, + image_url: $json->image_url ?? null, + is_cooling: $json->is_cooling ?? null, + is_fan_running: $json->is_fan_running ?? null, + is_heating: $json->is_heating ?? null, + is_temporary_manual_override_active: $json->is_temporary_manual_override_active ?? + null, + keynest_metadata: isset($json->keynest_metadata) + ? \Seam\Resources\Device\Properties\KeynestMetadata::from_json( + $json->keynest_metadata, + ) + : null, + keypad_battery: isset($json->keypad_battery) + ? \Seam\Resources\Device\Properties\KeypadBattery::from_json( + $json->keypad_battery, + ) + : null, + kisi_metadata: isset($json->kisi_metadata) + ? \Seam\Resources\Device\Properties\KisiMetadata::from_json( + $json->kisi_metadata, + ) + : null, + korelock_metadata: isset($json->korelock_metadata) + ? \Seam\Resources\Device\Properties\KorelockMetadata::from_json( + $json->korelock_metadata, + ) + : null, + kwikset_metadata: isset($json->kwikset_metadata) + ? \Seam\Resources\Device\Properties\KwiksetMetadata::from_json( + $json->kwikset_metadata, + ) + : null, + locked: $json->locked ?? null, + lockly_metadata: isset($json->lockly_metadata) + ? \Seam\Resources\Device\Properties\LocklyMetadata::from_json( + $json->lockly_metadata, + ) + : null, + manufacturer: $json->manufacturer ?? null, + max_active_codes_supported: $json->max_active_codes_supported ?? + null, + max_cooling_set_point_celsius: $json->max_cooling_set_point_celsius ?? + null, + max_cooling_set_point_fahrenheit: $json->max_cooling_set_point_fahrenheit ?? + null, + max_heating_set_point_celsius: $json->max_heating_set_point_celsius ?? + null, + max_heating_set_point_fahrenheit: $json->max_heating_set_point_fahrenheit ?? + null, + max_thermostat_daily_program_periods_per_day: $json->max_thermostat_daily_program_periods_per_day ?? + null, + max_unique_climate_presets_per_thermostat_weekly_program: $json->max_unique_climate_presets_per_thermostat_weekly_program ?? + null, + min_cooling_set_point_celsius: $json->min_cooling_set_point_celsius ?? + null, + min_cooling_set_point_fahrenheit: $json->min_cooling_set_point_fahrenheit ?? + null, + min_heating_cooling_delta_celsius: $json->min_heating_cooling_delta_celsius ?? + null, + min_heating_cooling_delta_fahrenheit: $json->min_heating_cooling_delta_fahrenheit ?? + null, + min_heating_set_point_celsius: $json->min_heating_set_point_celsius ?? + null, + min_heating_set_point_fahrenheit: $json->min_heating_set_point_fahrenheit ?? + null, + minut_metadata: isset($json->minut_metadata) + ? \Seam\Resources\Device\Properties\MinutMetadata::from_json( + $json->minut_metadata, + ) + : null, + nest_metadata: isset($json->nest_metadata) + ? \Seam\Resources\Device\Properties\NestMetadata::from_json( + $json->nest_metadata, + ) + : null, + noise_level_decibels: $json->noise_level_decibels ?? null, + noiseaware_metadata: isset($json->noiseaware_metadata) + ? \Seam\Resources\Device\Properties\NoiseawareMetadata::from_json( + $json->noiseaware_metadata, + ) + : null, + nuki_metadata: isset($json->nuki_metadata) + ? \Seam\Resources\Device\Properties\NukiMetadata::from_json( + $json->nuki_metadata, + ) + : null, + offline_access_codes_enabled: $json->offline_access_codes_enabled ?? + null, + offline_time_frame_options: array_map( + fn( + $o, + ) => \Seam\Resources\Device\Properties\OfflineTimeFrameOptions::from_json( + $o, + ), + $json->offline_time_frame_options ?? [], + ), + omnitec_metadata: isset($json->omnitec_metadata) + ? \Seam\Resources\Device\Properties\OmnitecMetadata::from_json( + $json->omnitec_metadata, + ) + : null, + online_access_codes_enabled: $json->online_access_codes_enabled ?? + null, + online_time_frame_options: array_map( + fn( + $o, + ) => \Seam\Resources\Device\Properties\OnlineTimeFrameOptions::from_json( + $o, + ), + $json->online_time_frame_options ?? [], + ), + relative_humidity: $json->relative_humidity ?? null, + ring_metadata: isset($json->ring_metadata) + ? \Seam\Resources\Device\Properties\RingMetadata::from_json( + $json->ring_metadata, + ) + : null, + salto_ks_metadata: isset($json->salto_ks_metadata) + ? \Seam\Resources\Device\Properties\SaltoKsMetadata::from_json( + $json->salto_ks_metadata, + ) + : null, + salto_metadata: isset($json->salto_metadata) + ? \Seam\Resources\Device\Properties\SaltoMetadata::from_json( + $json->salto_metadata, + ) + : null, + salto_space_credential_service_metadata: isset( + $json->salto_space_credential_service_metadata, + ) + ? \Seam\Resources\Device\Properties\SaltoSpaceCredentialServiceMetadata::from_json( + $json->salto_space_credential_service_metadata, + ) + : null, + schlage_metadata: isset($json->schlage_metadata) + ? \Seam\Resources\Device\Properties\SchlageMetadata::from_json( + $json->schlage_metadata, + ) + : null, + seam_bridge_metadata: isset($json->seam_bridge_metadata) + ? \Seam\Resources\Device\Properties\SeamBridgeMetadata::from_json( + $json->seam_bridge_metadata, + ) + : null, + sensi_metadata: isset($json->sensi_metadata) + ? \Seam\Resources\Device\Properties\SensiMetadata::from_json( + $json->sensi_metadata, + ) + : null, + serial_number: $json->serial_number ?? null, + smartthings_metadata: isset($json->smartthings_metadata) + ? \Seam\Resources\Device\Properties\SmartthingsMetadata::from_json( + $json->smartthings_metadata, + ) + : null, + supported_code_lengths: $json->supported_code_lengths ?? null, + supports_accessory_keypad: $json->supports_accessory_keypad ?? + null, + supports_backup_access_code_pool: $json->supports_backup_access_code_pool ?? + null, + supports_offline_access_codes: $json->supports_offline_access_codes ?? + null, + tado_metadata: isset($json->tado_metadata) + ? \Seam\Resources\Device\Properties\TadoMetadata::from_json( + $json->tado_metadata, + ) + : null, + tedee_metadata: isset($json->tedee_metadata) + ? \Seam\Resources\Device\Properties\TedeeMetadata::from_json( + $json->tedee_metadata, + ) + : null, + temperature_celsius: $json->temperature_celsius ?? null, + temperature_fahrenheit: $json->temperature_fahrenheit ?? null, + temperature_threshold: isset($json->temperature_threshold) + ? \Seam\Resources\Device\Properties\TemperatureThreshold::from_json( + $json->temperature_threshold, + ) + : null, + thermostat_daily_program_period_precision_minutes: $json->thermostat_daily_program_period_precision_minutes ?? + null, + thermostat_daily_programs: array_map( + fn( + $t, + ) => \Seam\Resources\Device\Properties\ThermostatDailyPrograms::from_json( + $t, + ), + $json->thermostat_daily_programs ?? [], + ), + thermostat_weekly_program: isset( + $json->thermostat_weekly_program, + ) + ? \Seam\Resources\Device\Properties\ThermostatWeeklyProgram::from_json( + $json->thermostat_weekly_program, + ) + : null, + ttlock_metadata: isset($json->ttlock_metadata) + ? \Seam\Resources\Device\Properties\TtlockMetadata::from_json( + $json->ttlock_metadata, + ) + : null, + two_n_metadata: isset($json->two_n_metadata) + ? \Seam\Resources\Device\Properties\TwoNMetadata::from_json( + $json->two_n_metadata, + ) + : null, + ultraloq_metadata: isset($json->ultraloq_metadata) + ? \Seam\Resources\Device\Properties\UltraloqMetadata::from_json( + $json->ultraloq_metadata, + ) + : null, + visionline_metadata: isset($json->visionline_metadata) + ? \Seam\Resources\Device\Properties\VisionlineMetadata::from_json( + $json->visionline_metadata, + ) + : null, + wyze_metadata: isset($json->wyze_metadata) + ? \Seam\Resources\Device\Properties\WyzeMetadata::from_json( + $json->wyze_metadata, + ) + : null, + yacan_metadata: isset($json->yacan_metadata) + ? \Seam\Resources\Device\Properties\YacanMetadata::from_json( + $json->yacan_metadata, + ) + : null, + ); + } + + public function __construct( + /** + * Appearance-related properties, as reported by the device. + */ + public \Seam\Resources\Device\Properties\Appearance|null $appearance, + /** + * Device model-related properties. + */ + public \Seam\Resources\Device\Properties\Model|null $model, + /** + * Name of the device. + * + * @deprecated use device.display_name instead + */ + public string|null $name, + /** + * Indicates whether the device is online. + */ + public bool|null $online, + /** + * Accessory keypad properties and state. + */ + public \Seam\Resources\Device\Properties\AccessoryKeypad|null $accessory_keypad = null, + /** + * Active [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + * + * @deprecated Use `active_thermostat_schedule_id` with `/thermostats/schedules/get` instead. + */ + public \Seam\Resources\Device\Properties\ActiveThermostatSchedule|null $active_thermostat_schedule = null, + /** + * ID of the active [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + public string|null $active_thermostat_schedule_id = null, + /** + * Metadata for an Akiles device. + */ + public \Seam\Resources\Device\Properties\AkilesMetadata|null $akiles_metadata = null, + /** + * Metadata for an Aqara device. + */ + public \Seam\Resources\Device\Properties\AqaraMetadata|null $aqara_metadata = null, + /** + * ASSA ABLOY Credential Service metadata for the phone. + */ + public \Seam\Resources\Device\Properties\AssaAbloyCredentialServiceMetadata|null $assa_abloy_credential_service_metadata = null, + /** + * Metadata for an ASSA ABLOY Vostio system. + */ + public \Seam\Resources\Device\Properties\AssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata = null, + /** + * Metadata for an August device. + */ + public \Seam\Resources\Device\Properties\AugustMetadata|null $august_metadata = null, + /** + * The delay in seconds before the lock automatically locks after being unlocked. + */ + public float|null $auto_lock_delay_seconds = null, + /** + * Indicates whether automatic locking is enabled. + */ + public bool|null $auto_lock_enabled = null, + /** + * Climate preset modes that the thermostat supports, such as "home", "away", "wake", "sleep", "occupied", and "unoccupied". + * + * @var list|null + */ + public array|null $available_climate_preset_modes = null, + /** + * Available [climate presets](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) for the thermostat. + * + * @var list<\Seam\Resources\Device\Properties\AvailableClimatePresets>|null + */ + public array|null $available_climate_presets = null, + /** + * Fan mode settings that the thermostat supports. + * + * @var list|null + */ + public array|null $available_fan_mode_settings = null, + /** + * HVAC mode settings that the thermostat supports. + * + * @var list|null + */ + public array|null $available_hvac_mode_settings = null, + /** + * Metadata for an Avigilon Alta system. + */ + public \Seam\Resources\Device\Properties\AvigilonAltaMetadata|null $avigilon_alta_metadata = null, + /** + * Indicates whether the [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) is currently enabled for the device. To disable it, set this to `false` using [/devices/update](https://docs.seam.co/api/devices/update). + */ + public bool|null $backup_access_code_pool_enabled = null, + /** + * Represents the current status of the battery charge level. + */ + public \Seam\Resources\Device\Properties\Battery|null $battery = null, + /** + * Indicates the battery level of the device as a decimal value between 0 and 1, inclusive. + */ + public float|null $battery_level = null, + /** + * Metadata for a Brivo device. + */ + public \Seam\Resources\Device\Properties\BrivoMetadata|null $brivo_metadata = null, + /** + * Constraints on access codes for the device. Seam represents each constraint as an object with a `constraint_type` property. Depending on the constraint type, there may also be additional properties. Note that some constraints are manufacturer- or device-specific. + * + * @var list<\Seam\Resources\Device\Properties\CodeConstraints>|null + */ + public array|null $code_constraints = null, + /** + * Metadata for a ControlByWeb device. + */ + public \Seam\Resources\Device\Properties\ControlbywebMetadata|null $controlbyweb_metadata = null, + /** + * Current climate setting. + */ + public \Seam\Resources\Device\Properties\CurrentClimateSetting|null $current_climate_setting = null, + /** + * Array of noise threshold IDs that are currently triggering. + * + * @var list|null + */ + public array|null $currently_triggering_noise_threshold_ids = null, + /** + * @deprecated use fallback_climate_preset_key to specify a fallback climate preset instead. + */ + public \Seam\Resources\Device\Properties\DefaultClimateSetting|null $default_climate_setting = null, + /** + * Indicates whether the door is open. + */ + public bool|null $door_open = null, + /** + * Metadata for a dormakaba Oracode device. + */ + public \Seam\Resources\Device\Properties\DormakabaOracodeMetadata|null $dormakaba_oracode_metadata = null, + /** + * Metadata for an ecobee device. + */ + public \Seam\Resources\Device\Properties\EcobeeMetadata|null $ecobee_metadata = null, + /** + * Key of the [fallback climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets/setting-the-fallback-climate-preset) for the thermostat. + */ + public string|null $fallback_climate_preset_key = null, + /** + * @var value-of<\Seam\Resources\Device\Properties\FanModeSetting>|string|null + * @deprecated Use `current_climate_setting.fan_mode_setting` instead. + */ + public string|null $fan_mode_setting = null, + /** + * Metadata for a 4SUITES device. + */ + public \Seam\Resources\Device\Properties\FourSuitesMetadata|null $four_suites_metadata = null, + /** + * Metadata for a Genie device. + */ + public \Seam\Resources\Device\Properties\GenieMetadata|null $genie_metadata = null, + /** + * Indicates whether the device has direct power. + */ + public bool|null $has_direct_power = null, + /** + * Indicates whether the device supports native entry events. + */ + public bool|null $has_native_entry_events = null, + /** + * Metadata for a Honeywell Resideo device. + */ + public \Seam\Resources\Device\Properties\HoneywellResideoMetadata|null $honeywell_resideo_metadata = null, + /** + * Metadata for an igloo device. + */ + public \Seam\Resources\Device\Properties\IglooMetadata|null $igloo_metadata = null, + /** + * Metadata for an igloohome device. + */ + public \Seam\Resources\Device\Properties\IgloohomeMetadata|null $igloohome_metadata = null, + /** + * Alt text for the device image. + */ + public string|null $image_alt_text = null, + /** + * Image URL for the device. + */ + public string|null $image_url = null, + /** + * Indicates whether the connected HVAC system is currently cooling, as reported by the thermostat. + */ + public bool|null $is_cooling = null, + /** + * Indicates whether the fan in the connected HVAC system is currently running, as reported by the thermostat. + */ + public bool|null $is_fan_running = null, + /** + * Indicates whether the connected HVAC system is currently heating, as reported by the thermostat. + */ + public bool|null $is_heating = null, + /** + * Indicates whether the current thermostat settings differ from the most recent active program or schedule that Seam activated. For this condition to occur, `current_climate_setting.manual_override_allowed` must also be `true`. + */ + public bool|null $is_temporary_manual_override_active = null, + /** + * Metadata for a KeyNest device. + */ + public \Seam\Resources\Device\Properties\KeynestMetadata|null $keynest_metadata = null, + /** + * Keypad battery status. + */ + public \Seam\Resources\Device\Properties\KeypadBattery|null $keypad_battery = null, + /** + * Metadata for a Kisi device. + */ + public \Seam\Resources\Device\Properties\KisiMetadata|null $kisi_metadata = null, + /** + * Metadata for a Korelock device. + */ + public \Seam\Resources\Device\Properties\KorelockMetadata|null $korelock_metadata = null, + /** + * Metadata for a Kwikset device. + */ + public \Seam\Resources\Device\Properties\KwiksetMetadata|null $kwikset_metadata = null, + /** + * Indicates whether the lock is locked. + */ + public bool|null $locked = null, + /** + * Metadata for a Lockly device. + */ + public \Seam\Resources\Device\Properties\LocklyMetadata|null $lockly_metadata = null, + /** + * Manufacturer of the device. When a device, such as a smart lock, is connected through a smart hub, the manufacturer of the device might be different from that of the smart hub. + */ + public string|null $manufacturer = null, + /** + * Maximum number of active access codes that the device supports. + */ + public float|null $max_active_codes_supported = null, + /** + * Maximum [cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#cooling-set-point) in °C. + */ + public float|null $max_cooling_set_point_celsius = null, + /** + * Maximum [cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#cooling-set-point) in °F. + */ + public float|null $max_cooling_set_point_fahrenheit = null, + /** + * Maximum [heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#heating-set-point) in °C. + */ + public float|null $max_heating_set_point_celsius = null, + /** + * Maximum [heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#heating-set-point) in °F. + */ + public float|null $max_heating_set_point_fahrenheit = null, + /** + * Maximum number of periods that the thermostat can support per day. For example, if the thermostat supports 4 periods per day, this value is 4. + */ + public float|null $max_thermostat_daily_program_periods_per_day = null, + /** + * Maximum number of climate presets that the thermostat can support for weekly programming. + */ + public float|null $max_unique_climate_presets_per_thermostat_weekly_program = null, + /** + * Minimum [cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#cooling-set-point) in °C. + */ + public float|null $min_cooling_set_point_celsius = null, + /** + * Minimum [cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#cooling-set-point) in °F. + */ + public float|null $min_cooling_set_point_fahrenheit = null, + /** + * Minimum [temperature difference](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#minimum-heating-cooling-temperature-delta) in °C between the cooling and heating set points when in heat-cool (auto) mode. + */ + public float|null $min_heating_cooling_delta_celsius = null, + /** + * Minimum [temperature difference](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#minimum-heating-cooling-temperature-delta) in °F between the cooling and heating set points when in heat-cool (auto) mode. + */ + public float|null $min_heating_cooling_delta_fahrenheit = null, + /** + * Minimum [heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#heating-set-point) in °C. + */ + public float|null $min_heating_set_point_celsius = null, + /** + * Minimum [heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#heating-set-point) in °F. + */ + public float|null $min_heating_set_point_fahrenheit = null, + /** + * Metadata for a Minut device. + */ + public \Seam\Resources\Device\Properties\MinutMetadata|null $minut_metadata = null, + /** + * Metadata for a Google Nest device. + */ + public \Seam\Resources\Device\Properties\NestMetadata|null $nest_metadata = null, + /** + * Indicates current noise level in decibels, if the device supports noise detection. + */ + public float|null $noise_level_decibels = null, + /** + * Metadata for a NoiseAware device. + */ + public \Seam\Resources\Device\Properties\NoiseawareMetadata|null $noiseaware_metadata = null, + /** + * Metadata for a Nuki device. + */ + public \Seam\Resources\Device\Properties\NukiMetadata|null $nuki_metadata = null, + /** + * Indicates whether it is currently possible to use offline access codes for the device. + * + * @deprecated use device.can_program_offline_access_codes + */ + public bool|null $offline_access_codes_enabled = null, + /** + * Time frames that may be requested when creating an offline access code, expressed as a list of options. The caller picks one option (by matching the requested duration when the options' duration ranges do not overlap, or by `display_name` when they do) and satisfies that one option's rules. When `undefined`, any time frame works. + * + * @var list<\Seam\Resources\Device\Properties\OfflineTimeFrameOptions>|null + */ + public array|null $offline_time_frame_options = null, + /** + * Metadata for an Omnitec device. + */ + public \Seam\Resources\Device\Properties\OmnitecMetadata|null $omnitec_metadata = null, + /** + * Indicates whether it is currently possible to use online access codes for the device. + * + * @deprecated use device.can_program_online_access_codes + */ + public bool|null $online_access_codes_enabled = null, + /** + * Time frames that may be requested when creating an online access code, expressed as a list of options. The caller picks one option (by matching the requested duration when the options' duration ranges do not overlap, or by `display_name` when they do) and satisfies that one option's rules. When `undefined`, any time frame works. + * + * @var list<\Seam\Resources\Device\Properties\OnlineTimeFrameOptions>|null + */ + public array|null $online_time_frame_options = null, + /** + * Reported relative humidity, as a value between 0 and 1, inclusive. + */ + public float|null $relative_humidity = null, + /** + * Metadata for a Ring device. + */ + public \Seam\Resources\Device\Properties\RingMetadata|null $ring_metadata = null, + /** + * Metadata for a Salto KS device. + */ + public \Seam\Resources\Device\Properties\SaltoKsMetadata|null $salto_ks_metadata = null, + /** + * Metada for a Salto device. + * + * @deprecated Use `salto_ks_metadata` instead. + */ + public \Seam\Resources\Device\Properties\SaltoMetadata|null $salto_metadata = null, + /** + * Salto Space credential service metadata for the phone. + */ + public \Seam\Resources\Device\Properties\SaltoSpaceCredentialServiceMetadata|null $salto_space_credential_service_metadata = null, + /** + * Metadata for a Schlage device. + */ + public \Seam\Resources\Device\Properties\SchlageMetadata|null $schlage_metadata = null, + /** + * Metadata for Seam Bridge. + */ + public \Seam\Resources\Device\Properties\SeamBridgeMetadata|null $seam_bridge_metadata = null, + /** + * Metadata for a Sensi device. + */ + public \Seam\Resources\Device\Properties\SensiMetadata|null $sensi_metadata = null, + /** + * Serial number of the device. + */ + public string|null $serial_number = null, + /** + * Metadata for a SmartThings device. + */ + public \Seam\Resources\Device\Properties\SmartthingsMetadata|null $smartthings_metadata = null, + /** + * Supported code lengths for access codes. + * + * @var list|null + */ + public array|null $supported_code_lengths = null, + /** + * @deprecated use device.properties.model.can_connect_accessory_keypad + */ + public bool|null $supports_accessory_keypad = null, + /** + * Indicates whether the device supports a [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes). + */ + public bool|null $supports_backup_access_code_pool = null, + /** + * @deprecated use offline_access_codes_enabled + */ + public bool|null $supports_offline_access_codes = null, + /** + * Metadata for a tado° device. + */ + public \Seam\Resources\Device\Properties\TadoMetadata|null $tado_metadata = null, + /** + * Metadata for a Tedee device. + */ + public \Seam\Resources\Device\Properties\TedeeMetadata|null $tedee_metadata = null, + /** + * Reported temperature in °C. + */ + public float|null $temperature_celsius = null, + /** + * Reported temperature in °F. + */ + public float|null $temperature_fahrenheit = null, + /** + * Current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. + */ + public \Seam\Resources\Device\Properties\TemperatureThreshold|null $temperature_threshold = null, + /** + * Precision of the thermostat's period in minutes. For example, if the thermostat supports 15-minute periods, this value is 15. All values are relative to the top of the hour, so for 15 minutes, the periods would be 0, 15, 30, and 45 minutes past the hour. + */ + public float|null $thermostat_daily_program_period_precision_minutes = null, + /** + * Configured [daily programs](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-programs) for the thermostat. + * + * @var list<\Seam\Resources\Device\Properties\ThermostatDailyPrograms>|null + */ + public array|null $thermostat_daily_programs = null, + /** + * Current [weekly program](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-programs) for the thermostat. + */ + public \Seam\Resources\Device\Properties\ThermostatWeeklyProgram|null $thermostat_weekly_program = null, + /** + * Metadata for a TTLock device. + */ + public \Seam\Resources\Device\Properties\TtlockMetadata|null $ttlock_metadata = null, + /** + * Metadata for a 2N device. + */ + public \Seam\Resources\Device\Properties\TwoNMetadata|null $two_n_metadata = null, + /** + * Metadata for an Ultraloq device. + */ + public \Seam\Resources\Device\Properties\UltraloqMetadata|null $ultraloq_metadata = null, + /** + * Metadata for an ASSA ABLOY Visionline system. + */ + public \Seam\Resources\Device\Properties\VisionlineMetadata|null $visionline_metadata = null, + /** + * Metadata for a Wyze device. + */ + public \Seam\Resources\Device\Properties\WyzeMetadata|null $wyze_metadata = null, + /** + * Metadata for a Yacan device. + */ + public \Seam\Resources\Device\Properties\YacanMetadata|null $yacan_metadata = null, + ) {} + } + + /** + * Array of warnings associated with the device. Each warning object within the array contains two fields: `warning_code` and `message`. `warning_code` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\Device\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\Device\Warnings\WarningCode::PARTIAL_BACKUP_ACCESS_CODE_POOL + => \Seam\Resources\Device\Warnings\PartialBackupAccessCodePool::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::MANY_ACTIVE_BACKUP_CODES + => \Seam\Resources\Device\Warnings\ManyActiveBackupCodes::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::THIRD_PARTY_INTEGRATION_DETECTED + => \Seam\Resources\Device\Warnings\ThirdPartyIntegrationDetected::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::TTLOCK_LOCK_GATEWAY_UNLOCKING_NOT_ENABLED + => \Seam\Resources\Device\Warnings\TtlockLockGatewayUnlockingNotEnabled::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::TTLOCK_WEAK_GATEWAY_SIGNAL + => \Seam\Resources\Device\Warnings\TtlockWeakGatewaySignal::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::POWER_SAVING_MODE + => \Seam\Resources\Device\Warnings\PowerSavingMode::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::TEMPERATURE_THRESHOLD_EXCEEDED + => \Seam\Resources\Device\Warnings\TemperatureThresholdExceeded::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::DEVICE_COMMUNICATION_DEGRADED + => \Seam\Resources\Device\Warnings\DeviceCommunicationDegraded::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::SCHEDULED_MAINTENANCE_WINDOW + => \Seam\Resources\Device\Warnings\ScheduledMaintenanceWindow::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::DEVICE_HAS_FLAKY_CONNECTION + => \Seam\Resources\Device\Warnings\DeviceHasFlakyConnection::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::SALTO_KS_OFFICE_MODE + => \Seam\Resources\Device\Warnings\SaltoKsOfficeMode::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::SALTO_KS_PRIVACY_MODE + => \Seam\Resources\Device\Warnings\SaltoKsPrivacyMode::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::PRIVACY_MODE + => \Seam\Resources\Device\Warnings\PrivacyMode::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::SALTO_KS_SUBSCRIPTION_LIMIT_ALMOST_REACHED + => \Seam\Resources\Device\Warnings\SaltoKsSubscriptionLimitAlmostReached::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::SALTO_KS_LOCK_ACCESS_CODE_SUPPORT_REMOVED + => \Seam\Resources\Device\Warnings\SaltoKsLockAccessCodeSupportRemoved::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::UNKNOWN_ISSUE_WITH_PHONE + => \Seam\Resources\Device\Warnings\UnknownIssueWithPhone::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::LOCKLY_TIME_ZONE_NOT_CONFIGURED + => \Seam\Resources\Device\Warnings\LocklyTimeZoneNotConfigured::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::ULTRALOQ_TIME_ZONE_UNKNOWN + => \Seam\Resources\Device\Warnings\UltraloqTimeZoneUnknown::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::TIME_ZONE_UNKNOWN + => \Seam\Resources\Device\Warnings\TimeZoneUnknown::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::TIME_ZONE_MISMATCH + => \Seam\Resources\Device\Warnings\TimeZoneMismatch::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::TWO_N_DEVICE_MISSING_TIMEZONE + => \Seam\Resources\Device\Warnings\TwoNDeviceMissingTimezone::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::HUB_REQUIRED_FOR_ADDITIONAL_CAPABILITIES + => \Seam\Resources\Device\Warnings\HubRequiredForAdditionalCapabilities::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::PROVIDER_ISSUE + => \Seam\Resources\Device\Warnings\ProviderIssue::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::KEYNEST_UNSUPPORTED_LOCKER + => \Seam\Resources\Device\Warnings\KeynestUnsupportedLocker::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::ACCESSORY_KEYPAD_SETUP_REQUIRED + => \Seam\Resources\Device\Warnings\AccessoryKeypadSetupRequired::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::UNRELIABLE_ONLINE_STATUS + => \Seam\Resources\Device\Warnings\UnreliableOnlineStatus::from_json( + $json, + ), + \Seam\Resources\Device\Warnings\WarningCode::MAX_ACCESS_CODES_REACHED + => \Seam\Resources\Device\Warnings\MaxAccessCodesReached::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } + + enum DeviceType: string + { + case AKUVOX_LOCK = "akuvox_lock"; + case AUGUST_LOCK = "august_lock"; + case BRIVO_ACCESS_POINT = "brivo_access_point"; + case BUTTERFLYMX_PANEL = "butterflymx_panel"; + case AVIGILON_ALTA_ENTRY = "avigilon_alta_entry"; + case DOORKING_LOCK = "doorking_lock"; + case GENIE_DOOR = "genie_door"; + case IGLOO_LOCK = "igloo_lock"; + case LINEAR_LOCK = "linear_lock"; + case LOCKLY_LOCK = "lockly_lock"; + case KWIKSET_LOCK = "kwikset_lock"; + case NUKI_LOCK = "nuki_lock"; + case SALTO_LOCK = "salto_lock"; + case SCHLAGE_LOCK = "schlage_lock"; + case SMARTTHINGS_LOCK = "smartthings_lock"; + case WYZE_LOCK = "wyze_lock"; + case YALE_LOCK = "yale_lock"; + case TWO_N_INTERCOM = "two_n_intercom"; + case CONTROLBYWEB_DEVICE = "controlbyweb_device"; + case TTLOCK_LOCK = "ttlock_lock"; + case IGLOOHOME_LOCK = "igloohome_lock"; + case FOUR_SUITES_DOOR = "four_suites_door"; + case DORMAKABA_ORACODE_DOOR = "dormakaba_oracode_door"; + case TEDEE_LOCK = "tedee_lock"; + case AKILES_LOCK = "akiles_lock"; + case ULTRALOQ_LOCK = "ultraloq_lock"; + case YACAN_LOCK = "yacan_lock"; + case KEYINCODE_LOCK = "keyincode_lock"; + case OMNITEC_LOCK = "omnitec_lock"; + case KISI_LOCK = "kisi_lock"; + case AQARA_LOCK = "aqara_lock"; + case KEYNEST_KEY = "keynest_key"; + case NOISEAWARE_ACTIVITY_ZONE = "noiseaware_activity_zone"; + case MINUT_SENSOR = "minut_sensor"; + case ECOBEE_THERMOSTAT = "ecobee_thermostat"; + case NEST_THERMOSTAT = "nest_thermostat"; + case HONEYWELL_RESIDEO_THERMOSTAT = "honeywell_resideo_thermostat"; + case TADO_THERMOSTAT = "tado_thermostat"; + case SENSI_THERMOSTAT = "sensi_thermostat"; + case SMARTTHINGS_THERMOSTAT = "smartthings_thermostat"; + case IOS_PHONE = "ios_phone"; + case ANDROID_PHONE = "android_phone"; + case RING_CAMERA = "ring_camera"; + } +} + +namespace Seam\Resources\Device\Errors { + /** + * Indicates that the account is disconnected. + */ + final class AccountDisconnected extends \Seam\Resources\Device\Errors + { + public static function from_json(mixed $json): AccountDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the Salto site user limit has been reached. + */ + final class SaltoKsSubscriptionLimitExceeded extends + \Seam\Resources\Device\Errors + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that Seam's integration user does not have sufficient permissions on the provider's system to which this device belongs, so Seam cannot manage access codes or unlock the device. See the error message for specifics, then either reauthorize the connected account in Seam or grant the integration user the required permissions in the provider's system. + */ + final class InsufficientPermissions extends \Seam\Resources\Device\Errors + { + public static function from_json( + mixed $json, + ): InsufficientPermissions|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that one or more dormakaba sites associated with the connected account could not be connected. Contact dormakaba support. + */ + final class DormakabaSitesDisconnected extends \Seam\Resources\Device\Errors + { + public static function from_json( + mixed $json, + ): DormakabaSitesDisconnected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the device is offline. + */ + final class DeviceOffline extends \Seam\Resources\Device\Errors + { + public static function from_json(mixed $json): DeviceOffline|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the device has been removed. + */ + final class DeviceRemoved extends \Seam\Resources\Device\Errors + { + public static function from_json(mixed $json): DeviceRemoved|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the hub is disconnected. + */ + final class HubDisconnected extends \Seam\Resources\Device\Errors + { + public static function from_json(mixed $json): HubDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the device is disconnected. + */ + final class DeviceDisconnected extends \Seam\Resources\Device\Errors + { + public static function from_json(mixed $json): DeviceDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) is empty. + */ + final class EmptyBackupAccessCodePool extends \Seam\Resources\Device\Errors + { + public static function from_json( + mixed $json, + ): EmptyBackupAccessCodePool|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the user is not authorized to use the August lock. + */ + final class AugustLockNotAuthorized extends \Seam\Resources\Device\Errors + { + public static function from_json( + mixed $json, + ): AugustLockNotAuthorized|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that device credentials are missing. + */ + final class MissingDeviceCredentials extends \Seam\Resources\Device\Errors + { + public static function from_json( + mixed $json, + ): MissingDeviceCredentials|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the auxiliary heat is running. + */ + final class AuxiliaryHeatRunning extends \Seam\Resources\Device\Errors + { + public static function from_json(mixed $json): AuxiliaryHeatRunning|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that a subscription is required to connect. + */ + final class SubscriptionRequired extends \Seam\Resources\Device\Errors + { + public static function from_json(mixed $json): SubscriptionRequired|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the Seam API cannot communicate with [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge), for example, if the Seam Bridge executable has stopped or if the computer running the Seam Bridge executable is offline. See also [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system#acs_system-errors-seam_bridge_disconnected). + */ + final class BridgeDisconnected extends \Seam\Resources\Device\Errors + { + public static function from_json(mixed $json): BridgeDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + public bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + public bool|null $is_connected_account_error = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + enum ErrorCode: string + { + case ACCOUNT_DISCONNECTED = "account_disconnected"; + case SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED = "salto_ks_subscription_limit_exceeded"; + case INSUFFICIENT_PERMISSIONS = "insufficient_permissions"; + case DORMAKABA_SITES_DISCONNECTED = "dormakaba_sites_disconnected"; + case DEVICE_OFFLINE = "device_offline"; + case DEVICE_REMOVED = "device_removed"; + case HUB_DISCONNECTED = "hub_disconnected"; + case DEVICE_DISCONNECTED = "device_disconnected"; + case EMPTY_BACKUP_ACCESS_CODE_POOL = "empty_backup_access_code_pool"; + case AUGUST_LOCK_NOT_AUTHORIZED = "august_lock_not_authorized"; + case MISSING_DEVICE_CREDENTIALS = "missing_device_credentials"; + case AUXILIARY_HEAT_RUNNING = "auxiliary_heat_running"; + case SUBSCRIPTION_REQUIRED = "subscription_required"; + case BRIDGE_DISCONNECTED = "bridge_disconnected"; + } +} + +namespace Seam\Resources\Device\Properties { + /** + * Accessory keypad properties and state. + */ + class AccessoryKeypad + { + public static function from_json(mixed $json): AccessoryKeypad|null + { + if (!$json) { + return null; + } + return new self( + is_connected: $json->is_connected ?? null, + battery: isset($json->battery) + ? \Seam\Resources\Device\Properties\AccessoryKeypad\Battery::from_json( + $json->battery, + ) + : null, + ); + } + + public function __construct( + /** + * Indicates if an accessory keypad is connected to the device. + */ + public bool|null $is_connected, + /** + * Keypad battery properties. + */ + public \Seam\Resources\Device\Properties\AccessoryKeypad\Battery|null $battery = null, + ) {} + } + + /** + * Appearance-related properties, as reported by the device. + */ + class Appearance + { + public static function from_json(mixed $json): Appearance|null + { + if (!$json) { + return null; + } + return new self(name: $json->name ?? null); + } + + public function __construct( + /** + * Name of the device as seen from the provider API and application, not settable through Seam. + */ + public string|null $name, + ) {} + } + + /** + * Represents the current status of the battery charge level. + */ + class Battery + { + public static function from_json(mixed $json): Battery|null + { + if (!$json) { + return null; + } + return new self( + level: $json->level ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * Battery charge level as a value between 0 and 1, inclusive. + */ + public float|null $level, + /** + * Represents the current status of the battery charge level. Values are `critical`, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; `low`, which signifies that the battery is under the preferred threshold and should be charged soon; `good`, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and `full`, which represents a battery that is fully charged, providing the maximum duration of usage. + * + * @var value-of<\Seam\Resources\Device\Properties\Battery\Status>|string|null + */ + public string|null $status, + ) {} + } + + /** + * Device model-related properties. + */ + class Model + { + public static function from_json(mixed $json): Model|null + { + if (!$json) { + return null; + } + return new self( + display_name: $json->display_name ?? null, + manufacturer_display_name: $json->manufacturer_display_name ?? + null, + accessory_keypad_supported: $json->accessory_keypad_supported ?? + null, + can_connect_accessory_keypad: $json->can_connect_accessory_keypad ?? + null, + has_built_in_keypad: $json->has_built_in_keypad ?? null, + offline_access_codes_supported: $json->offline_access_codes_supported ?? + null, + online_access_codes_supported: $json->online_access_codes_supported ?? + null, + ); + } + + public function __construct( + /** + * Display name of the device model. + */ + public string|null $display_name, + /** + * Display name that corresponds to the manufacturer-specific terminology for the device. + */ + public string|null $manufacturer_display_name, + /** + * @deprecated use device.properties.model.can_connect_accessory_keypad + */ + public bool|null $accessory_keypad_supported = null, + /** + * Indicates whether the device can connect a accessory keypad. + */ + public bool|null $can_connect_accessory_keypad = null, + /** + * Indicates whether the device has a built in accessory keypad. + */ + public bool|null $has_built_in_keypad = null, + /** + * @deprecated use device.can_program_offline_access_codes. + */ + public bool|null $offline_access_codes_supported = null, + /** + * @deprecated use device.can_program_online_access_codes. + */ + public bool|null $online_access_codes_supported = null, + ) {} + } + + /** + * ASSA ABLOY Credential Service metadata for the phone. + */ + class AssaAbloyCredentialServiceMetadata + { + public static function from_json( + mixed $json, + ): AssaAbloyCredentialServiceMetadata|null { + if (!$json) { + return null; + } + return new self( + endpoints: array_map( + fn( + $e, + ) => \Seam\Resources\Device\Properties\AssaAbloyCredentialServiceMetadata\Endpoints::from_json( + $e, + ), + $json->endpoints ?? [], + ), + has_active_endpoint: $json->has_active_endpoint ?? null, + ); + } + + public function __construct( + /** + * Endpoints associated with the phone. + * + * @var list<\Seam\Resources\Device\Properties\AssaAbloyCredentialServiceMetadata\Endpoints>|null + */ + public array|null $endpoints = null, + /** + * Indicates whether the credential service has active endpoints associated with the phone. + */ + public bool|null $has_active_endpoint = null, + ) {} + } + + /** + * Salto Space credential service metadata for the phone. + */ + class SaltoSpaceCredentialServiceMetadata + { + public static function from_json( + mixed $json, + ): SaltoSpaceCredentialServiceMetadata|null { + if (!$json) { + return null; + } + return new self(has_active_phone: $json->has_active_phone ?? null); + } + + public function __construct( + /** + * Indicates whether the credential service has an active associated phone. + */ + public bool|null $has_active_phone = null, + ) {} + } + + /** + * Metadata for an Akiles device. + */ + class AkilesMetadata + { + public static function from_json(mixed $json): AkilesMetadata|null + { + if (!$json) { + return null; + } + return new self( + _member_group_id: $json->_member_group_id ?? null, + gadget_id: $json->gadget_id ?? null, + gadget_name: $json->gadget_name ?? null, + product_name: $json->product_name ?? null, + ); + } + + public function __construct( + /** + * Group ID to which to add users for an Akiles device. + */ + public string|null $_member_group_id = null, + /** + * Gadget ID for an Akiles device. + */ + public string|null $gadget_id = null, + /** + * Gadget name for an Akiles device. + */ + public string|null $gadget_name = null, + /** + * Product name for an Akiles device. + */ + public string|null $product_name = null, + ) {} + } + + /** + * Metadata for an Aqara device. + */ + class AqaraMetadata + { + public static function from_json(mixed $json): AqaraMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_name: $json->device_name ?? null, + did: $json->did ?? null, + firmware_version: $json->firmware_version ?? null, + model: $json->model ?? null, + model_type: $json->model_type ?? null, + parent_did: $json->parent_did ?? null, + position_id: $json->position_id ?? null, + time_zone: $json->time_zone ?? null, + ); + } + + public function __construct( + /** + * Device name for an Aqara device. + */ + public string|null $device_name = null, + /** + * Device ID (did) for an Aqara device. + */ + public string|null $did = null, + /** + * Firmware version for an Aqara device. + */ + public string|null $firmware_version = null, + /** + * Model identifier for an Aqara device. + */ + public string|null $model = null, + /** + * Model type for an Aqara device. + */ + public float|null $model_type = null, + /** + * Parent gateway device ID for an Aqara device. + */ + public string|null $parent_did = null, + /** + * Position (room) ID for an Aqara device. + */ + public string|null $position_id = null, + /** + * Time zone reported for an Aqara device (e.g. GMT-07:00). + */ + public string|null $time_zone = null, + ) {} + } + + /** + * Metadata for an ASSA ABLOY Vostio system. + */ + class AssaAbloyVostioMetadata + { + public static function from_json( + mixed $json, + ): AssaAbloyVostioMetadata|null { + if (!$json) { + return null; + } + return new self(encoder_name: $json->encoder_name ?? null); + } + + public function __construct( + /** + * Encoder name for an ASSA ABLOY Vostio system. + */ + public string|null $encoder_name = null, + ) {} + } + + /** + * Metadata for an August device. + */ + class AugustMetadata + { + public static function from_json(mixed $json): AugustMetadata|null + { + if (!$json) { + return null; + } + return new self( + has_keypad: $json->has_keypad ?? null, + house_id: $json->house_id ?? null, + house_name: $json->house_name ?? null, + keypad_battery_level: $json->keypad_battery_level ?? null, + lock_id: $json->lock_id ?? null, + lock_name: $json->lock_name ?? null, + model: $json->model ?? null, + ); + } + + public function __construct( + /** + * Indicates whether an August device has a keypad. + */ + public bool|null $has_keypad = null, + /** + * House ID for an August device. + */ + public string|null $house_id = null, + /** + * House name for an August device. + */ + public string|null $house_name = null, + /** + * Keypad battery level for an August device. + */ + public string|null $keypad_battery_level = null, + /** + * Lock ID for an August device. + */ + public string|null $lock_id = null, + /** + * Lock name for an August device. + */ + public string|null $lock_name = null, + /** + * Model for an August device. + */ + public string|null $model = null, + ) {} + } + + /** + * Metadata for an Avigilon Alta system. + */ + class AvigilonAltaMetadata + { + public static function from_json(mixed $json): AvigilonAltaMetadata|null + { + if (!$json) { + return null; + } + return new self( + entry_name: $json->entry_name ?? null, + entry_relays_total_count: $json->entry_relays_total_count ?? + null, + org_name: $json->org_name ?? null, + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + zone_id: $json->zone_id ?? null, + zone_name: $json->zone_name ?? null, + ); + } + + public function __construct( + /** + * Entry name for an Avigilon Alta system. + */ + public string|null $entry_name = null, + /** + * Total count of entry relays for an Avigilon Alta system. + */ + public float|null $entry_relays_total_count = null, + /** + * Organization name for an Avigilon Alta system. + */ + public string|null $org_name = null, + /** + * Site ID for an Avigilon Alta system. + */ + public float|null $site_id = null, + /** + * Site name for an Avigilon Alta system. + */ + public string|null $site_name = null, + /** + * Zone ID for an Avigilon Alta system. + */ + public float|null $zone_id = null, + /** + * Zone name for an Avigilon Alta system. + */ + public string|null $zone_name = null, + ) {} + } + + /** + * Metadata for a Brivo device. + */ + class BrivoMetadata + { + public static function from_json(mixed $json): BrivoMetadata|null + { + if (!$json) { + return null; + } + return new self( + activation_enabled: $json->activation_enabled ?? null, + device_name: $json->device_name ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the Brivo access point has activation (remote unlock) enabled. + */ + public bool|null $activation_enabled = null, + /** + * Device name for a Brivo device. + */ + public string|null $device_name = null, + ) {} + } + + /** + * Metadata for a ControlByWeb device. + */ + class ControlbywebMetadata + { + public static function from_json(mixed $json): ControlbywebMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + relay_name: $json->relay_name ?? null, + ); + } + + public function __construct( + /** + * Device ID for a ControlByWeb device. + */ + public string|null $device_id = null, + /** + * Device name for a ControlByWeb device. + */ + public string|null $device_name = null, + /** + * Relay name for a ControlByWeb device. + */ + public string|null $relay_name = null, + ) {} + } + + /** + * Metadata for a dormakaba Oracode device. + */ + class DormakabaOracodeMetadata + { + public static function from_json( + mixed $json, + ): DormakabaOracodeMetadata|null { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + door_id: $json->door_id ?? null, + door_is_wireless: $json->door_is_wireless ?? null, + door_name: $json->door_name ?? null, + iana_timezone: $json->iana_timezone ?? null, + predefined_time_slots: array_map( + fn( + $p, + ) => \Seam\Resources\Device\Properties\DormakabaOracodeMetadata\PredefinedTimeSlots::from_json( + $p, + ), + $json->predefined_time_slots ?? [], + ), + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + ); + } + + public function __construct( + /** + * Device ID for a dormakaba Oracode device. + */ + public string|null $device_id = null, + /** + * Door ID for a dormakaba Oracode device. + */ + public float|null $door_id = null, + /** + * Indicates whether a door is wireless for a dormakaba Oracode device. + */ + public bool|null $door_is_wireless = null, + /** + * Door name for a dormakaba Oracode device. + */ + public string|null $door_name = null, + /** + * IANA time zone for a dormakaba Oracode device. + */ + public string|null $iana_timezone = null, + /** + * Predefined time slots for a dormakaba Oracode device. + * + * @var list<\Seam\Resources\Device\Properties\DormakabaOracodeMetadata\PredefinedTimeSlots>|null + */ + public array|null $predefined_time_slots = null, + /** + * Site ID for a dormakaba Oracode device. + * + * @deprecated Previously marked as "@DEPRECATED." + */ + public float|null $site_id = null, + /** + * Site name for a dormakaba Oracode device. + */ + public string|null $site_name = null, + ) {} + } + + /** + * Metadata for an ecobee device. + */ + class EcobeeMetadata + { + public static function from_json(mixed $json): EcobeeMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_name: $json->device_name ?? null, + ecobee_device_id: $json->ecobee_device_id ?? null, + ); + } + + public function __construct( + /** + * Device name for an ecobee device. + */ + public string|null $device_name = null, + /** + * Device ID for an ecobee device. + */ + public string|null $ecobee_device_id = null, + ) {} + } + + /** + * Metadata for a 4SUITES device. + */ + class FourSuitesMetadata + { + public static function from_json(mixed $json): FourSuitesMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + reclose_delay_in_seconds: $json->reclose_delay_in_seconds ?? + null, + ); + } + + public function __construct( + /** + * Device ID for a 4SUITES device. + */ + public float|null $device_id = null, + /** + * Device name for a 4SUITES device. + */ + public string|null $device_name = null, + /** + * Reclose delay, in seconds, for a 4SUITES device. + */ + public float|null $reclose_delay_in_seconds = null, + ) {} + } + + /** + * Metadata for a Genie device. + */ + class GenieMetadata + { + public static function from_json(mixed $json): GenieMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_name: $json->device_name ?? null, + door_name: $json->door_name ?? null, + ); + } + + public function __construct( + /** + * Lock name for a Genie device. + */ + public string|null $device_name = null, + /** + * Door name for a Genie device. + */ + public string|null $door_name = null, + ) {} + } + + /** + * Metadata for a Honeywell Resideo device. + */ + class HoneywellResideoMetadata + { + public static function from_json( + mixed $json, + ): HoneywellResideoMetadata|null { + if (!$json) { + return null; + } + return new self( + device_name: $json->device_name ?? null, + honeywell_resideo_device_id: $json->honeywell_resideo_device_id ?? + null, + ); + } + + public function __construct( + /** + * Device name for a Honeywell Resideo device. + */ + public string|null $device_name = null, + /** + * Device ID for a Honeywell Resideo device. + */ + public string|null $honeywell_resideo_device_id = null, + ) {} + } + + /** + * Metadata for an igloo device. + */ + class IglooMetadata + { + public static function from_json(mixed $json): IglooMetadata|null + { + if (!$json) { + return null; + } + return new self( + bridge_id: $json->bridge_id ?? null, + device_id: $json->device_id ?? null, + model: $json->model ?? null, + ); + } + + public function __construct( + /** + * Bridge ID for an igloo device. + */ + public string|null $bridge_id = null, + /** + * Device ID for an igloo device. + */ + public string|null $device_id = null, + /** + * Model for an igloo device. + */ + public string|null $model = null, + ) {} + } + + /** + * Metadata for an igloohome device. + */ + class IgloohomeMetadata + { + public static function from_json(mixed $json): IgloohomeMetadata|null + { + if (!$json) { + return null; + } + return new self( + bridge_id: $json->bridge_id ?? null, + bridge_name: $json->bridge_name ?? null, + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + is_accessory_keypad_linked_to_bridge: $json->is_accessory_keypad_linked_to_bridge ?? + null, + keypad_id: $json->keypad_id ?? null, + ); + } + + public function __construct( + /** + * Bridge ID for an igloohome device. + */ + public string|null $bridge_id = null, + /** + * Bridge name for an igloohome device. + */ + public string|null $bridge_name = null, + /** + * Device ID for an igloohome device. + */ + public string|null $device_id = null, + /** + * Device name for an igloohome device. + */ + public string|null $device_name = null, + /** + * Indicates whether a keypad is linked to a bridge for an igloohome device. + */ + public bool|null $is_accessory_keypad_linked_to_bridge = null, + /** + * Keypad ID for an igloohome device. + */ + public string|null $keypad_id = null, + ) {} + } + + /** + * Metadata for a KeyNest device. + */ + class KeynestMetadata + { + public static function from_json(mixed $json): KeynestMetadata|null + { + if (!$json) { + return null; + } + return new self( + address: $json->address ?? null, + current_or_last_store_id: $json->current_or_last_store_id ?? + null, + current_status: $json->current_status ?? null, + current_user_company: $json->current_user_company ?? null, + current_user_email: $json->current_user_email ?? null, + current_user_name: $json->current_user_name ?? null, + current_user_phone_number: $json->current_user_phone_number ?? + null, + default_office_id: $json->default_office_id ?? null, + device_name: $json->device_name ?? null, + fob_id: $json->fob_id ?? null, + handover_method: $json->handover_method ?? null, + has_photo: $json->has_photo ?? null, + is_quadient_locker: $json->is_quadient_locker ?? null, + key_id: $json->key_id ?? null, + key_notes: $json->key_notes ?? null, + keynest_app_user: $json->keynest_app_user ?? null, + last_movement: $json->last_movement ?? null, + property_id: $json->property_id ?? null, + property_postcode: $json->property_postcode ?? null, + status_type: $json->status_type ?? null, + subscription_plan: $json->subscription_plan ?? null, + ); + } + + public function __construct( + /** + * Address for a KeyNest device. + */ + public string|null $address = null, + /** + * Current or last store ID for a KeyNest device. + */ + public float|null $current_or_last_store_id = null, + /** + * Current status for a KeyNest device. + */ + public string|null $current_status = null, + /** + * Current user company for a KeyNest device. + */ + public string|null $current_user_company = null, + /** + * Current user email for a KeyNest device. + */ + public string|null $current_user_email = null, + /** + * Current user name for a KeyNest device. + */ + public string|null $current_user_name = null, + /** + * Current user phone number for a KeyNest device. + */ + public string|null $current_user_phone_number = null, + /** + * Default office ID for a KeyNest device. + */ + public float|null $default_office_id = null, + /** + * Device name for a KeyNest device. + */ + public string|null $device_name = null, + /** + * Fob ID for a KeyNest device. + */ + public float|null $fob_id = null, + /** + * Handover method for a KeyNest device. + */ + public string|null $handover_method = null, + /** + * Whether the KeyNest device has a photo. + */ + public bool|null $has_photo = null, + /** + * Whether the key is in a locker that does not support the access codes API. + */ + public bool|null $is_quadient_locker = null, + /** + * Key ID for a KeyNest device. + */ + public string|null $key_id = null, + /** + * Key notes for a KeyNest device. + */ + public string|null $key_notes = null, + /** + * KeyNest app user for a KeyNest device. + */ + public string|null $keynest_app_user = null, + /** + * Last movement timestamp for a KeyNest device. + */ + public string|null $last_movement = null, + /** + * Property ID for a KeyNest device. + */ + public string|null $property_id = null, + /** + * Property postcode for a KeyNest device. + */ + public string|null $property_postcode = null, + /** + * Status type for a KeyNest device. + */ + public string|null $status_type = null, + /** + * Subscription plan for a KeyNest device. + */ + public string|null $subscription_plan = null, + ) {} + } + + /** + * Metadata for a Kisi device. + */ + class KisiMetadata + { + public static function from_json(mixed $json): KisiMetadata|null + { + if (!$json) { + return null; + } + return new self( + description: $json->description ?? null, + lock_id: $json->lock_id ?? null, + lock_name: $json->lock_name ?? null, + place_name: $json->place_name ?? null, + ); + } + + public function __construct( + /** + * Description for a Kisi device. + */ + public string|null $description = null, + /** + * Lock ID for a Kisi device. + */ + public float|null $lock_id = null, + /** + * Lock name for a Kisi device. + */ + public string|null $lock_name = null, + /** + * Place name for a Kisi device. + */ + public string|null $place_name = null, + ) {} + } + + /** + * Metadata for a Korelock device. + */ + class KorelockMetadata + { + public static function from_json(mixed $json): KorelockMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + firmware_version: $json->firmware_version ?? null, + location_id: $json->location_id ?? null, + model_code: $json->model_code ?? null, + serial_number: $json->serial_number ?? null, + wifi_signal_strength: $json->wifi_signal_strength ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Korelock device. + */ + public string|null $device_id = null, + /** + * Device name for a Korelock device. + */ + public string|null $device_name = null, + /** + * Firmware version for a Korelock device. + */ + public string|null $firmware_version = null, + /** + * Location ID for a Korelock device. Required for timebound access codes. + */ + public string|null $location_id = null, + /** + * Model code for a Korelock device. + */ + public string|null $model_code = null, + /** + * Serial number for a Korelock device. + */ + public string|null $serial_number = null, + /** + * WiFi signal strength (0-1) for a Korelock device. + */ + public float|null $wifi_signal_strength = null, + ) {} + } + + /** + * Metadata for a Kwikset device. + */ + class KwiksetMetadata + { + public static function from_json(mixed $json): KwiksetMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + model_number: $json->model_number ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Kwikset device. + */ + public string|null $device_id = null, + /** + * Device name for a Kwikset device. + */ + public string|null $device_name = null, + /** + * Model number for a Kwikset device. + */ + public string|null $model_number = null, + ) {} + } + + /** + * Metadata for a Lockly device. + */ + class LocklyMetadata + { + public static function from_json(mixed $json): LocklyMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + model: $json->model ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Lockly device. + */ + public string|null $device_id = null, + /** + * Device name for a Lockly device. + */ + public string|null $device_name = null, + /** + * Model for a Lockly device. + */ + public string|null $model = null, + ) {} + } + + /** + * Metadata for a Minut device. + */ + class MinutMetadata + { + public static function from_json(mixed $json): MinutMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + latest_sensor_values: isset($json->latest_sensor_values) + ? \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues::from_json( + $json->latest_sensor_values, + ) + : null, + ); + } + + public function __construct( + /** + * Device ID for a Minut device. + */ + public string|null $device_id = null, + /** + * Device name for a Minut device. + */ + public string|null $device_name = null, + /** + * Latest sensor values for a Minut device. + */ + public \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues|null $latest_sensor_values = null, + ) {} + } + + /** + * Metadata for a Google Nest device. + */ + class NestMetadata + { + public static function from_json(mixed $json): NestMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_custom_name: $json->device_custom_name ?? null, + device_name: $json->device_name ?? null, + display_name: $json->display_name ?? null, + nest_device_id: $json->nest_device_id ?? null, + nest_structure_id: $json->nest_structure_id ?? null, + structure_name: $json->structure_name ?? null, + ); + } + + public function __construct( + /** + * Custom device name for a Google Nest device. The device owner sets this value. + */ + public string|null $device_custom_name = null, + /** + * Device name for a Google Nest device. Google sets this value. + */ + public string|null $device_name = null, + /** + * Display name for a Google Nest device. + */ + public string|null $display_name = null, + /** + * Device ID for a Google Nest device. + */ + public string|null $nest_device_id = null, + /** + * ID of the Google Nest structure containing the device. + */ + public string|null $nest_structure_id = null, + /** + * Name of the Google Nest structure containing the device. The device owner sets this value. + */ + public string|null $structure_name = null, + ) {} + } + + /** + * Metadata for a NoiseAware device. + */ + class NoiseawareMetadata + { + public static function from_json(mixed $json): NoiseawareMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_model: $json->device_model ?? null, + device_name: $json->device_name ?? null, + noise_level_decibel: $json->noise_level_decibel ?? null, + noise_level_nrs: $json->noise_level_nrs ?? null, + ); + } + + public function __construct( + /** + * Device ID for a NoiseAware device. + */ + public string|null $device_id = null, + /** + * Device model for a NoiseAware device. + * + * @var value-of<\Seam\Resources\Device\Properties\NoiseawareMetadata\DeviceModel>|string|null + */ + public string|null $device_model = null, + /** + * Device name for a NoiseAware device. + */ + public string|null $device_name = null, + /** + * Noise level, in decibels, for a NoiseAware device. + */ + public float|null $noise_level_decibel = null, + /** + * Noise level, expressed as a Noise Risk Score (NRS), for a NoiseAware device. + */ + public float|null $noise_level_nrs = null, + ) {} + } + + /** + * Metadata for a Nuki device. + */ + class NukiMetadata + { + public static function from_json(mixed $json): NukiMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + keypad_2_paired: $json->keypad_2_paired ?? null, + keypad_battery_critical: $json->keypad_battery_critical ?? null, + keypad_paired: $json->keypad_paired ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Nuki device. + */ + public string|null $device_id = null, + /** + * Device name for a Nuki device. + */ + public string|null $device_name = null, + /** + * Indicates whether keypad 2 is paired for a Nuki device. + */ + public bool|null $keypad_2_paired = null, + /** + * Indicates whether the keypad battery is in a critical state for a Nuki device. + */ + public bool|null $keypad_battery_critical = null, + /** + * Indicates whether the keypad is paired for a Nuki device. + */ + public bool|null $keypad_paired = null, + ) {} + } + + /** + * Metadata for an Omnitec device. + */ + class OmnitecMetadata + { + public static function from_json(mixed $json): OmnitecMetadata|null + { + if (!$json) { + return null; + } + return new self( + has_gateway: $json->has_gateway ?? null, + lock_alias: $json->lock_alias ?? null, + lock_id: $json->lock_id ?? null, + lock_mac: $json->lock_mac ?? null, + lock_name: $json->lock_name ?? null, + time_zone: $json->time_zone ?? null, + timezone_raw_offset_ms: $json->timezone_raw_offset_ms ?? null, + ); + } + + public function __construct( + /** + * Whether the Omnitec lock has a connected gateway for remote operations. + */ + public bool|null $has_gateway = null, + /** + * Operator-assigned alias for an Omnitec device. + */ + public string|null $lock_alias = null, + /** + * Lock ID for an Omnitec device. + */ + public float|null $lock_id = null, + /** + * Bluetooth MAC address for an Omnitec device. + */ + public string|null $lock_mac = null, + /** + * Lock name for an Omnitec device. + */ + public string|null $lock_name = null, + /** + * IANA time zone for the Omnitec device, used to schedule time-bound access codes at the correct local time (accounting for DST). + */ + public string|null $time_zone = null, + /** + * Static UTC offset of the Omnitec lock in milliseconds. Does not account for DST. + */ + public float|null $timezone_raw_offset_ms = null, + ) {} + } + + /** + * Metadata for a Ring device. + */ + class RingMetadata + { + public static function from_json(mixed $json): RingMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Ring device. + */ + public string|null $device_id = null, + /** + * Device name for a Ring device. + */ + public string|null $device_name = null, + ) {} + } + + /** + * Metadata for a Salto KS device. + */ + class SaltoKsMetadata + { + public static function from_json(mixed $json): SaltoKsMetadata|null + { + if (!$json) { + return null; + } + return new self( + battery_level: $json->battery_level ?? null, + customer_reference: $json->customer_reference ?? null, + has_custom_pin_subscription: $json->has_custom_pin_subscription ?? + null, + lock_id: $json->lock_id ?? null, + lock_type: $json->lock_type ?? null, + locked_state: $json->locked_state ?? null, + model: $json->model ?? null, + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + ); + } + + public function __construct( + /** + * Battery level for a Salto KS device. + */ + public string|null $battery_level = null, + /** + * Customer reference for a Salto KS device. + */ + public string|null $customer_reference = null, + /** + * Indicates whether the site has a Salto KS subscription that supports custom PINs. + */ + public bool|null $has_custom_pin_subscription = null, + /** + * Lock ID for a Salto KS device. + */ + public string|null $lock_id = null, + /** + * Lock type for a Salto KS device. + */ + public string|null $lock_type = null, + /** + * Locked state for a Salto KS device. + */ + public string|null $locked_state = null, + /** + * Model for a Salto KS device. + */ + public string|null $model = null, + /** + * Site ID for the Salto KS site to which the device belongs. + */ + public string|null $site_id = null, + /** + * Site name for the Salto KS site to which the device belongs. + */ + public string|null $site_name = null, + ) {} + } + + /** + * Metada for a Salto device. + * + * @deprecated Use `salto_ks_metadata` instead. + */ + class SaltoMetadata + { + public static function from_json(mixed $json): SaltoMetadata|null + { + if (!$json) { + return null; + } + return new self( + battery_level: $json->battery_level ?? null, + customer_reference: $json->customer_reference ?? null, + lock_id: $json->lock_id ?? null, + lock_type: $json->lock_type ?? null, + locked_state: $json->locked_state ?? null, + model: $json->model ?? null, + site_id: $json->site_id ?? null, + site_name: $json->site_name ?? null, + ); + } + + public function __construct( + /** + * Battery level for a Salto device. + */ + public string|null $battery_level = null, + /** + * Customer reference for a Salto device. + */ + public string|null $customer_reference = null, + /** + * Lock ID for a Salto device. + */ + public string|null $lock_id = null, + /** + * Lock type for a Salto device. + */ + public string|null $lock_type = null, + /** + * Locked state for a Salto device. + */ + public string|null $locked_state = null, + /** + * Model for a Salto device. + */ + public string|null $model = null, + /** + * Site ID for the Salto KS site to which the device belongs. + */ + public string|null $site_id = null, + /** + * Site name for the Salto KS site to which the device belongs. + */ + public string|null $site_name = null, + ) {} + } + + /** + * Metadata for a Schlage device. + */ + class SchlageMetadata + { + public static function from_json(mixed $json): SchlageMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + model: $json->model ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Schlage device. + */ + public string|null $device_id = null, + /** + * Device name for a Schlage device. + */ + public string|null $device_name = null, + /** + * Model for a Schlage device. + */ + public string|null $model = null, + ) {} + } + + /** + * Metadata for Seam Bridge. + */ + class SeamBridgeMetadata + { + public static function from_json(mixed $json): SeamBridgeMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_num: $json->device_num ?? null, + name: $json->name ?? null, + unlock_method: $json->unlock_method ?? null, + ); + } + + public function __construct( + /** + * Device number for Seam Bridge. + */ + public float|null $device_num = null, + /** + * Name for Seam Bridge. + */ + public string|null $name = null, + /** + * Unlock method for Seam Bridge. + * + * @var value-of<\Seam\Resources\Device\Properties\SeamBridgeMetadata\UnlockMethod>|string|null + */ + public string|null $unlock_method = null, + ) {} + } + + /** + * Metadata for a Sensi device. + */ + class SensiMetadata + { + public static function from_json(mixed $json): SensiMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + dual_setpoints_not_supported: $json->dual_setpoints_not_supported ?? + null, + enforced_setpoint_range_celsius: $json->enforced_setpoint_range_celsius ?? + null, + product_type: $json->product_type ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Sensi device. + */ + public string|null $device_id = null, + /** + * Device name for a Sensi device. + */ + public string|null $device_name = null, + /** + * Set to true when the device does not support the /dual-setpoints API endpoint. + */ + public bool|null $dual_setpoints_not_supported = null, + /** + * Enforced setpoint range in Celsius for a Sensi device, derived from an OutOfRange API error. + * + * @var list|null + */ + public array|null $enforced_setpoint_range_celsius = null, + /** + * Product type for a Sensi device. + */ + public string|null $product_type = null, + ) {} + } + + /** + * Metadata for a SmartThings device. + */ + class SmartthingsMetadata + { + public static function from_json(mixed $json): SmartthingsMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + location_id: $json->location_id ?? null, + model: $json->model ?? null, + ); + } + + public function __construct( + /** + * Device ID for a SmartThings device. + */ + public string|null $device_id = null, + /** + * Device name for a SmartThings device. + */ + public string|null $device_name = null, + /** + * Location ID for a SmartThings device. + */ + public string|null $location_id = null, + /** + * Model for a SmartThings device. + */ + public string|null $model = null, + ) {} + } + + /** + * Metadata for a tado° device. + */ + class TadoMetadata + { + public static function from_json(mixed $json): TadoMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_type: $json->device_type ?? null, + serial_no: $json->serial_no ?? null, + ); + } + + public function __construct( + /** + * Device type for a tado° device. + */ + public string|null $device_type = null, + /** + * Serial number for a tado° device. + */ + public string|null $serial_no = null, + ) {} + } + + /** + * Metadata for a Tedee device. + */ + class TedeeMetadata + { + public static function from_json(mixed $json): TedeeMetadata|null + { + if (!$json) { + return null; + } + return new self( + bridge_id: $json->bridge_id ?? null, + bridge_name: $json->bridge_name ?? null, + device_id: $json->device_id ?? null, + device_model: $json->device_model ?? null, + device_name: $json->device_name ?? null, + keypad_id: $json->keypad_id ?? null, + serial_number: $json->serial_number ?? null, + ); + } + + public function __construct( + /** + * Bridge ID for a Tedee device. + */ + public float|null $bridge_id = null, + /** + * Bridge name for a Tedee device. + */ + public string|null $bridge_name = null, + /** + * Device ID for a Tedee device. + */ + public float|null $device_id = null, + /** + * Device model for a Tedee device. + */ + public string|null $device_model = null, + /** + * Device name for a Tedee device. + */ + public string|null $device_name = null, + /** + * Keypad ID for a Tedee device. + */ + public float|null $keypad_id = null, + /** + * Serial number for a Tedee device. + */ + public string|null $serial_number = null, + ) {} + } + + /** + * Metadata for a TTLock device. + */ + class TtlockMetadata + { + public static function from_json(mixed $json): TtlockMetadata|null + { + if (!$json) { + return null; + } + return new self( + feature_value: $json->feature_value ?? null, + features: isset($json->features) + ? \Seam\Resources\Device\Properties\TtlockMetadata\Features::from_json( + $json->features, + ) + : null, + has_gateway: $json->has_gateway ?? null, + lock_alias: $json->lock_alias ?? null, + lock_id: $json->lock_id ?? null, + timezone_raw_offset_ms: $json->timezone_raw_offset_ms ?? null, + wireless_keypads: array_map( + fn( + $w, + ) => \Seam\Resources\Device\Properties\TtlockMetadata\WirelessKeypads::from_json( + $w, + ), + $json->wireless_keypads ?? [], + ), + ); + } + + public function __construct( + /** + * Feature value for a TTLock device. + */ + public string|null $feature_value = null, + /** + * Features for a TTLock device. + */ + public \Seam\Resources\Device\Properties\TtlockMetadata\Features|null $features = null, + /** + * Indicates whether a TTLock device has a gateway. + */ + public bool|null $has_gateway = null, + /** + * Lock alias for a TTLock device. + */ + public string|null $lock_alias = null, + /** + * Lock ID for a TTLock device. + */ + public float|null $lock_id = null, + /** + * Lock-side timezone offset in milliseconds east of UTC, as configured in the TTLock app. Source of truth for the lock's wall-clock interpretation of access code start/end times — a misconfigured value here is the typical cause of customer "codes offset by N hours" reports. Diagnostic only; Seam does not convert times based on this value. + */ + public float|null $timezone_raw_offset_ms = null, + /** + * Wireless keypads for a TTLock device. + * + * @var list<\Seam\Resources\Device\Properties\TtlockMetadata\WirelessKeypads>|null + */ + public array|null $wireless_keypads = null, + ) {} + } + + /** + * Metadata for a 2N device. + */ + class TwoNMetadata + { + public static function from_json(mixed $json): TwoNMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + ); + } + + public function __construct( + /** + * Device ID for a 2N device. + */ + public float|null $device_id = null, + /** + * Device name for a 2N device. + */ + public string|null $device_name = null, + ) {} + } + + /** + * Metadata for an Ultraloq device. + */ + class UltraloqMetadata + { + public static function from_json(mixed $json): UltraloqMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + device_type: $json->device_type ?? null, + time_zone: $json->time_zone ?? null, + ); + } + + public function __construct( + /** + * Device ID for an Ultraloq device. + */ + public string|null $device_id = null, + /** + * Device name for an Ultraloq device. + */ + public string|null $device_name = null, + /** + * Device type for an Ultraloq device. + */ + public string|null $device_type = null, + /** + * IANA timezone for the Ultraloq device. + */ + public string|null $time_zone = null, + ) {} + } + + /** + * Metadata for an ASSA ABLOY Visionline system. + */ + class VisionlineMetadata + { + public static function from_json(mixed $json): VisionlineMetadata|null + { + if (!$json) { + return null; + } + return new self(encoder_id: $json->encoder_id ?? null); + } + + public function __construct( + /** + * Encoder ID for an ASSA ABLOY Visionline system. + */ + public string|null $encoder_id = null, + ) {} + } + + /** + * Metadata for a Wyze device. + */ + class WyzeMetadata + { + public static function from_json(mixed $json): WyzeMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_info_model: $json->device_info_model ?? null, + device_name: $json->device_name ?? null, + keypad_uuid: $json->keypad_uuid ?? null, + locker_status_hardlock: $json->locker_status_hardlock ?? null, + product_model: $json->product_model ?? null, + product_name: $json->product_name ?? null, + product_type: $json->product_type ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Wyze device. + */ + public string|null $device_id = null, + /** + * Device information model for a Wyze device. + */ + public string|null $device_info_model = null, + /** + * Device name for a Wyze device. + */ + public string|null $device_name = null, + /** + * Keypad UUID for a Wyze device. + */ + public string|null $keypad_uuid = null, + /** + * Locker status (hardlock) for a Wyze device. + */ + public float|null $locker_status_hardlock = null, + /** + * Product model for a Wyze device. + */ + public string|null $product_model = null, + /** + * Product name for a Wyze device. + */ + public string|null $product_name = null, + /** + * Product type for a Wyze device. + */ + public string|null $product_type = null, + ) {} + } + + /** + * Metadata for a Yacan device. + */ + class YacanMetadata + { + public static function from_json(mixed $json): YacanMetadata|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + device_type: $json->device_type ?? null, + serial_number: $json->serial_number ?? null, + ); + } + + public function __construct( + /** + * Device ID for a Yacan device. + */ + public string|null $device_id = null, + /** + * Device name for a Yacan device. + */ + public string|null $device_name = null, + /** + * Device type for a Yacan device. + */ + public string|null $device_type = null, + /** + * Serial number for a Yacan device. + */ + public string|null $serial_number = null, + ) {} + } + + /** + * Constraints on access codes for the device. Seam represents each constraint as an object with a `constraint_type` property. Depending on the constraint type, there may also be additional properties. Note that some constraints are manufacturer- or device-specific. + */ + class CodeConstraints + { + public static function from_json(mixed $json): CodeConstraints|null + { + if (!$json) { + return null; + } + return new self( + constraint_type: $json->constraint_type ?? null, + max_length: $json->max_length ?? null, + min_length: $json->min_length ?? null, + ); + } + + public function __construct( + /** + * @var value-of<\Seam\Resources\Device\Properties\CodeConstraints\ConstraintType>|string|null + */ + public string|null $constraint_type, + /** + * Maximum name length constraint for access codes. + */ + public float|null $max_length = null, + /** + * Minimum name length constraint for access codes. + */ + public float|null $min_length = null, + ) {} + } + + /** + * Keypad battery status. + */ + class KeypadBattery + { + public static function from_json(mixed $json): KeypadBattery|null + { + if (!$json) { + return null; + } + return new self(level: $json->level ?? null); + } + + public function __construct( + /** + * Keypad battery charge level. + */ + public float|null $level, + ) {} + } + + /** + * Time frames that may be requested when creating an offline access code, expressed as a list of options. The caller picks one option (by matching the requested duration when the options' duration ranges do not overlap, or by `display_name` when they do) and satisfies that one option's rules. When `undefined`, any time frame works. + */ + class OfflineTimeFrameOptions + { + public static function from_json( + mixed $json, + ): OfflineTimeFrameOptions|null { + if (!$json) { + return null; + } + return new self( + display_name: $json->display_name ?? null, + end_date_recurrence_rule: $json->end_date_recurrence_rule ?? + null, + matching_start_end_time: $json->matching_start_end_time ?? null, + max_duration: $json->max_duration ?? null, + min_duration: $json->min_duration ?? null, + start_date_recurrence_rule: $json->start_date_recurrence_rule ?? + null, + time_pairs: array_map( + fn( + $t, + ) => \Seam\Resources\Device\Properties\OfflineTimeFrameOptions\TimePairs::from_json( + $t, + ), + $json->time_pairs ?? [], + ), + time_zone: $json->time_zone ?? null, + ); + } + + public function __construct( + /** + * Label for this option. For a single-option device, the product name (for example, `algoPIN` or `SmartPIN`); for a multi-option device, a label that distinguishes it (for example, `Hourly` or `Fixed start times`). + */ + public string|null $display_name, + /** + * iCalendar recurrence rule (RRULE) that the end date must fall on. Constrains which calendar dates are selectable, independent of the time-of-day rules. + */ + public string|null $end_date_recurrence_rule = null, + /** + * When `true`, the start and end must fall at the same time of day (the caller picks which). Mutually exclusive with `time_pairs`. + */ + public true|null $matching_start_end_time = null, + /** + * Maximum duration this option covers, as an ISO 8601 duration (for example, `PT672H` or `P367D`). Omitted when there is no maximum. + */ + public string|null $max_duration = null, + /** + * Minimum duration this option covers, as an ISO 8601 duration (for example, `PT1H` or `P29D`). Omitted when there is no minimum. + */ + public string|null $min_duration = null, + /** + * iCalendar recurrence rule (RRULE) that the start date must fall on (for example, `FREQ=MONTHLY;BYDAY=1MO,3MO`). Constrains which calendar dates are selectable, independent of the time-of-day rules. + */ + public string|null $start_date_recurrence_rule = null, + /** + * Fixed start/end time pairings the caller chooses from. Mutually exclusive with `matching_start_end_time`. + * + * @var list<\Seam\Resources\Device\Properties\OfflineTimeFrameOptions\TimePairs>|null + */ + public array|null $time_pairs = null, + /** + * IANA time zone for interpreting `time_pairs` and the date recurrence rules. Present only when the option fixes times or dates. + */ + public string|null $time_zone = null, + ) {} + } + + /** + * Time frames that may be requested when creating an online access code, expressed as a list of options. The caller picks one option (by matching the requested duration when the options' duration ranges do not overlap, or by `display_name` when they do) and satisfies that one option's rules. When `undefined`, any time frame works. + */ + class OnlineTimeFrameOptions + { + public static function from_json( + mixed $json, + ): OnlineTimeFrameOptions|null { + if (!$json) { + return null; + } + return new self( + display_name: $json->display_name ?? null, + end_date_recurrence_rule: $json->end_date_recurrence_rule ?? + null, + matching_start_end_time: $json->matching_start_end_time ?? null, + max_duration: $json->max_duration ?? null, + min_duration: $json->min_duration ?? null, + start_date_recurrence_rule: $json->start_date_recurrence_rule ?? + null, + time_pairs: array_map( + fn( + $t, + ) => \Seam\Resources\Device\Properties\OnlineTimeFrameOptions\TimePairs::from_json( + $t, + ), + $json->time_pairs ?? [], + ), + time_zone: $json->time_zone ?? null, + ); + } + + public function __construct( + /** + * Label for this option. For a single-option device, the product name (for example, `algoPIN` or `SmartPIN`); for a multi-option device, a label that distinguishes it (for example, `Hourly` or `Fixed start times`). + */ + public string|null $display_name, + /** + * iCalendar recurrence rule (RRULE) that the end date must fall on. Constrains which calendar dates are selectable, independent of the time-of-day rules. + */ + public string|null $end_date_recurrence_rule = null, + /** + * When `true`, the start and end must fall at the same time of day (the caller picks which). Mutually exclusive with `time_pairs`. + */ + public true|null $matching_start_end_time = null, + /** + * Maximum duration this option covers, as an ISO 8601 duration (for example, `PT672H` or `P367D`). Omitted when there is no maximum. + */ + public string|null $max_duration = null, + /** + * Minimum duration this option covers, as an ISO 8601 duration (for example, `PT1H` or `P29D`). Omitted when there is no minimum. + */ + public string|null $min_duration = null, + /** + * iCalendar recurrence rule (RRULE) that the start date must fall on (for example, `FREQ=MONTHLY;BYDAY=1MO,3MO`). Constrains which calendar dates are selectable, independent of the time-of-day rules. + */ + public string|null $start_date_recurrence_rule = null, + /** + * Fixed start/end time pairings the caller chooses from. Mutually exclusive with `matching_start_end_time`. + * + * @var list<\Seam\Resources\Device\Properties\OnlineTimeFrameOptions\TimePairs>|null + */ + public array|null $time_pairs = null, + /** + * IANA time zone for interpreting `time_pairs` and the date recurrence rules. Present only when the option fixes times or dates. + */ + public string|null $time_zone = null, + ) {} + } + + /** + * Active [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + * + * @deprecated Use `active_thermostat_schedule_id` with `/thermostats/schedules/get` instead. + */ + class ActiveThermostatSchedule + { + public static function from_json( + mixed $json, + ): ActiveThermostatSchedule|null { + if (!$json) { + return null; + } + return new self( + climate_preset_key: $json->climate_preset_key ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + ends_at: $json->ends_at ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\Device\Properties\ActiveThermostatSchedule\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + name: $json->name ?? null, + starts_at: $json->starts_at ?? null, + thermostat_schedule_id: $json->thermostat_schedule_id ?? null, + workspace_id: $json->workspace_id ?? null, + is_override_allowed: $json->is_override_allowed ?? null, + max_override_period_minutes: $json->max_override_period_minutes ?? + null, + ); + } + + public function __construct( + /** + * Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to use for the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + public string|null $climate_preset_key, + /** + * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) was created. + */ + public string|null $created_at, + /** + * ID of the desired [thermostat](https://docs.seam.co/capability-guides/thermostats) device. + */ + public string|null $device_id, + /** + * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $ends_at, + /** + * Errors associated with the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + * + * @var list<\Seam\Resources\Device\Properties\ActiveThermostatSchedule\Errors> + */ + public array $errors, + /** + * User-friendly name to identify the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + public string|null $name, + /** + * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at, + /** + * ID of the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + public string|null $thermostat_schedule_id, + /** + * ID of the workspace that contains the thermostat schedule. + */ + public string|null $workspace_id, + /** + * Indicates whether a person at the thermostat can change the thermostat's settings after the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) starts. + */ + public bool|null $is_override_allowed = null, + /** + * Number of minutes for which a person at the thermostat can change the thermostat's settings after the activation of the scheduled [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). + */ + public int|null $max_override_period_minutes = null, + ) {} + } + + /** + * Available [climate presets](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) for the thermostat. + */ + class AvailableClimatePresets + { + public static function from_json( + mixed $json, + ): AvailableClimatePresets|null { + if (!$json) { + return null; + } + return new self( + can_delete: $json->can_delete ?? null, + can_edit: $json->can_edit ?? null, + can_use_with_thermostat_daily_programs: $json->can_use_with_thermostat_daily_programs ?? + null, + climate_preset_key: $json->climate_preset_key ?? null, + display_name: $json->display_name ?? null, + manual_override_allowed: $json->manual_override_allowed ?? null, + climate_preset_mode: $json->climate_preset_mode ?? null, + cooling_set_point_celsius: $json->cooling_set_point_celsius ?? + null, + cooling_set_point_fahrenheit: $json->cooling_set_point_fahrenheit ?? + null, + ecobee_metadata: isset($json->ecobee_metadata) + ? \Seam\Resources\Device\Properties\AvailableClimatePresets\EcobeeMetadata::from_json( + $json->ecobee_metadata, + ) + : null, + fan_mode_setting: $json->fan_mode_setting ?? null, + heating_set_point_celsius: $json->heating_set_point_celsius ?? + null, + heating_set_point_fahrenheit: $json->heating_set_point_fahrenheit ?? + null, + hvac_mode_setting: $json->hvac_mode_setting ?? null, + name: $json->name ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be deleted. + */ + public bool|null $can_delete, + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be edited. + */ + public bool|null $can_edit, + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be programmed in a thermostat daily program. + */ + public bool|null $can_use_with_thermostat_daily_programs, + /** + * Unique key to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $climate_preset_key, + /** + * Display name for the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $display_name, + /** + * Indicates whether a person at the thermostat can change the thermostat's settings. See [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). + * + * @deprecated Use 'thermostat_schedule.is_override_allowed' + */ + public bool|null $manual_override_allowed, + /** + * The climate preset mode for the thermostat, based on the available climate preset modes reported by the device. + * + * @var value-of<\Seam\Resources\Device\Properties\AvailableClimatePresets\ClimatePresetMode>|string|null + */ + public string|null $climate_preset_mode = null, + /** + * Temperature to which the thermostat should cool (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $cooling_set_point_celsius = null, + /** + * Temperature to which the thermostat should cool (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $cooling_set_point_fahrenheit = null, + /** + * Metadata specific to the Ecobee climate, if applicable. + */ + public \Seam\Resources\Device\Properties\AvailableClimatePresets\EcobeeMetadata|null $ecobee_metadata = null, + /** + * Desired [fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings), such as `on`, `auto`, or `circulate`. + * + * @var value-of<\Seam\Resources\Device\Properties\AvailableClimatePresets\FanModeSetting>|string|null + */ + public string|null $fan_mode_setting = null, + /** + * Temperature to which the thermostat should heat (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $heating_set_point_celsius = null, + /** + * Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $heating_set_point_fahrenheit = null, + /** + * Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. + * + * @var value-of<\Seam\Resources\Device\Properties\AvailableClimatePresets\HvacModeSetting>|string|null + */ + public string|null $hvac_mode_setting = null, + /** + * User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $name = null, + ) {} + } + + /** + * Current climate setting. + */ + class CurrentClimateSetting + { + public static function from_json( + mixed $json, + ): CurrentClimateSetting|null { + if (!$json) { + return null; + } + return new self( + can_delete: $json->can_delete ?? null, + can_edit: $json->can_edit ?? null, + can_use_with_thermostat_daily_programs: $json->can_use_with_thermostat_daily_programs ?? + null, + climate_preset_key: $json->climate_preset_key ?? null, + climate_preset_mode: $json->climate_preset_mode ?? null, + cooling_set_point_celsius: $json->cooling_set_point_celsius ?? + null, + cooling_set_point_fahrenheit: $json->cooling_set_point_fahrenheit ?? + null, + display_name: $json->display_name ?? null, + ecobee_metadata: isset($json->ecobee_metadata) + ? \Seam\Resources\Device\Properties\CurrentClimateSetting\EcobeeMetadata::from_json( + $json->ecobee_metadata, + ) + : null, + fan_mode_setting: $json->fan_mode_setting ?? null, + heating_set_point_celsius: $json->heating_set_point_celsius ?? + null, + heating_set_point_fahrenheit: $json->heating_set_point_fahrenheit ?? + null, + hvac_mode_setting: $json->hvac_mode_setting ?? null, + manual_override_allowed: $json->manual_override_allowed ?? null, + name: $json->name ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be deleted. + */ + public bool|null $can_delete = null, + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be edited. + */ + public bool|null $can_edit = null, + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be programmed in a thermostat daily program. + */ + public bool|null $can_use_with_thermostat_daily_programs = null, + /** + * Unique key to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $climate_preset_key = null, + /** + * The climate preset mode for the thermostat, based on the available climate preset modes reported by the device. + * + * @var value-of<\Seam\Resources\Device\Properties\CurrentClimateSetting\ClimatePresetMode>|string|null + */ + public string|null $climate_preset_mode = null, + /** + * Temperature to which the thermostat should cool (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $cooling_set_point_celsius = null, + /** + * Temperature to which the thermostat should cool (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $cooling_set_point_fahrenheit = null, + /** + * Display name for the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $display_name = null, + /** + * Metadata specific to the Ecobee climate, if applicable. + */ + public \Seam\Resources\Device\Properties\CurrentClimateSetting\EcobeeMetadata|null $ecobee_metadata = null, + /** + * Desired [fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings), such as `on`, `auto`, or `circulate`. + * + * @var value-of<\Seam\Resources\Device\Properties\CurrentClimateSetting\FanModeSetting>|string|null + */ + public string|null $fan_mode_setting = null, + /** + * Temperature to which the thermostat should heat (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $heating_set_point_celsius = null, + /** + * Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $heating_set_point_fahrenheit = null, + /** + * Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. + * + * @var value-of<\Seam\Resources\Device\Properties\CurrentClimateSetting\HvacModeSetting>|string|null + */ + public string|null $hvac_mode_setting = null, + /** + * Indicates whether a person at the thermostat can change the thermostat's settings. See [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). + * + * @deprecated Use 'thermostat_schedule.is_override_allowed' + */ + public bool|null $manual_override_allowed = null, + /** + * User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $name = null, + ) {} + } + + /** + * @deprecated use fallback_climate_preset_key to specify a fallback climate preset instead. + */ + class DefaultClimateSetting + { + public static function from_json( + mixed $json, + ): DefaultClimateSetting|null { + if (!$json) { + return null; + } + return new self( + can_delete: $json->can_delete ?? null, + can_edit: $json->can_edit ?? null, + can_use_with_thermostat_daily_programs: $json->can_use_with_thermostat_daily_programs ?? + null, + climate_preset_key: $json->climate_preset_key ?? null, + climate_preset_mode: $json->climate_preset_mode ?? null, + cooling_set_point_celsius: $json->cooling_set_point_celsius ?? + null, + cooling_set_point_fahrenheit: $json->cooling_set_point_fahrenheit ?? + null, + display_name: $json->display_name ?? null, + ecobee_metadata: isset($json->ecobee_metadata) + ? \Seam\Resources\Device\Properties\DefaultClimateSetting\EcobeeMetadata::from_json( + $json->ecobee_metadata, + ) + : null, + fan_mode_setting: $json->fan_mode_setting ?? null, + heating_set_point_celsius: $json->heating_set_point_celsius ?? + null, + heating_set_point_fahrenheit: $json->heating_set_point_fahrenheit ?? + null, + hvac_mode_setting: $json->hvac_mode_setting ?? null, + manual_override_allowed: $json->manual_override_allowed ?? null, + name: $json->name ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be deleted. + */ + public bool|null $can_delete = null, + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be edited. + */ + public bool|null $can_edit = null, + /** + * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be programmed in a thermostat daily program. + */ + public bool|null $can_use_with_thermostat_daily_programs = null, + /** + * Unique key to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $climate_preset_key = null, + /** + * The climate preset mode for the thermostat, based on the available climate preset modes reported by the device. + * + * @var value-of<\Seam\Resources\Device\Properties\DefaultClimateSetting\ClimatePresetMode>|string|null + */ + public string|null $climate_preset_mode = null, + /** + * Temperature to which the thermostat should cool (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $cooling_set_point_celsius = null, + /** + * Temperature to which the thermostat should cool (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $cooling_set_point_fahrenheit = null, + /** + * Display name for the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $display_name = null, + /** + * Metadata specific to the Ecobee climate, if applicable. + */ + public \Seam\Resources\Device\Properties\DefaultClimateSetting\EcobeeMetadata|null $ecobee_metadata = null, + /** + * Desired [fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings), such as `on`, `auto`, or `circulate`. + * + * @var value-of<\Seam\Resources\Device\Properties\DefaultClimateSetting\FanModeSetting>|string|null + */ + public string|null $fan_mode_setting = null, + /** + * Temperature to which the thermostat should heat (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $heating_set_point_celsius = null, + /** + * Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $heating_set_point_fahrenheit = null, + /** + * Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. + * + * @var value-of<\Seam\Resources\Device\Properties\DefaultClimateSetting\HvacModeSetting>|string|null + */ + public string|null $hvac_mode_setting = null, + /** + * Indicates whether a person at the thermostat can change the thermostat's settings. See [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). + * + * @deprecated Use 'thermostat_schedule.is_override_allowed' + */ + public bool|null $manual_override_allowed = null, + /** + * User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + */ + public string|null $name = null, + ) {} + } + + /** + * Current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. + */ + class TemperatureThreshold + { + public static function from_json(mixed $json): TemperatureThreshold|null + { + if (!$json) { + return null; + } + return new self( + lower_limit_celsius: $json->lower_limit_celsius ?? null, + lower_limit_fahrenheit: $json->lower_limit_fahrenheit ?? null, + upper_limit_celsius: $json->upper_limit_celsius ?? null, + upper_limit_fahrenheit: $json->upper_limit_fahrenheit ?? null, + ); + } + + public function __construct( + /** + * Lower limit in °C within the current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. + */ + public float|null $lower_limit_celsius, + /** + * Lower limit in °F within the current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. + */ + public float|null $lower_limit_fahrenheit, + /** + * Upper limit in °C within the current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. + */ + public float|null $upper_limit_celsius, + /** + * Upper limit in °F within the current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. + */ + public float|null $upper_limit_fahrenheit, + ) {} + } + + /** + * Configured [daily programs](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-programs) for the thermostat. + */ + class ThermostatDailyPrograms + { + public static function from_json( + mixed $json, + ): ThermostatDailyPrograms|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + name: $json->name ?? null, + periods: array_map( + fn( + $p, + ) => \Seam\Resources\Device\Properties\ThermostatDailyPrograms\Periods::from_json( + $p, + ), + $json->periods ?? [], + ), + thermostat_daily_program_id: $json->thermostat_daily_program_id ?? + null, + workspace_id: $json->workspace_id ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the thermostat daily program was created. + */ + public string|null $created_at, + /** + * ID of the thermostat device on which the thermostat daily program is configured. + */ + public string|null $device_id, + /** + * User-friendly name to identify the thermostat daily program. + */ + public string|null $name, + /** + * Array of thermostat daily program periods. + * + * @var list<\Seam\Resources\Device\Properties\ThermostatDailyPrograms\Periods> + */ + public array $periods, + /** + * ID of the thermostat daily program. + */ + public string|null $thermostat_daily_program_id, + /** + * ID of the workspace that contains the thermostat daily program. + */ + public string|null $workspace_id, + ) {} + } + + /** + * Current [weekly program](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-programs) for the thermostat. + */ + class ThermostatWeeklyProgram + { + public static function from_json( + mixed $json, + ): ThermostatWeeklyProgram|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + friday_program_id: $json->friday_program_id ?? null, + monday_program_id: $json->monday_program_id ?? null, + saturday_program_id: $json->saturday_program_id ?? null, + sunday_program_id: $json->sunday_program_id ?? null, + thursday_program_id: $json->thursday_program_id ?? null, + tuesday_program_id: $json->tuesday_program_id ?? null, + wednesday_program_id: $json->wednesday_program_id ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the thermostat weekly program was created. + */ + public string|null $created_at, + /** + * ID of the thermostat daily program to run on Fridays. + */ + public string|null $friday_program_id, + /** + * ID of the thermostat daily program to run on Mondays. + */ + public string|null $monday_program_id, + /** + * ID of the thermostat daily program to run on Saturdays. + */ + public string|null $saturday_program_id, + /** + * ID of the thermostat daily program to run on Sundays. + */ + public string|null $sunday_program_id, + /** + * ID of the thermostat daily program to run on Thursdays. + */ + public string|null $thursday_program_id, + /** + * ID of the thermostat daily program to run on Tuesdays. + */ + public string|null $tuesday_program_id, + /** + * ID of the thermostat daily program to run on Wednesdays. + */ + public string|null $wednesday_program_id, + ) {} + } + + enum FanModeSetting: string + { + case AUTO = "auto"; + case ON = "on"; + case CIRCULATE = "circulate"; + } +} + +namespace Seam\Resources\Device\Properties\AccessoryKeypad { + /** + * Keypad battery properties. + */ + class Battery + { + public static function from_json(mixed $json): Battery|null + { + if (!$json) { + return null; + } + return new self(level: $json->level ?? null); + } + + public function __construct(public float|null $level) {} + } +} + +namespace Seam\Resources\Device\Properties\Battery { + enum Status: string + { + case CRITICAL = "critical"; + case LOW = "low"; + case GOOD = "good"; + case FULL = "full"; + } +} + +namespace Seam\Resources\Device\Properties\AssaAbloyCredentialServiceMetadata { + /** + * Endpoints associated with the phone. + */ + class Endpoints + { + public static function from_json(mixed $json): Endpoints|null + { + if (!$json) { + return null; + } + return new self( + endpoint_id: $json->endpoint_id ?? null, + is_active: $json->is_active ?? null, + ); + } + + public function __construct( + /** + * ID of the associated endpoint. + */ + public string|null $endpoint_id = null, + /** + * Indicated whether the endpoint is active. + */ + public bool|null $is_active = null, + ) {} + } } -/** - * Latest accelerometer Z-axis reading for a Minut device. - */ -class DeviceAccelerometerZ -{ - public static function from_json(mixed $json): DeviceAccelerometerZ|null - { - if (!$json) { - return null; - } - return new self(time: $json->time ?? null, value: $json->value ?? null); - } - - public function __construct( - /** - * Time of latest accelerometer Z-axis reading for a Minut device. - */ - public string|null $time, - /** - * Value of latest accelerometer Z-axis reading for a Minut device. - */ - public float|null $value, - ) {} -} +namespace Seam\Resources\Device\Properties\DormakabaOracodeMetadata { + /** + * Predefined time slots for a dormakaba Oracode device. + */ + class PredefinedTimeSlots + { + public static function from_json(mixed $json): PredefinedTimeSlots|null + { + if (!$json) { + return null; + } + return new self( + check_in_time: $json->check_in_time ?? null, + check_out_time: $json->check_out_time ?? null, + dormakaba_oracode_user_level_id: $json->dormakaba_oracode_user_level_id ?? + null, + dormakaba_oracode_user_level_prefix: $json->dormakaba_oracode_user_level_prefix ?? + null, + is_24_hour: $json->is_24_hour ?? null, + is_biweekly_mode: $json->is_biweekly_mode ?? null, + is_master: $json->is_master ?? null, + is_one_shot: $json->is_one_shot ?? null, + name: $json->name ?? null, + prefix: $json->prefix ?? null, + ); + } + + public function __construct( + /** + * Check in time for a time slot for a dormakaba Oracode device. + */ + public string|null $check_in_time = null, + /** + * Checkout time for a time slot for a dormakaba Oracode device. + */ + public string|null $check_out_time = null, + /** + * ID of a user level for a dormakaba Oracode device. + */ + public string|null $dormakaba_oracode_user_level_id = null, + /** + * Prefix for a user level for a dormakaba Oracode device. + */ + public float|null $dormakaba_oracode_user_level_prefix = null, + /** + * Indicates whether a time slot for a dormakaba Oracode device is a 24-hour time slot. + */ + public bool|null $is_24_hour = null, + /** + * Indicates whether a time slot for a dormakaba Oracode device is in biweekly mode. + */ + public bool|null $is_biweekly_mode = null, + /** + * Indicates whether a time slot for a dormakaba Oracode device is a master time slot. + */ + public bool|null $is_master = null, + /** + * Indicates whether a time slot for a dormakaba Oracode device is a one-shot time slot. + */ + public bool|null $is_one_shot = null, + /** + * Name of a time slot for a dormakaba Oracode device. + */ + public string|null $name = null, + /** + * Prefix for a time slot for a dormakaba Oracode device. + */ + public float|null $prefix = null, + ) {} + } +} + +namespace Seam\Resources\Device\Properties\MinutMetadata { + /** + * Latest sensor values for a Minut device. + */ + class LatestSensorValues + { + public static function from_json(mixed $json): LatestSensorValues|null + { + if (!$json) { + return null; + } + return new self( + accelerometer_z: isset($json->accelerometer_z) + ? \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\AccelerometerZ::from_json( + $json->accelerometer_z, + ) + : null, + humidity: isset($json->humidity) + ? \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\Humidity::from_json( + $json->humidity, + ) + : null, + pressure: isset($json->pressure) + ? \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\Pressure::from_json( + $json->pressure, + ) + : null, + sound: isset($json->sound) + ? \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\Sound::from_json( + $json->sound, + ) + : null, + temperature: isset($json->temperature) + ? \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\Temperature::from_json( + $json->temperature, + ) + : null, + ); + } + + public function __construct( + /** + * Latest accelerometer Z-axis reading for a Minut device. + */ + public \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\AccelerometerZ|null $accelerometer_z = null, + /** + * Latest humidity reading for a Minut device. + */ + public \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\Humidity|null $humidity = null, + /** + * Latest pressure reading for a Minut device. + */ + public \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\Pressure|null $pressure = null, + /** + * Latest sound reading for a Minut device. + */ + public \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\Sound|null $sound = null, + /** + * Latest temperature reading for a Minut device. + */ + public \Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues\Temperature|null $temperature = null, + ) {} + } +} + +namespace Seam\Resources\Device\Properties\MinutMetadata\LatestSensorValues { + /** + * Latest accelerometer Z-axis reading for a Minut device. + */ + class AccelerometerZ + { + public static function from_json(mixed $json): AccelerometerZ|null + { + if (!$json) { + return null; + } + return new self( + time: $json->time ?? null, + value: $json->value ?? null, + ); + } + + public function __construct( + /** + * Time of latest accelerometer Z-axis reading for a Minut device. + */ + public string|null $time = null, + /** + * Value of latest accelerometer Z-axis reading for a Minut device. + */ + public float|null $value = null, + ) {} + } + + /** + * Latest humidity reading for a Minut device. + */ + class Humidity + { + public static function from_json(mixed $json): Humidity|null + { + if (!$json) { + return null; + } + return new self( + time: $json->time ?? null, + value: $json->value ?? null, + ); + } + + public function __construct( + /** + * Time of latest humidity reading for a Minut device. + */ + public string|null $time = null, + /** + * Value of latest humidity reading for a Minut device. + */ + public float|null $value = null, + ) {} + } + + /** + * Latest pressure reading for a Minut device. + */ + class Pressure + { + public static function from_json(mixed $json): Pressure|null + { + if (!$json) { + return null; + } + return new self( + time: $json->time ?? null, + value: $json->value ?? null, + ); + } + + public function __construct( + /** + * Time of latest pressure reading for a Minut device. + */ + public string|null $time = null, + /** + * Value of latest pressure reading for a Minut device. + */ + public float|null $value = null, + ) {} + } + + /** + * Latest sound reading for a Minut device. + */ + class Sound + { + public static function from_json(mixed $json): Sound|null + { + if (!$json) { + return null; + } + return new self( + time: $json->time ?? null, + value: $json->value ?? null, + ); + } + + public function __construct( + /** + * Time of latest sound reading for a Minut device. + */ + public string|null $time = null, + /** + * Value of latest sound reading for a Minut device. + */ + public float|null $value = null, + ) {} + } + + /** + * Latest temperature reading for a Minut device. + */ + class Temperature + { + public static function from_json(mixed $json): Temperature|null + { + if (!$json) { + return null; + } + return new self( + time: $json->time ?? null, + value: $json->value ?? null, + ); + } -/** - * Accessory keypad properties and state. - */ -class DeviceAccessoryKeypad -{ - public static function from_json(mixed $json): DeviceAccessoryKeypad|null - { - if (!$json) { - return null; - } - return new self( - battery: isset($json->battery) - ? DeviceBattery::from_json($json->battery) - : null, - is_connected: $json->is_connected ?? null, - ); - } - - public function __construct( - /** - * Keypad battery properties. - */ - public DeviceBattery|null $battery, - /** - * Indicates if an accessory keypad is connected to the device. - */ - public bool|null $is_connected, - ) {} + public function __construct( + /** + * Time of latest temperature reading for a Minut device. + */ + public string|null $time = null, + /** + * Value of latest temperature reading for a Minut device. + */ + public float|null $value = null, + ) {} + } } -/** - * Active [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ -class DeviceActiveThermostatSchedule -{ - public static function from_json( - mixed $json, - ): DeviceActiveThermostatSchedule|null { - if (!$json) { - return null; - } - return new self( - climate_preset_key: $json->climate_preset_key ?? null, - created_at: $json->created_at ?? null, - device_id: $json->device_id ?? null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => DeviceErrors::from_json($e), - $json->errors ?? [], - ), - is_override_allowed: $json->is_override_allowed ?? null, - max_override_period_minutes: $json->max_override_period_minutes ?? - null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - thermostat_schedule_id: $json->thermostat_schedule_id ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to use for the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public string|null $climate_preset_key, - /** - * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) was created. - */ - public string|null $created_at, - /** - * ID of the desired [thermostat](https://docs.seam.co/capability-guides/thermostats) device. - */ - public string|null $device_id, - /** - * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $ends_at, - /** - * Errors associated with the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public array $errors, - /** - * Indicates whether a person at the thermostat can change the thermostat's settings after the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) starts. - */ - public bool|null $is_override_allowed, - /** - * Number of minutes for which a person at the thermostat can change the thermostat's settings after the activation of the scheduled [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). - */ - public int|null $max_override_period_minutes, - /** - * User-friendly name to identify the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public string|null $name, - /** - * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at, - /** - * ID of the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public string|null $thermostat_schedule_id, - /** - * ID of the workspace that contains the thermostat schedule. - */ - public string|null $workspace_id, - ) {} +namespace Seam\Resources\Device\Properties\NoiseawareMetadata { + enum DeviceModel: string + { + case INDOOR = "indoor"; + case OUTDOOR = "outdoor"; + } } -/** - * Metadata for an Akiles device. - */ -class DeviceAkilesMetadata -{ - public static function from_json(mixed $json): DeviceAkilesMetadata|null - { - if (!$json) { - return null; - } - return new self( - _member_group_id: $json->_member_group_id ?? null, - gadget_id: $json->gadget_id ?? null, - gadget_name: $json->gadget_name ?? null, - product_name: $json->product_name ?? null, - ); - } - - public function __construct( - /** - * Group ID to which to add users for an Akiles device. - */ - public string|null $_member_group_id, - /** - * Gadget ID for an Akiles device. - */ - public string|null $gadget_id, - /** - * Gadget name for an Akiles device. - */ - public string|null $gadget_name, - /** - * Product name for an Akiles device. - */ - public string|null $product_name, - ) {} +namespace Seam\Resources\Device\Properties\SeamBridgeMetadata { + enum UnlockMethod: string + { + case BRIDGE = "bridge"; + case DOORKING = "doorking"; + } } -/** - * Appearance-related properties, as reported by the device. - */ -class DeviceAppearance -{ - public static function from_json(mixed $json): DeviceAppearance|null +namespace Seam\Resources\Device\Properties\TtlockMetadata { + /** + * Features for a TTLock device. + */ + class Features { - if (!$json) { - return null; + public static function from_json(mixed $json): Features|null + { + if (!$json) { + return null; + } + return new self( + auto_lock_time_config: $json->auto_lock_time_config ?? null, + incomplete_keyboard_passcode: $json->incomplete_keyboard_passcode ?? + null, + lock_command: $json->lock_command ?? null, + passcode: $json->passcode ?? null, + passcode_management: $json->passcode_management ?? null, + unlock_via_gateway: $json->unlock_via_gateway ?? null, + wifi: $json->wifi ?? null, + ); } - return new self(name: $json->name ?? null); + + public function __construct( + /** + * Indicates whether a TTLock device supports auto-lock time configuration. + */ + public bool|null $auto_lock_time_config = null, + /** + * Indicates whether a TTLock device supports an incomplete keyboard passcode. + */ + public bool|null $incomplete_keyboard_passcode = null, + /** + * Indicates whether a TTLock device supports the lock command. + */ + public bool|null $lock_command = null, + /** + * Indicates whether a TTLock device supports a passcode. + */ + public bool|null $passcode = null, + /** + * Indicates whether a TTLock device supports passcode management. + */ + public bool|null $passcode_management = null, + /** + * Indicates whether a TTLock device supports unlock via gateway. + */ + public bool|null $unlock_via_gateway = null, + /** + * Indicates whether a TTLock device supports Wi-Fi. + */ + public bool|null $wifi = null, + ) {} } - public function __construct( - /** - * Name of the device as seen from the provider API and application, not settable through Seam. - */ - public string|null $name, - ) {} -} + /** + * Wireless keypads for a TTLock device. + */ + class WirelessKeypads + { + public static function from_json(mixed $json): WirelessKeypads|null + { + if (!$json) { + return null; + } + return new self( + wireless_keypad_id: $json->wireless_keypad_id ?? null, + wireless_keypad_name: $json->wireless_keypad_name ?? null, + ); + } -/** - * Metadata for an Aqara device. - */ -class DeviceAqaraMetadata -{ - public static function from_json(mixed $json): DeviceAqaraMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_name: $json->device_name ?? null, - did: $json->did ?? null, - firmware_version: $json->firmware_version ?? null, - model: $json->model ?? null, - model_type: $json->model_type ?? null, - parent_did: $json->parent_did ?? null, - position_id: $json->position_id ?? null, - time_zone: $json->time_zone ?? null, - ); - } - - public function __construct( - /** - * Device name for an Aqara device. - */ - public string|null $device_name, - /** - * Device ID (did) for an Aqara device. - */ - public string|null $did, - /** - * Firmware version for an Aqara device. - */ - public string|null $firmware_version, - /** - * Model identifier for an Aqara device. - */ - public string|null $model, - /** - * Model type for an Aqara device. - */ - public float|null $model_type, - /** - * Parent gateway device ID for an Aqara device. - */ - public string|null $parent_did, - /** - * Position (room) ID for an Aqara device. - */ - public string|null $position_id, - /** - * Time zone reported for an Aqara device (e.g. GMT-07:00). - */ - public string|null $time_zone, - ) {} + public function __construct( + /** + * ID for a wireless keypad for a TTLock device. + */ + public float|null $wireless_keypad_id = null, + /** + * Name for a wireless keypad for a TTLock device. + */ + public string|null $wireless_keypad_name = null, + ) {} + } } -/** - * ASSA ABLOY Credential Service metadata for the phone. - */ -class DeviceAssaAbloyCredentialServiceMetadata -{ - public static function from_json( - mixed $json, - ): DeviceAssaAbloyCredentialServiceMetadata|null { - if (!$json) { - return null; - } - return new self( - endpoints: array_map( - fn($e) => DeviceEndpoints::from_json($e), - $json->endpoints ?? [], - ), - has_active_endpoint: $json->has_active_endpoint ?? null, - ); - } - - public function __construct( - /** - * Endpoints associated with the phone. - */ - public array $endpoints, - /** - * Indicates whether the credential service has active endpoints associated with the phone. - */ - public bool|null $has_active_endpoint, - ) {} +namespace Seam\Resources\Device\Properties\CodeConstraints { + enum ConstraintType: string + { + case NO_ZEROS = "no_zeros"; + case CANNOT_START_WITH_12 = "cannot_start_with_12"; + case NO_TRIPLE_CONSECUTIVE_INTS = "no_triple_consecutive_ints"; + case CANNOT_SPECIFY_PIN_CODE = "cannot_specify_pin_code"; + case PIN_CODE_MATCHES_EXISTING_SET = "pin_code_matches_existing_set"; + case START_DATE_IN_FUTURE = "start_date_in_future"; + case NO_ASCENDING_OR_DESCENDING_SEQUENCE = "no_ascending_or_descending_sequence"; + case AT_LEAST_THREE_UNIQUE_DIGITS = "at_least_three_unique_digits"; + case CANNOT_CONTAIN_089 = "cannot_contain_089"; + case CANNOT_CONTAIN_0789 = "cannot_contain_0789"; + case UNIQUE_FIRST_FOUR_DIGITS = "unique_first_four_digits"; + case NO_ALL_SAME_DIGITS = "no_all_same_digits"; + case NAME_LENGTH = "name_length"; + case NAME_MUST_BE_UNIQUE = "name_must_be_unique"; + } } -/** - * Metadata for an ASSA ABLOY Vostio system. - */ -class DeviceAssaAbloyVostioMetadata -{ - public static function from_json( - mixed $json, - ): DeviceAssaAbloyVostioMetadata|null { - if (!$json) { - return null; - } - return new self(encoder_name: $json->encoder_name ?? null); - } - - public function __construct( - /** - * Encoder name for an ASSA ABLOY Vostio system. - */ - public string|null $encoder_name, - ) {} -} +namespace Seam\Resources\Device\Properties\OfflineTimeFrameOptions { + /** + * Fixed start/end time pairings the caller chooses from. Mutually exclusive with `matching_start_end_time`. + */ + class TimePairs + { + public static function from_json(mixed $json): TimePairs|null + { + if (!$json) { + return null; + } + return new self( + display_name: $json->display_name ?? null, + end_time: $json->end_time ?? null, + start_time: $json->start_time ?? null, + ); + } -/** - * Metadata for an August device. - */ -class DeviceAugustMetadata -{ - public static function from_json(mixed $json): DeviceAugustMetadata|null - { - if (!$json) { - return null; - } - return new self( - has_keypad: $json->has_keypad ?? null, - house_id: $json->house_id ?? null, - house_name: $json->house_name ?? null, - keypad_battery_level: $json->keypad_battery_level ?? null, - lock_id: $json->lock_id ?? null, - lock_name: $json->lock_name ?? null, - model: $json->model ?? null, - ); - } - - public function __construct( - /** - * Indicates whether an August device has a keypad. - */ - public bool|null $has_keypad, - /** - * House ID for an August device. - */ - public string|null $house_id, - /** - * House name for an August device. - */ - public string|null $house_name, - /** - * Keypad battery level for an August device. - */ - public string|null $keypad_battery_level, - /** - * Lock ID for an August device. - */ - public string|null $lock_id, - /** - * Lock name for an August device. - */ - public string|null $lock_name, - /** - * Model for an August device. - */ - public string|null $model, - ) {} + public function __construct( + /** + * Label for the start/end time pairing. + */ + public string|null $display_name, + /** + * End time of day as a 24-hour `HH:MM` value, interpreted in the option's `time_zone`. An `end_time` earlier on the clock than `start_time` means the end falls on a later date. + */ + public string|null $end_time, + /** + * Start time of day as a 24-hour `HH:MM` value, interpreted in the option's `time_zone`. + */ + public string|null $start_time, + ) {} + } } -/** - * Available [climate presets](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) for the thermostat. - */ -class DeviceAvailableClimatePresets -{ - public static function from_json( - mixed $json, - ): DeviceAvailableClimatePresets|null { - if (!$json) { - return null; - } - return new self( - can_delete: $json->can_delete ?? null, - can_edit: $json->can_edit ?? null, - can_use_with_thermostat_daily_programs: $json->can_use_with_thermostat_daily_programs ?? - null, - climate_preset_key: $json->climate_preset_key ?? null, - climate_preset_mode: $json->climate_preset_mode ?? null, - cooling_set_point_celsius: $json->cooling_set_point_celsius ?? null, - cooling_set_point_fahrenheit: $json->cooling_set_point_fahrenheit ?? - null, - display_name: $json->display_name ?? null, - ecobee_metadata: isset($json->ecobee_metadata) - ? DeviceEcobeeMetadata::from_json($json->ecobee_metadata) - : null, - fan_mode_setting: $json->fan_mode_setting ?? null, - heating_set_point_celsius: $json->heating_set_point_celsius ?? null, - heating_set_point_fahrenheit: $json->heating_set_point_fahrenheit ?? - null, - hvac_mode_setting: $json->hvac_mode_setting ?? null, - manual_override_allowed: $json->manual_override_allowed ?? null, - name: $json->name ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be deleted. - */ - public bool|null $can_delete, - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be edited. - */ - public bool|null $can_edit, - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be programmed in a thermostat daily program. - */ - public bool|null $can_use_with_thermostat_daily_programs, - /** - * Unique key to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $climate_preset_key, - /** - * The climate preset mode for the thermostat, based on the available climate preset modes reported by the device. - */ - public string|null $climate_preset_mode, - /** - * Temperature to which the thermostat should cool (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $cooling_set_point_celsius, - /** - * Temperature to which the thermostat should cool (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $cooling_set_point_fahrenheit, - /** - * Display name for the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $display_name, - /** - * Metadata specific to the Ecobee climate, if applicable. - */ - public DeviceEcobeeMetadata|null $ecobee_metadata, - /** - * Desired [fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings), such as `on`, `auto`, or `circulate`. - */ - public string|null $fan_mode_setting, - /** - * Temperature to which the thermostat should heat (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $heating_set_point_celsius, - /** - * Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $heating_set_point_fahrenheit, - /** - * Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. - */ - public string|null $hvac_mode_setting, - /** - * Indicates whether a person at the thermostat can change the thermostat's settings. See [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). - * - * @deprecated Use 'thermostat_schedule.is_override_allowed' - */ - public bool|null $manual_override_allowed, - /** - * User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $name, - ) {} +namespace Seam\Resources\Device\Properties\OnlineTimeFrameOptions { + /** + * Fixed start/end time pairings the caller chooses from. Mutually exclusive with `matching_start_end_time`. + */ + class TimePairs + { + public static function from_json(mixed $json): TimePairs|null + { + if (!$json) { + return null; + } + return new self( + display_name: $json->display_name ?? null, + end_time: $json->end_time ?? null, + start_time: $json->start_time ?? null, + ); + } + + public function __construct( + /** + * Label for the start/end time pairing. + */ + public string|null $display_name, + /** + * End time of day as a 24-hour `HH:MM` value, interpreted in the option's `time_zone`. An `end_time` earlier on the clock than `start_time` means the end falls on a later date. + */ + public string|null $end_time, + /** + * Start time of day as a 24-hour `HH:MM` value, interpreted in the option's `time_zone`. + */ + public string|null $start_time, + ) {} + } } -/** - * Metadata for an Avigilon Alta system. - */ -class DeviceAvigilonAltaMetadata -{ - public static function from_json( - mixed $json, - ): DeviceAvigilonAltaMetadata|null { - if (!$json) { - return null; - } - return new self( - entry_name: $json->entry_name ?? null, - entry_relays_total_count: $json->entry_relays_total_count ?? null, - org_name: $json->org_name ?? null, - site_id: $json->site_id ?? null, - site_name: $json->site_name ?? null, - zone_id: $json->zone_id ?? null, - zone_name: $json->zone_name ?? null, - ); - } - - public function __construct( - /** - * Entry name for an Avigilon Alta system. - */ - public string|null $entry_name, - /** - * Total count of entry relays for an Avigilon Alta system. - */ - public float|null $entry_relays_total_count, - /** - * Organization name for an Avigilon Alta system. - */ - public string|null $org_name, - /** - * Site ID for an Avigilon Alta system. - */ - public float|null $site_id, - /** - * Site name for an Avigilon Alta system. - */ - public string|null $site_name, - /** - * Zone ID for an Avigilon Alta system. - */ - public float|null $zone_id, - /** - * Zone name for an Avigilon Alta system. - */ - public string|null $zone_name, - ) {} +namespace Seam\Resources\Device\Properties\ActiveThermostatSchedule { + /** + * Errors associated with the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } } -/** - * Keypad battery properties. - */ -class DeviceBattery -{ - public static function from_json(mixed $json): DeviceBattery|null +namespace Seam\Resources\Device\Properties\AvailableClimatePresets { + /** + * Metadata specific to the Ecobee climate, if applicable. + */ + class EcobeeMetadata { - if (!$json) { - return null; + public static function from_json(mixed $json): EcobeeMetadata|null + { + if (!$json) { + return null; + } + return new self( + climate_ref: $json->climate_ref ?? null, + is_optimized: $json->is_optimized ?? null, + owner: $json->owner ?? null, + ); } - return new self(level: $json->level ?? null); + + public function __construct( + /** + * Reference to the Ecobee climate, if applicable. + */ + public string|null $climate_ref = null, + /** + * Indicates if the climate preset is optimized by Ecobee. + */ + public bool|null $is_optimized = null, + /** + * Indicates whether the climate preset is owned by the user or the system. + * + * @var value-of<\Seam\Resources\Device\Properties\AvailableClimatePresets\EcobeeMetadata\Owner>|string|null + */ + public string|null $owner = null, + ) {} } - public function __construct(public float|null $level) {} -} + enum ClimatePresetMode: string + { + case HOME = "home"; + case AWAY = "away"; + case WAKE = "wake"; + case SLEEP = "sleep"; + case OCCUPIED = "occupied"; + case UNOCCUPIED = "unoccupied"; + } -/** - * Metadata for a Brivo device. - */ -class DeviceBrivoMetadata -{ - public static function from_json(mixed $json): DeviceBrivoMetadata|null - { - if (!$json) { - return null; - } - return new self( - activation_enabled: $json->activation_enabled ?? null, - device_name: $json->device_name ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the Brivo access point has activation (remote unlock) enabled. - */ - public bool|null $activation_enabled, - /** - * Device name for a Brivo device. - */ - public string|null $device_name, - ) {} -} + enum FanModeSetting: string + { + case AUTO = "auto"; + case ON = "on"; + case CIRCULATE = "circulate"; + } -/** - * Constraints on access codes for the device. Seam represents each constraint as an object with a `constraint_type` property. Depending on the constraint type, there may also be additional properties. Note that some constraints are manufacturer- or device-specific. - */ -class DeviceCodeConstraints -{ - public static function from_json(mixed $json): DeviceCodeConstraints|null - { - if (!$json) { - return null; - } - return new self( - constraint_type: $json->constraint_type ?? null, - max_length: $json->max_length ?? null, - min_length: $json->min_length ?? null, - ); - } - - public function __construct( - public string|null $constraint_type, - /** - * Maximum name length constraint for access codes. - */ - public float|null $max_length, - /** - * Minimum name length constraint for access codes. - */ - public float|null $min_length, - ) {} + enum HvacModeSetting: string + { + case OFF = "off"; + case HEAT = "heat"; + case COOL = "cool"; + case HEAT_COOL = "heat_cool"; + case ECO = "eco"; + } } -/** - * Metadata for a ControlByWeb device. - */ -class DeviceControlbywebMetadata -{ - public static function from_json( - mixed $json, - ): DeviceControlbywebMetadata|null { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - relay_name: $json->relay_name ?? null, - ); - } - - public function __construct( - /** - * Device ID for a ControlByWeb device. - */ - public string|null $device_id, - /** - * Device name for a ControlByWeb device. - */ - public string|null $device_name, - /** - * Relay name for a ControlByWeb device. - */ - public string|null $relay_name, - ) {} +namespace Seam\Resources\Device\Properties\AvailableClimatePresets\EcobeeMetadata { + enum Owner: string + { + case USER = "user"; + case SYSTEM = "system"; + } } -/** - * Current climate setting. - */ -class DeviceCurrentClimateSetting -{ - public static function from_json( - mixed $json, - ): DeviceCurrentClimateSetting|null { - if (!$json) { - return null; - } - return new self( - can_delete: $json->can_delete ?? null, - can_edit: $json->can_edit ?? null, - can_use_with_thermostat_daily_programs: $json->can_use_with_thermostat_daily_programs ?? - null, - climate_preset_key: $json->climate_preset_key ?? null, - climate_preset_mode: $json->climate_preset_mode ?? null, - cooling_set_point_celsius: $json->cooling_set_point_celsius ?? null, - cooling_set_point_fahrenheit: $json->cooling_set_point_fahrenheit ?? - null, - display_name: $json->display_name ?? null, - ecobee_metadata: isset($json->ecobee_metadata) - ? DeviceEcobeeMetadata::from_json($json->ecobee_metadata) - : null, - fan_mode_setting: $json->fan_mode_setting ?? null, - heating_set_point_celsius: $json->heating_set_point_celsius ?? null, - heating_set_point_fahrenheit: $json->heating_set_point_fahrenheit ?? - null, - hvac_mode_setting: $json->hvac_mode_setting ?? null, - manual_override_allowed: $json->manual_override_allowed ?? null, - name: $json->name ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be deleted. - */ - public bool|null $can_delete, - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be edited. - */ - public bool|null $can_edit, - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be programmed in a thermostat daily program. - */ - public bool|null $can_use_with_thermostat_daily_programs, - /** - * Unique key to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $climate_preset_key, - /** - * The climate preset mode for the thermostat, based on the available climate preset modes reported by the device. - */ - public string|null $climate_preset_mode, - /** - * Temperature to which the thermostat should cool (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $cooling_set_point_celsius, - /** - * Temperature to which the thermostat should cool (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $cooling_set_point_fahrenheit, - /** - * Display name for the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $display_name, - /** - * Metadata specific to the Ecobee climate, if applicable. - */ - public DeviceEcobeeMetadata|null $ecobee_metadata, - /** - * Desired [fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings), such as `on`, `auto`, or `circulate`. - */ - public string|null $fan_mode_setting, - /** - * Temperature to which the thermostat should heat (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $heating_set_point_celsius, - /** - * Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $heating_set_point_fahrenheit, - /** - * Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. - */ - public string|null $hvac_mode_setting, - /** - * Indicates whether a person at the thermostat can change the thermostat's settings. See [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). - * - * @deprecated Use 'thermostat_schedule.is_override_allowed' - */ - public bool|null $manual_override_allowed, - /** - * User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $name, - ) {} -} +namespace Seam\Resources\Device\Properties\CurrentClimateSetting { + /** + * Metadata specific to the Ecobee climate, if applicable. + */ + class EcobeeMetadata + { + public static function from_json(mixed $json): EcobeeMetadata|null + { + if (!$json) { + return null; + } + return new self( + climate_ref: $json->climate_ref ?? null, + is_optimized: $json->is_optimized ?? null, + owner: $json->owner ?? null, + ); + } -class DeviceDefaultClimateSetting -{ - public static function from_json( - mixed $json, - ): DeviceDefaultClimateSetting|null { - if (!$json) { - return null; - } - return new self( - can_delete: $json->can_delete ?? null, - can_edit: $json->can_edit ?? null, - can_use_with_thermostat_daily_programs: $json->can_use_with_thermostat_daily_programs ?? - null, - climate_preset_key: $json->climate_preset_key ?? null, - climate_preset_mode: $json->climate_preset_mode ?? null, - cooling_set_point_celsius: $json->cooling_set_point_celsius ?? null, - cooling_set_point_fahrenheit: $json->cooling_set_point_fahrenheit ?? - null, - display_name: $json->display_name ?? null, - ecobee_metadata: isset($json->ecobee_metadata) - ? DeviceEcobeeMetadata::from_json($json->ecobee_metadata) - : null, - fan_mode_setting: $json->fan_mode_setting ?? null, - heating_set_point_celsius: $json->heating_set_point_celsius ?? null, - heating_set_point_fahrenheit: $json->heating_set_point_fahrenheit ?? - null, - hvac_mode_setting: $json->hvac_mode_setting ?? null, - manual_override_allowed: $json->manual_override_allowed ?? null, - name: $json->name ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be deleted. - */ - public bool|null $can_delete, - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be edited. - */ - public bool|null $can_edit, - /** - * Indicates whether the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) key can be programmed in a thermostat daily program. - */ - public bool|null $can_use_with_thermostat_daily_programs, - /** - * Unique key to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $climate_preset_key, - /** - * The climate preset mode for the thermostat, based on the available climate preset modes reported by the device. - */ - public string|null $climate_preset_mode, - /** - * Temperature to which the thermostat should cool (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $cooling_set_point_celsius, - /** - * Temperature to which the thermostat should cool (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $cooling_set_point_fahrenheit, - /** - * Display name for the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $display_name, - /** - * Metadata specific to the Ecobee climate, if applicable. - */ - public DeviceEcobeeMetadata|null $ecobee_metadata, - /** - * Desired [fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings), such as `on`, `auto`, or `circulate`. - */ - public string|null $fan_mode_setting, - /** - * Temperature to which the thermostat should heat (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $heating_set_point_celsius, - /** - * Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $heating_set_point_fahrenheit, - /** - * Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. - */ - public string|null $hvac_mode_setting, - /** - * Indicates whether a person at the thermostat can change the thermostat's settings. See [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). - * - * @deprecated Use 'thermostat_schedule.is_override_allowed' - */ - public bool|null $manual_override_allowed, - /** - * User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). - */ - public string|null $name, - ) {} -} + public function __construct( + /** + * Reference to the Ecobee climate, if applicable. + */ + public string|null $climate_ref = null, + /** + * Indicates if the climate preset is optimized by Ecobee. + */ + public bool|null $is_optimized = null, + /** + * Indicates whether the climate preset is owned by the user or the system. + * + * @var value-of<\Seam\Resources\Device\Properties\CurrentClimateSetting\EcobeeMetadata\Owner>|string|null + */ + public string|null $owner = null, + ) {} + } -/** - * Manufacturer of the device. Represents the hardware brand, which may differ from the provider. - */ -class DeviceDeviceManufacturer -{ - public static function from_json(mixed $json): DeviceDeviceManufacturer|null - { - if (!$json) { - return null; - } - return new self( - display_name: $json->display_name ?? null, - image_url: $json->image_url ?? null, - manufacturer: $json->manufacturer ?? null, - ); - } - - public function __construct( - /** - * Display name for the manufacturer, such as `August`, `Yale`, `Salto`, and so on. - */ - public string|null $display_name, - /** - * Image URL for the manufacturer logo. - */ - public string|null $image_url, - /** - * Manufacturer identifier, such as `august`, `yale`, `salto`, and so on. - */ - public string|null $manufacturer, - ) {} -} + enum ClimatePresetMode: string + { + case HOME = "home"; + case AWAY = "away"; + case WAKE = "wake"; + case SLEEP = "sleep"; + case OCCUPIED = "occupied"; + case UNOCCUPIED = "unoccupied"; + } -/** - * Provider of the device. Represents the third-party service through which the device is controlled. - */ -class DeviceDeviceProvider -{ - public static function from_json(mixed $json): DeviceDeviceProvider|null - { - if (!$json) { - return null; - } - return new self( - device_provider_name: $json->device_provider_name ?? null, - display_name: $json->display_name ?? null, - image_url: $json->image_url ?? null, - provider_category: $json->provider_category ?? null, - ); - } - - public function __construct( - /** - * Device provider name. Corresponds to the integration type, such as `august`, `schlage`, `yale_access`, and so on. - */ - public string|null $device_provider_name, - /** - * Display name for the device provider type. - */ - public string|null $display_name, - /** - * Image URL for the device provider. - */ - public string|null $image_url, - /** - * Provider category. Indicates the third-party provider type, such as `stable`, for stable integrations, or `internal`, for internal integrations. - */ - public string|null $provider_category, - ) {} -} + enum FanModeSetting: string + { + case AUTO = "auto"; + case ON = "on"; + case CIRCULATE = "circulate"; + } -/** - * Metadata for a dormakaba Oracode device. - */ -class DeviceDormakabaOracodeMetadata -{ - public static function from_json( - mixed $json, - ): DeviceDormakabaOracodeMetadata|null { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - door_id: $json->door_id ?? null, - door_is_wireless: $json->door_is_wireless ?? null, - door_name: $json->door_name ?? null, - iana_timezone: $json->iana_timezone ?? null, - predefined_time_slots: array_map( - fn($p) => DevicePredefinedTimeSlots::from_json($p), - $json->predefined_time_slots ?? [], - ), - site_id: $json->site_id ?? null, - site_name: $json->site_name ?? null, - ); - } - - public function __construct( - /** - * Device ID for a dormakaba Oracode device. - */ - public mixed $device_id, - /** - * Door ID for a dormakaba Oracode device. - */ - public float|null $door_id, - /** - * Indicates whether a door is wireless for a dormakaba Oracode device. - */ - public bool|null $door_is_wireless, - /** - * Door name for a dormakaba Oracode device. - */ - public string|null $door_name, - /** - * IANA time zone for a dormakaba Oracode device. - */ - public string|null $iana_timezone, - /** - * Predefined time slots for a dormakaba Oracode device. - */ - public array $predefined_time_slots, - /** - * Site ID for a dormakaba Oracode device. - * - * @deprecated Previously marked as "@DEPRECATED." - */ - public float|null $site_id, - /** - * Site name for a dormakaba Oracode device. - */ - public string|null $site_name, - ) {} + enum HvacModeSetting: string + { + case OFF = "off"; + case HEAT = "heat"; + case COOL = "cool"; + case HEAT_COOL = "heat_cool"; + case ECO = "eco"; + } } -/** - * Metadata for an ecobee device. - */ -class DeviceEcobeeMetadata -{ - public static function from_json(mixed $json): DeviceEcobeeMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_name: $json->device_name ?? null, - ecobee_device_id: $json->ecobee_device_id ?? null, - ); - } - - public function __construct( - /** - * Device name for an ecobee device. - */ - public string|null $device_name, - /** - * Device ID for an ecobee device. - */ - public string|null $ecobee_device_id, - ) {} +namespace Seam\Resources\Device\Properties\CurrentClimateSetting\EcobeeMetadata { + enum Owner: string + { + case USER = "user"; + case SYSTEM = "system"; + } } -/** - * Endpoints associated with the phone. - */ -class DeviceEndpoints -{ - public static function from_json(mixed $json): DeviceEndpoints|null - { - if (!$json) { - return null; - } - return new self( - endpoint_id: $json->endpoint_id ?? null, - is_active: $json->is_active ?? null, - ); - } - - public function __construct( - /** - * ID of the associated endpoint. - */ - public string|null $endpoint_id, - /** - * Indicated whether the endpoint is active. - */ - public bool|null $is_active, - ) {} -} +namespace Seam\Resources\Device\Properties\DefaultClimateSetting { + /** + * Metadata specific to the Ecobee climate, if applicable. + */ + class EcobeeMetadata + { + public static function from_json(mixed $json): EcobeeMetadata|null + { + if (!$json) { + return null; + } + return new self( + climate_ref: $json->climate_ref ?? null, + is_optimized: $json->is_optimized ?? null, + owner: $json->owner ?? null, + ); + } -/** - * Array of errors associated with the device. Each error object within the array contains two fields: `error_code` and `message`. `error_code` is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. - */ -class DeviceErrors -{ - public static function from_json(mixed $json): DeviceErrors|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - is_bridge_error: $json->is_bridge_error ?? null, - is_connected_account_error: $json->is_connected_account_error ?? - null, - is_device_error: $json->is_device_error ?? null, - message: $json->message ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). - */ - public bool|null $is_bridge_error, - /** - * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. - */ - public bool|null $is_connected_account_error, - /** - * Indicates that the error is not a device error. - */ - public bool|null $is_device_error, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} -} + public function __construct( + /** + * Reference to the Ecobee climate, if applicable. + */ + public string|null $climate_ref = null, + /** + * Indicates if the climate preset is optimized by Ecobee. + */ + public bool|null $is_optimized = null, + /** + * Indicates whether the climate preset is owned by the user or the system. + * + * @var value-of<\Seam\Resources\Device\Properties\DefaultClimateSetting\EcobeeMetadata\Owner>|string|null + */ + public string|null $owner = null, + ) {} + } -/** - * Features for a TTLock device. - */ -class DeviceFeatures -{ - public static function from_json(mixed $json): DeviceFeatures|null - { - if (!$json) { - return null; - } - return new self( - auto_lock_time_config: $json->auto_lock_time_config ?? null, - incomplete_keyboard_passcode: $json->incomplete_keyboard_passcode ?? - null, - lock_command: $json->lock_command ?? null, - passcode: $json->passcode ?? null, - passcode_management: $json->passcode_management ?? null, - unlock_via_gateway: $json->unlock_via_gateway ?? null, - wifi: $json->wifi ?? null, - ); - } - - public function __construct( - /** - * Indicates whether a TTLock device supports auto-lock time configuration. - */ - public bool|null $auto_lock_time_config, - /** - * Indicates whether a TTLock device supports an incomplete keyboard passcode. - */ - public bool|null $incomplete_keyboard_passcode, - /** - * Indicates whether a TTLock device supports the lock command. - */ - public bool|null $lock_command, - /** - * Indicates whether a TTLock device supports a passcode. - */ - public bool|null $passcode, - /** - * Indicates whether a TTLock device supports passcode management. - */ - public bool|null $passcode_management, - /** - * Indicates whether a TTLock device supports unlock via gateway. - */ - public bool|null $unlock_via_gateway, - /** - * Indicates whether a TTLock device supports Wi-Fi. - */ - public bool|null $wifi, - ) {} -} + enum ClimatePresetMode: string + { + case HOME = "home"; + case AWAY = "away"; + case WAKE = "wake"; + case SLEEP = "sleep"; + case OCCUPIED = "occupied"; + case UNOCCUPIED = "unoccupied"; + } -/** - * Metadata for a 4SUITES device. - */ -class DeviceFourSuitesMetadata -{ - public static function from_json(mixed $json): DeviceFourSuitesMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - reclose_delay_in_seconds: $json->reclose_delay_in_seconds ?? null, - ); - } - - public function __construct( - /** - * Device ID for a 4SUITES device. - */ - public float|null $device_id, - /** - * Device name for a 4SUITES device. - */ - public string|null $device_name, - /** - * Reclose delay, in seconds, for a 4SUITES device. - */ - public float|null $reclose_delay_in_seconds, - ) {} -} + enum FanModeSetting: string + { + case AUTO = "auto"; + case ON = "on"; + case CIRCULATE = "circulate"; + } -/** - * Metadata for a Genie device. - */ -class DeviceGenieMetadata -{ - public static function from_json(mixed $json): DeviceGenieMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_name: $json->device_name ?? null, - door_name: $json->door_name ?? null, - ); - } - - public function __construct( - /** - * Lock name for a Genie device. - */ - public string|null $device_name, - /** - * Door name for a Genie device. - */ - public string|null $door_name, - ) {} + enum HvacModeSetting: string + { + case OFF = "off"; + case HEAT = "heat"; + case COOL = "cool"; + case HEAT_COOL = "heat_cool"; + case ECO = "eco"; + } } -/** - * Metadata for a Honeywell Resideo device. - */ -class DeviceHoneywellResideoMetadata -{ - public static function from_json( - mixed $json, - ): DeviceHoneywellResideoMetadata|null { - if (!$json) { - return null; - } - return new self( - device_name: $json->device_name ?? null, - honeywell_resideo_device_id: $json->honeywell_resideo_device_id ?? - null, - ); - } - - public function __construct( - /** - * Device name for a Honeywell Resideo device. - */ - public string|null $device_name, - /** - * Device ID for a Honeywell Resideo device. - */ - public string|null $honeywell_resideo_device_id, - ) {} +namespace Seam\Resources\Device\Properties\DefaultClimateSetting\EcobeeMetadata { + enum Owner: string + { + case USER = "user"; + case SYSTEM = "system"; + } } -/** - * Latest humidity reading for a Minut device. - */ -class DeviceHumidity -{ - public static function from_json(mixed $json): DeviceHumidity|null - { - if (!$json) { - return null; - } - return new self(time: $json->time ?? null, value: $json->value ?? null); - } - - public function __construct( - /** - * Time of latest humidity reading for a Minut device. - */ - public string|null $time, - /** - * Value of latest humidity reading for a Minut device. - */ - public float|null $value, - ) {} -} +namespace Seam\Resources\Device\Properties\ThermostatDailyPrograms { + /** + * Array of thermostat daily program periods. + */ + class Periods + { + public static function from_json(mixed $json): Periods|null + { + if (!$json) { + return null; + } + return new self( + climate_preset_key: $json->climate_preset_key ?? null, + starts_at_time: $json->starts_at_time ?? null, + ); + } -/** - * Metadata for an igloohome device. - */ -class DeviceIgloohomeMetadata -{ - public static function from_json(mixed $json): DeviceIgloohomeMetadata|null - { - if (!$json) { - return null; - } - return new self( - bridge_id: $json->bridge_id ?? null, - bridge_name: $json->bridge_name ?? null, - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - is_accessory_keypad_linked_to_bridge: $json->is_accessory_keypad_linked_to_bridge ?? - null, - keypad_id: $json->keypad_id ?? null, - ); - } - - public function __construct( - /** - * Bridge ID for an igloohome device. - */ - public string|null $bridge_id, - /** - * Bridge name for an igloohome device. - */ - public string|null $bridge_name, - /** - * Device ID for an igloohome device. - */ - public string|null $device_id, - /** - * Device name for an igloohome device. - */ - public string|null $device_name, - /** - * Indicates whether a keypad is linked to a bridge for an igloohome device. - */ - public bool|null $is_accessory_keypad_linked_to_bridge, - /** - * Keypad ID for an igloohome device. - */ - public string|null $keypad_id, - ) {} + public function __construct( + /** + * Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to activate at the `starts_at_time`. + */ + public string|null $climate_preset_key, + /** + * Time at which the thermostat daily program period starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at_time, + ) {} + } } -/** - * Metadata for an igloo device. - */ -class DeviceIglooMetadata -{ - public static function from_json(mixed $json): DeviceIglooMetadata|null - { - if (!$json) { - return null; - } - return new self( - bridge_id: $json->bridge_id ?? null, - device_id: $json->device_id ?? null, - model: $json->model ?? null, - ); - } - - public function __construct( - /** - * Bridge ID for an igloo device. - */ - public string|null $bridge_id, - /** - * Device ID for an igloo device. - */ - public string|null $device_id, - /** - * Model for an igloo device. - */ - public string|null $model, - ) {} -} +namespace Seam\Resources\Device\Warnings { + /** + * Indicates that the backup access code is unhealthy. + */ + final class PartialBackupAccessCodePool extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): PartialBackupAccessCodePool|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a KeyNest device. - */ -class DeviceKeynestMetadata -{ - public static function from_json(mixed $json): DeviceKeynestMetadata|null - { - if (!$json) { - return null; - } - return new self( - address: $json->address ?? null, - current_or_last_store_id: $json->current_or_last_store_id ?? null, - current_status: $json->current_status ?? null, - current_user_company: $json->current_user_company ?? null, - current_user_email: $json->current_user_email ?? null, - current_user_name: $json->current_user_name ?? null, - current_user_phone_number: $json->current_user_phone_number ?? null, - default_office_id: $json->default_office_id ?? null, - device_name: $json->device_name ?? null, - fob_id: $json->fob_id ?? null, - handover_method: $json->handover_method ?? null, - has_photo: $json->has_photo ?? null, - is_quadient_locker: $json->is_quadient_locker ?? null, - key_id: $json->key_id ?? null, - key_notes: $json->key_notes ?? null, - keynest_app_user: $json->keynest_app_user ?? null, - last_movement: $json->last_movement ?? null, - property_id: $json->property_id ?? null, - property_postcode: $json->property_postcode ?? null, - status_type: $json->status_type ?? null, - subscription_plan: $json->subscription_plan ?? null, - ); - } - - public function __construct( - /** - * Address for a KeyNest device. - */ - public string|null $address, - /** - * Current or last store ID for a KeyNest device. - */ - public float|null $current_or_last_store_id, - /** - * Current status for a KeyNest device. - */ - public string|null $current_status, - /** - * Current user company for a KeyNest device. - */ - public string|null $current_user_company, - /** - * Current user email for a KeyNest device. - */ - public string|null $current_user_email, - /** - * Current user name for a KeyNest device. - */ - public string|null $current_user_name, - /** - * Current user phone number for a KeyNest device. - */ - public string|null $current_user_phone_number, - /** - * Default office ID for a KeyNest device. - */ - public float|null $default_office_id, - /** - * Device name for a KeyNest device. - */ - public string|null $device_name, - /** - * Fob ID for a KeyNest device. - */ - public float|null $fob_id, - /** - * Handover method for a KeyNest device. - */ - public string|null $handover_method, - /** - * Whether the KeyNest device has a photo. - */ - public bool|null $has_photo, - /** - * Whether the key is in a locker that does not support the access codes API. - */ - public bool|null $is_quadient_locker, - /** - * Key ID for a KeyNest device. - */ - public string|null $key_id, - /** - * Key notes for a KeyNest device. - */ - public string|null $key_notes, - /** - * KeyNest app user for a KeyNest device. - */ - public string|null $keynest_app_user, - /** - * Last movement timestamp for a KeyNest device. - */ - public string|null $last_movement, - /** - * Property ID for a KeyNest device. - */ - public string|null $property_id, - /** - * Property postcode for a KeyNest device. - */ - public string|null $property_postcode, - /** - * Status type for a KeyNest device. - */ - public string|null $status_type, - /** - * Subscription plan for a KeyNest device. - */ - public string|null $subscription_plan, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Keypad battery status. - */ -class DeviceKeypadBattery -{ - public static function from_json(mixed $json): DeviceKeypadBattery|null + /** + * Indicates that there are too many backup codes. + */ + final class ManyActiveBackupCodes extends \Seam\Resources\Device\Warnings { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): ManyActiveBackupCodes|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self(level: $json->level ?? null); } - public function __construct( - /** - * Keypad battery charge level. - */ - public float|null $level, - ) {} -} + /** + * Indicates that a third-party integration has been detected. + */ + final class ThirdPartyIntegrationDetected extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): ThirdPartyIntegrationDetected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Kisi device. - */ -class DeviceKisiMetadata -{ - public static function from_json(mixed $json): DeviceKisiMetadata|null - { - if (!$json) { - return null; - } - return new self( - description: $json->description ?? null, - lock_id: $json->lock_id ?? null, - lock_name: $json->lock_name ?? null, - place_name: $json->place_name ?? null, - ); - } - - public function __construct( - /** - * Description for a Kisi device. - */ - public string|null $description, - /** - * Lock ID for a Kisi device. - */ - public float|null $lock_id, - /** - * Lock name for a Kisi device. - */ - public string|null $lock_name, - /** - * Place name for a Kisi device. - */ - public string|null $place_name, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for a Korelock device. - */ -class DeviceKorelockMetadata -{ - public static function from_json(mixed $json): DeviceKorelockMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - firmware_version: $json->firmware_version ?? null, - location_id: $json->location_id ?? null, - model_code: $json->model_code ?? null, - serial_number: $json->serial_number ?? null, - wifi_signal_strength: $json->wifi_signal_strength ?? null, - ); - } - - public function __construct( - /** - * Device ID for a Korelock device. - */ - public string|null $device_id, - /** - * Device name for a Korelock device. - */ - public string|null $device_name, - /** - * Firmware version for a Korelock device. - */ - public string|null $firmware_version, - /** - * Location ID for a Korelock device. Required for timebound access codes. - */ - public string|null $location_id, - /** - * Model code for a Korelock device. - */ - public string|null $model_code, - /** - * Serial number for a Korelock device. - */ - public string|null $serial_number, - /** - * WiFi signal strength (0-1) for a Korelock device. - */ - public float|null $wifi_signal_strength, - ) {} -} + /** + * Indicates that the Remote Unlock feature is not enabled in the settings." + */ + final class TtlockLockGatewayUnlockingNotEnabled extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): TtlockLockGatewayUnlockingNotEnabled|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Kwikset device. - */ -class DeviceKwiksetMetadata -{ - public static function from_json(mixed $json): DeviceKwiksetMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - model_number: $json->model_number ?? null, - ); - } - - public function __construct( - /** - * Device ID for a Kwikset device. - */ - public string|null $device_id, - /** - * Device name for a Kwikset device. - */ - public string|null $device_name, - /** - * Model number for a Kwikset device. - */ - public string|null $model_number, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Latest sensor values for a Minut device. - */ -class DeviceLatestSensorValues -{ - public static function from_json(mixed $json): DeviceLatestSensorValues|null - { - if (!$json) { - return null; - } - return new self( - accelerometer_z: isset($json->accelerometer_z) - ? DeviceAccelerometerZ::from_json($json->accelerometer_z) - : null, - humidity: isset($json->humidity) - ? DeviceHumidity::from_json($json->humidity) - : null, - pressure: isset($json->pressure) - ? DevicePressure::from_json($json->pressure) - : null, - sound: isset($json->sound) - ? DeviceSound::from_json($json->sound) - : null, - temperature: isset($json->temperature) - ? DeviceTemperature::from_json($json->temperature) - : null, - ); - } - - public function __construct( - /** - * Latest accelerometer Z-axis reading for a Minut device. - */ - public DeviceAccelerometerZ|null $accelerometer_z, - /** - * Latest humidity reading for a Minut device. - */ - public DeviceHumidity|null $humidity, - /** - * Latest pressure reading for a Minut device. - */ - public DevicePressure|null $pressure, - /** - * Latest sound reading for a Minut device. - */ - public DeviceSound|null $sound, - /** - * Latest temperature reading for a Minut device. - */ - public DeviceTemperature|null $temperature, - ) {} -} + /** + * Indicates that the gateway signal is weak. + */ + final class TtlockWeakGatewaySignal extends \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): TtlockWeakGatewaySignal|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Location information for the device. - */ -class DeviceLocation -{ - public static function from_json(mixed $json): DeviceLocation|null - { - if (!$json) { - return null; - } - return new self( - location_name: $json->location_name ?? null, - time_zone: $json->time_zone ?? null, - timezone: $json->timezone ?? null, - ); - } - - public function __construct( - /** - * Name of the device location. - */ - public string|null $location_name, - /** - * Time zone of the device location. - */ - public string|null $time_zone, - /** - * Time zone of the device location. - * - * @deprecated Use `time_zone` instead. - */ - public string|null $timezone, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for a Lockly device. - */ -class DeviceLocklyMetadata -{ - public static function from_json(mixed $json): DeviceLocklyMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - model: $json->model ?? null, - ); - } - - public function __construct( - /** - * Device ID for a Lockly device. - */ - public string|null $device_id, - /** - * Device name for a Lockly device. - */ - public string|null $device_name, - /** - * Model for a Lockly device. - */ - public string|null $model, - ) {} -} + /** + * Indicates that the device is in power saving mode and may have limited functionality. + */ + final class PowerSavingMode extends \Seam\Resources\Device\Warnings + { + public static function from_json(mixed $json): PowerSavingMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Minut device. - */ -class DeviceMinutMetadata -{ - public static function from_json(mixed $json): DeviceMinutMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - latest_sensor_values: isset($json->latest_sensor_values) - ? DeviceLatestSensorValues::from_json( - $json->latest_sensor_values, - ) - : null, - ); - } - - public function __construct( - /** - * Device ID for a Minut device. - */ - public string|null $device_id, - /** - * Device name for a Minut device. - */ - public string|null $device_name, - /** - * Latest sensor values for a Minut device. - */ - public DeviceLatestSensorValues|null $latest_sensor_values, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Device model-related properties. - */ -class DeviceModel -{ - public static function from_json(mixed $json): DeviceModel|null - { - if (!$json) { - return null; - } - return new self( - accessory_keypad_supported: $json->accessory_keypad_supported ?? - null, - can_connect_accessory_keypad: $json->can_connect_accessory_keypad ?? - null, - display_name: $json->display_name ?? null, - has_built_in_keypad: $json->has_built_in_keypad ?? null, - manufacturer_display_name: $json->manufacturer_display_name ?? null, - offline_access_codes_supported: $json->offline_access_codes_supported ?? - null, - online_access_codes_supported: $json->online_access_codes_supported ?? - null, - ); - } - - public function __construct( - /** - * @deprecated use device.properties.model.can_connect_accessory_keypad - */ - public bool|null $accessory_keypad_supported, - /** - * Indicates whether the device can connect a accessory keypad. - */ - public bool|null $can_connect_accessory_keypad, - /** - * Display name of the device model. - */ - public string|null $display_name, - /** - * Indicates whether the device has a built in accessory keypad. - */ - public bool|null $has_built_in_keypad, - /** - * Display name that corresponds to the manufacturer-specific terminology for the device. - */ - public string|null $manufacturer_display_name, - /** - * @deprecated use device.can_program_offline_access_codes. - */ - public bool|null $offline_access_codes_supported, - /** - * @deprecated use device.can_program_online_access_codes. - */ - public bool|null $online_access_codes_supported, - ) {} -} + /** + * Indicates that the temperature threshold has been exceeded. + */ + final class TemperatureThresholdExceeded extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): TemperatureThresholdExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Google Nest device. - */ -class DeviceNestMetadata -{ - public static function from_json(mixed $json): DeviceNestMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_custom_name: $json->device_custom_name ?? null, - device_name: $json->device_name ?? null, - display_name: $json->display_name ?? null, - nest_device_id: $json->nest_device_id ?? null, - ); - } - - public function __construct( - /** - * Custom device name for a Google Nest device. The device owner sets this value. - */ - public string|null $device_custom_name, - /** - * Device name for a Google Nest device. Google sets this value. - */ - public string|null $device_name, - /** - * Display name for a Google Nest device. - */ - public string|null $display_name, - /** - * Device ID for a Google Nest device. - */ - public string|null $nest_device_id, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for a NoiseAware device. - */ -class DeviceNoiseawareMetadata -{ - public static function from_json(mixed $json): DeviceNoiseawareMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_model: $json->device_model ?? null, - device_name: $json->device_name ?? null, - noise_level_decibel: $json->noise_level_decibel ?? null, - noise_level_nrs: $json->noise_level_nrs ?? null, - ); - } - - public function __construct( - /** - * Device ID for a NoiseAware device. - */ - public string|null $device_id, - /** - * Device model for a NoiseAware device. - */ - public string|null $device_model, - /** - * Device name for a NoiseAware device. - */ - public string|null $device_name, - /** - * Noise level, in decibels, for a NoiseAware device. - */ - public float|null $noise_level_decibel, - /** - * Noise level, expressed as a Noise Risk Score (NRS), for a NoiseAware device. - */ - public float|null $noise_level_nrs, - ) {} -} + /** + * Indicates that the device appears to be unresponsive. + */ + final class DeviceCommunicationDegraded extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): DeviceCommunicationDegraded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Nuki device. - */ -class DeviceNukiMetadata -{ - public static function from_json(mixed $json): DeviceNukiMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - keypad_2_paired: $json->keypad_2_paired ?? null, - keypad_battery_critical: $json->keypad_battery_critical ?? null, - keypad_paired: $json->keypad_paired ?? null, - ); - } - - public function __construct( - /** - * Device ID for a Nuki device. - */ - public string|null $device_id, - /** - * Device name for a Nuki device. - */ - public string|null $device_name, - /** - * Indicates whether keypad 2 is paired for a Nuki device. - */ - public bool|null $keypad_2_paired, - /** - * Indicates whether the keypad battery is in a critical state for a Nuki device. - */ - public bool|null $keypad_battery_critical, - /** - * Indicates whether the keypad is paired for a Nuki device. - */ - public bool|null $keypad_paired, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Time frames that may be requested when creating an offline access code, expressed as a list of options. The caller picks one option (by matching the requested duration when the options' duration ranges do not overlap, or by `display_name` when they do) and satisfies that one option's rules. When `undefined`, any time frame works. - */ -class DeviceOfflineTimeFrameOptions -{ - public static function from_json( - mixed $json, - ): DeviceOfflineTimeFrameOptions|null { - if (!$json) { - return null; - } - return new self( - display_name: $json->display_name ?? null, - end_date_recurrence_rule: $json->end_date_recurrence_rule ?? null, - matching_start_end_time: $json->matching_start_end_time ?? null, - max_duration: $json->max_duration ?? null, - min_duration: $json->min_duration ?? null, - start_date_recurrence_rule: $json->start_date_recurrence_rule ?? - null, - time_pairs: array_map( - fn($t) => DeviceTimePairs::from_json($t), - $json->time_pairs ?? [], - ), - time_zone: $json->time_zone ?? null, - ); - } - - public function __construct( - /** - * Label for this option. For a single-option device, the product name (for example, `algoPIN` or `SmartPIN`); for a multi-option device, a label that distinguishes it (for example, `Hourly` or `Fixed start times`). - */ - public string|null $display_name, - /** - * iCalendar recurrence rule (RRULE) that the end date must fall on. Constrains which calendar dates are selectable, independent of the time-of-day rules. - */ - public string|null $end_date_recurrence_rule, - /** - * When `true`, the start and end must fall at the same time of day (the caller picks which). Mutually exclusive with `time_pairs`. - */ - public bool|null $matching_start_end_time, - /** - * Maximum duration this option covers, as an ISO 8601 duration (for example, `PT672H` or `P367D`). Omitted when there is no maximum. - */ - public string|null $max_duration, - /** - * Minimum duration this option covers, as an ISO 8601 duration (for example, `PT1H` or `P29D`). Omitted when there is no minimum. - */ - public string|null $min_duration, - /** - * iCalendar recurrence rule (RRULE) that the start date must fall on (for example, `FREQ=MONTHLY;BYDAY=1MO,3MO`). Constrains which calendar dates are selectable, independent of the time-of-day rules. - */ - public string|null $start_date_recurrence_rule, - /** - * Fixed start/end time pairings the caller chooses from. Mutually exclusive with `matching_start_end_time`. - */ - public array $time_pairs, - /** - * IANA time zone for interpreting `time_pairs` and the date recurrence rules. Present only when the option fixes times or dates. - */ - public string|null $time_zone, - ) {} -} + /** + * Indicates that a scheduled maintenance window has been detected. + */ + final class ScheduledMaintenanceWindow extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): ScheduledMaintenanceWindow|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for an Omnitec device. - */ -class DeviceOmnitecMetadata -{ - public static function from_json(mixed $json): DeviceOmnitecMetadata|null - { - if (!$json) { - return null; - } - return new self( - has_gateway: $json->has_gateway ?? null, - lock_alias: $json->lock_alias ?? null, - lock_id: $json->lock_id ?? null, - lock_mac: $json->lock_mac ?? null, - lock_name: $json->lock_name ?? null, - time_zone: $json->time_zone ?? null, - timezone_raw_offset_ms: $json->timezone_raw_offset_ms ?? null, - ); - } - - public function __construct( - /** - * Whether the Omnitec lock has a connected gateway for remote operations. - */ - public bool|null $has_gateway, - /** - * Operator-assigned alias for an Omnitec device. - */ - public string|null $lock_alias, - /** - * Lock ID for an Omnitec device. - */ - public float|null $lock_id, - /** - * Bluetooth MAC address for an Omnitec device. - */ - public string|null $lock_mac, - /** - * Lock name for an Omnitec device. - */ - public string|null $lock_name, - /** - * IANA time zone for the Omnitec device, used to schedule time-bound access codes at the correct local time (accounting for DST). - */ - public string|null $time_zone, - /** - * Static UTC offset of the Omnitec lock in milliseconds. Does not account for DST. - */ - public float|null $timezone_raw_offset_ms, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Time frames that may be requested when creating an online access code, expressed as a list of options. The caller picks one option (by matching the requested duration when the options' duration ranges do not overlap, or by `display_name` when they do) and satisfies that one option's rules. When `undefined`, any time frame works. - */ -class DeviceOnlineTimeFrameOptions -{ - public static function from_json( - mixed $json, - ): DeviceOnlineTimeFrameOptions|null { - if (!$json) { - return null; - } - return new self( - display_name: $json->display_name ?? null, - end_date_recurrence_rule: $json->end_date_recurrence_rule ?? null, - matching_start_end_time: $json->matching_start_end_time ?? null, - max_duration: $json->max_duration ?? null, - min_duration: $json->min_duration ?? null, - start_date_recurrence_rule: $json->start_date_recurrence_rule ?? - null, - time_pairs: array_map( - fn($t) => DeviceTimePairs::from_json($t), - $json->time_pairs ?? [], - ), - time_zone: $json->time_zone ?? null, - ); - } - - public function __construct( - /** - * Label for this option. For a single-option device, the product name (for example, `algoPIN` or `SmartPIN`); for a multi-option device, a label that distinguishes it (for example, `Hourly` or `Fixed start times`). - */ - public string|null $display_name, - /** - * iCalendar recurrence rule (RRULE) that the end date must fall on. Constrains which calendar dates are selectable, independent of the time-of-day rules. - */ - public string|null $end_date_recurrence_rule, - /** - * When `true`, the start and end must fall at the same time of day (the caller picks which). Mutually exclusive with `time_pairs`. - */ - public bool|null $matching_start_end_time, - /** - * Maximum duration this option covers, as an ISO 8601 duration (for example, `PT672H` or `P367D`). Omitted when there is no maximum. - */ - public string|null $max_duration, - /** - * Minimum duration this option covers, as an ISO 8601 duration (for example, `PT1H` or `P29D`). Omitted when there is no minimum. - */ - public string|null $min_duration, - /** - * iCalendar recurrence rule (RRULE) that the start date must fall on (for example, `FREQ=MONTHLY;BYDAY=1MO,3MO`). Constrains which calendar dates are selectable, independent of the time-of-day rules. - */ - public string|null $start_date_recurrence_rule, - /** - * Fixed start/end time pairings the caller chooses from. Mutually exclusive with `matching_start_end_time`. - */ - public array $time_pairs, - /** - * IANA time zone for interpreting `time_pairs` and the date recurrence rules. Present only when the option fixes times or dates. - */ - public string|null $time_zone, - ) {} -} + /** + * Indicates that the device has a flaky connection. + */ + final class DeviceHasFlakyConnection extends \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): DeviceHasFlakyConnection|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Array of thermostat daily program periods. - */ -class DevicePeriods -{ - public static function from_json(mixed $json): DevicePeriods|null - { - if (!$json) { - return null; - } - return new self( - climate_preset_key: $json->climate_preset_key ?? null, - starts_at_time: $json->starts_at_time ?? null, - ); - } - - public function __construct( - /** - * Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to activate at the `starts_at_time`. - */ - public string|null $climate_preset_key, - /** - * Time at which the thermostat daily program period starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at_time, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Predefined time slots for a dormakaba Oracode device. - */ -class DevicePredefinedTimeSlots -{ - public static function from_json( - mixed $json, - ): DevicePredefinedTimeSlots|null { - if (!$json) { - return null; - } - return new self( - check_in_time: $json->check_in_time ?? null, - check_out_time: $json->check_out_time ?? null, - dormakaba_oracode_user_level_id: $json->dormakaba_oracode_user_level_id ?? - null, - dormakaba_oracode_user_level_prefix: $json->dormakaba_oracode_user_level_prefix ?? - null, - is_24_hour: $json->is_24_hour ?? null, - is_biweekly_mode: $json->is_biweekly_mode ?? null, - is_master: $json->is_master ?? null, - is_one_shot: $json->is_one_shot ?? null, - name: $json->name ?? null, - prefix: $json->prefix ?? null, - ); - } - - public function __construct( - /** - * Check in time for a time slot for a dormakaba Oracode device. - */ - public string|null $check_in_time, - /** - * Checkout time for a time slot for a dormakaba Oracode device. - */ - public string|null $check_out_time, - /** - * ID of a user level for a dormakaba Oracode device. - */ - public string|null $dormakaba_oracode_user_level_id, - /** - * Prefix for a user level for a dormakaba Oracode device. - */ - public float|null $dormakaba_oracode_user_level_prefix, - /** - * Indicates whether a time slot for a dormakaba Oracode device is a 24-hour time slot. - */ - public bool|null $is_24_hour, - /** - * Indicates whether a time slot for a dormakaba Oracode device is in biweekly mode. - */ - public bool|null $is_biweekly_mode, - /** - * Indicates whether a time slot for a dormakaba Oracode device is a master time slot. - */ - public bool|null $is_master, - /** - * Indicates whether a time slot for a dormakaba Oracode device is a one-shot time slot. - */ - public bool|null $is_one_shot, - /** - * Name of a time slot for a dormakaba Oracode device. - */ - public string|null $name, - /** - * Prefix for a time slot for a dormakaba Oracode device. - */ - public float|null $prefix, - ) {} -} + /** + * Indicates that the Salto KS lock is in Office Mode. Access Codes will not unlock doors. + */ + final class SaltoKsOfficeMode extends \Seam\Resources\Device\Warnings + { + public static function from_json(mixed $json): SaltoKsOfficeMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Latest pressure reading for a Minut device. - */ -class DevicePressure -{ - public static function from_json(mixed $json): DevicePressure|null - { - if (!$json) { - return null; - } - return new self(time: $json->time ?? null, value: $json->value ?? null); - } - - public function __construct( - /** - * Time of latest pressure reading for a Minut device. - */ - public string|null $time, - /** - * Value of latest pressure reading for a Minut device. - */ - public float|null $value, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Properties of the device. - */ -class DeviceProperties -{ - public static function from_json(mixed $json): DeviceProperties|null - { - if (!$json) { - return null; - } - return new self( - accessory_keypad: isset($json->accessory_keypad) - ? DeviceAccessoryKeypad::from_json($json->accessory_keypad) - : null, - active_thermostat_schedule: isset($json->active_thermostat_schedule) - ? DeviceActiveThermostatSchedule::from_json( - $json->active_thermostat_schedule, - ) - : null, - active_thermostat_schedule_id: $json->active_thermostat_schedule_id ?? - null, - akiles_metadata: isset($json->akiles_metadata) - ? DeviceAkilesMetadata::from_json($json->akiles_metadata) - : null, - appearance: isset($json->appearance) - ? DeviceAppearance::from_json($json->appearance) - : null, - aqara_metadata: isset($json->aqara_metadata) - ? DeviceAqaraMetadata::from_json($json->aqara_metadata) - : null, - assa_abloy_credential_service_metadata: isset( - $json->assa_abloy_credential_service_metadata, - ) - ? DeviceAssaAbloyCredentialServiceMetadata::from_json( - $json->assa_abloy_credential_service_metadata, - ) - : null, - assa_abloy_vostio_metadata: isset($json->assa_abloy_vostio_metadata) - ? DeviceAssaAbloyVostioMetadata::from_json( - $json->assa_abloy_vostio_metadata, - ) - : null, - august_metadata: isset($json->august_metadata) - ? DeviceAugustMetadata::from_json($json->august_metadata) - : null, - auto_lock_delay_seconds: $json->auto_lock_delay_seconds ?? null, - auto_lock_enabled: $json->auto_lock_enabled ?? null, - available_climate_preset_modes: $json->available_climate_preset_modes ?? - null, - available_climate_presets: array_map( - fn($a) => DeviceAvailableClimatePresets::from_json($a), - $json->available_climate_presets ?? [], - ), - available_fan_mode_settings: $json->available_fan_mode_settings ?? - null, - available_hvac_mode_settings: $json->available_hvac_mode_settings ?? - null, - avigilon_alta_metadata: isset($json->avigilon_alta_metadata) - ? DeviceAvigilonAltaMetadata::from_json( - $json->avigilon_alta_metadata, - ) - : null, - backup_access_code_pool_enabled: $json->backup_access_code_pool_enabled ?? - null, - battery: isset($json->battery) - ? DeviceBattery::from_json($json->battery) - : null, - battery_level: $json->battery_level ?? null, - brivo_metadata: isset($json->brivo_metadata) - ? DeviceBrivoMetadata::from_json($json->brivo_metadata) - : null, - code_constraints: array_map( - fn($c) => DeviceCodeConstraints::from_json($c), - $json->code_constraints ?? [], - ), - controlbyweb_metadata: isset($json->controlbyweb_metadata) - ? DeviceControlbywebMetadata::from_json( - $json->controlbyweb_metadata, - ) - : null, - current_climate_setting: isset($json->current_climate_setting) - ? DeviceCurrentClimateSetting::from_json( - $json->current_climate_setting, - ) - : null, - currently_triggering_noise_threshold_ids: $json->currently_triggering_noise_threshold_ids ?? - null, - default_climate_setting: isset($json->default_climate_setting) - ? DeviceDefaultClimateSetting::from_json( - $json->default_climate_setting, - ) - : null, - door_open: $json->door_open ?? null, - dormakaba_oracode_metadata: isset($json->dormakaba_oracode_metadata) - ? DeviceDormakabaOracodeMetadata::from_json( - $json->dormakaba_oracode_metadata, - ) - : null, - ecobee_metadata: isset($json->ecobee_metadata) - ? DeviceEcobeeMetadata::from_json($json->ecobee_metadata) - : null, - fallback_climate_preset_key: $json->fallback_climate_preset_key ?? - null, - fan_mode_setting: $json->fan_mode_setting ?? null, - four_suites_metadata: isset($json->four_suites_metadata) - ? DeviceFourSuitesMetadata::from_json( - $json->four_suites_metadata, - ) - : null, - genie_metadata: isset($json->genie_metadata) - ? DeviceGenieMetadata::from_json($json->genie_metadata) - : null, - has_direct_power: $json->has_direct_power ?? null, - has_native_entry_events: $json->has_native_entry_events ?? null, - honeywell_resideo_metadata: isset($json->honeywell_resideo_metadata) - ? DeviceHoneywellResideoMetadata::from_json( - $json->honeywell_resideo_metadata, - ) - : null, - igloo_metadata: isset($json->igloo_metadata) - ? DeviceIglooMetadata::from_json($json->igloo_metadata) - : null, - igloohome_metadata: isset($json->igloohome_metadata) - ? DeviceIgloohomeMetadata::from_json($json->igloohome_metadata) - : null, - image_alt_text: $json->image_alt_text ?? null, - image_url: $json->image_url ?? null, - is_cooling: $json->is_cooling ?? null, - is_fan_running: $json->is_fan_running ?? null, - is_heating: $json->is_heating ?? null, - is_temporary_manual_override_active: $json->is_temporary_manual_override_active ?? - null, - keynest_metadata: isset($json->keynest_metadata) - ? DeviceKeynestMetadata::from_json($json->keynest_metadata) - : null, - keypad_battery: isset($json->keypad_battery) - ? DeviceKeypadBattery::from_json($json->keypad_battery) - : null, - kisi_metadata: isset($json->kisi_metadata) - ? DeviceKisiMetadata::from_json($json->kisi_metadata) - : null, - korelock_metadata: isset($json->korelock_metadata) - ? DeviceKorelockMetadata::from_json($json->korelock_metadata) - : null, - kwikset_metadata: isset($json->kwikset_metadata) - ? DeviceKwiksetMetadata::from_json($json->kwikset_metadata) - : null, - locked: $json->locked ?? null, - lockly_metadata: isset($json->lockly_metadata) - ? DeviceLocklyMetadata::from_json($json->lockly_metadata) - : null, - manufacturer: $json->manufacturer ?? null, - max_active_codes_supported: $json->max_active_codes_supported ?? - null, - max_cooling_set_point_celsius: $json->max_cooling_set_point_celsius ?? - null, - max_cooling_set_point_fahrenheit: $json->max_cooling_set_point_fahrenheit ?? - null, - max_heating_set_point_celsius: $json->max_heating_set_point_celsius ?? - null, - max_heating_set_point_fahrenheit: $json->max_heating_set_point_fahrenheit ?? - null, - max_thermostat_daily_program_periods_per_day: $json->max_thermostat_daily_program_periods_per_day ?? - null, - max_unique_climate_presets_per_thermostat_weekly_program: $json->max_unique_climate_presets_per_thermostat_weekly_program ?? - null, - min_cooling_set_point_celsius: $json->min_cooling_set_point_celsius ?? - null, - min_cooling_set_point_fahrenheit: $json->min_cooling_set_point_fahrenheit ?? - null, - min_heating_cooling_delta_celsius: $json->min_heating_cooling_delta_celsius ?? - null, - min_heating_cooling_delta_fahrenheit: $json->min_heating_cooling_delta_fahrenheit ?? - null, - min_heating_set_point_celsius: $json->min_heating_set_point_celsius ?? - null, - min_heating_set_point_fahrenheit: $json->min_heating_set_point_fahrenheit ?? - null, - minut_metadata: isset($json->minut_metadata) - ? DeviceMinutMetadata::from_json($json->minut_metadata) - : null, - model: isset($json->model) - ? DeviceModel::from_json($json->model) - : null, - name: $json->name ?? null, - nest_metadata: isset($json->nest_metadata) - ? DeviceNestMetadata::from_json($json->nest_metadata) - : null, - noise_level_decibels: $json->noise_level_decibels ?? null, - noiseaware_metadata: isset($json->noiseaware_metadata) - ? DeviceNoiseawareMetadata::from_json( - $json->noiseaware_metadata, - ) - : null, - nuki_metadata: isset($json->nuki_metadata) - ? DeviceNukiMetadata::from_json($json->nuki_metadata) - : null, - offline_access_codes_enabled: $json->offline_access_codes_enabled ?? - null, - offline_time_frame_options: array_map( - fn($o) => DeviceOfflineTimeFrameOptions::from_json($o), - $json->offline_time_frame_options ?? [], - ), - omnitec_metadata: isset($json->omnitec_metadata) - ? DeviceOmnitecMetadata::from_json($json->omnitec_metadata) - : null, - online: $json->online ?? null, - online_access_codes_enabled: $json->online_access_codes_enabled ?? - null, - online_time_frame_options: array_map( - fn($o) => DeviceOnlineTimeFrameOptions::from_json($o), - $json->online_time_frame_options ?? [], - ), - relative_humidity: $json->relative_humidity ?? null, - ring_metadata: isset($json->ring_metadata) - ? DeviceRingMetadata::from_json($json->ring_metadata) - : null, - salto_ks_metadata: isset($json->salto_ks_metadata) - ? DeviceSaltoKsMetadata::from_json($json->salto_ks_metadata) - : null, - salto_metadata: isset($json->salto_metadata) - ? DeviceSaltoMetadata::from_json($json->salto_metadata) - : null, - salto_space_credential_service_metadata: isset( - $json->salto_space_credential_service_metadata, - ) - ? DeviceSaltoSpaceCredentialServiceMetadata::from_json( - $json->salto_space_credential_service_metadata, - ) - : null, - schlage_metadata: isset($json->schlage_metadata) - ? DeviceSchlageMetadata::from_json($json->schlage_metadata) - : null, - seam_bridge_metadata: isset($json->seam_bridge_metadata) - ? DeviceSeamBridgeMetadata::from_json( - $json->seam_bridge_metadata, - ) - : null, - sensi_metadata: isset($json->sensi_metadata) - ? DeviceSensiMetadata::from_json($json->sensi_metadata) - : null, - serial_number: $json->serial_number ?? null, - smartthings_metadata: isset($json->smartthings_metadata) - ? DeviceSmartthingsMetadata::from_json( - $json->smartthings_metadata, - ) - : null, - supported_code_lengths: $json->supported_code_lengths ?? null, - supports_accessory_keypad: $json->supports_accessory_keypad ?? null, - supports_backup_access_code_pool: $json->supports_backup_access_code_pool ?? - null, - supports_offline_access_codes: $json->supports_offline_access_codes ?? - null, - tado_metadata: isset($json->tado_metadata) - ? DeviceTadoMetadata::from_json($json->tado_metadata) - : null, - tedee_metadata: isset($json->tedee_metadata) - ? DeviceTedeeMetadata::from_json($json->tedee_metadata) - : null, - temperature_celsius: $json->temperature_celsius ?? null, - temperature_fahrenheit: $json->temperature_fahrenheit ?? null, - temperature_threshold: isset($json->temperature_threshold) - ? DeviceTemperatureThreshold::from_json( - $json->temperature_threshold, - ) - : null, - thermostat_daily_program_period_precision_minutes: $json->thermostat_daily_program_period_precision_minutes ?? - null, - thermostat_daily_programs: array_map( - fn($t) => DeviceThermostatDailyPrograms::from_json($t), - $json->thermostat_daily_programs ?? [], - ), - thermostat_weekly_program: isset($json->thermostat_weekly_program) - ? DeviceThermostatWeeklyProgram::from_json( - $json->thermostat_weekly_program, - ) - : null, - ttlock_metadata: isset($json->ttlock_metadata) - ? DeviceTtlockMetadata::from_json($json->ttlock_metadata) - : null, - two_n_metadata: isset($json->two_n_metadata) - ? DeviceTwoNMetadata::from_json($json->two_n_metadata) - : null, - ultraloq_metadata: isset($json->ultraloq_metadata) - ? DeviceUltraloqMetadata::from_json($json->ultraloq_metadata) - : null, - visionline_metadata: isset($json->visionline_metadata) - ? DeviceVisionlineMetadata::from_json( - $json->visionline_metadata, - ) - : null, - wyze_metadata: isset($json->wyze_metadata) - ? DeviceWyzeMetadata::from_json($json->wyze_metadata) - : null, - ); - } - - public function __construct( - /** - * Accessory keypad properties and state. - */ - public DeviceAccessoryKeypad|null $accessory_keypad, - /** - * Active [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - * - * @deprecated Use `active_thermostat_schedule_id` with `/thermostats/schedules/get` instead. - */ - public DeviceActiveThermostatSchedule|null $active_thermostat_schedule, - /** - * ID of the active [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public string|null $active_thermostat_schedule_id, - /** - * Metadata for an Akiles device. - */ - public DeviceAkilesMetadata|null $akiles_metadata, - /** - * Appearance-related properties, as reported by the device. - */ - public DeviceAppearance|null $appearance, - /** - * Metadata for an Aqara device. - */ - public DeviceAqaraMetadata|null $aqara_metadata, - /** - * ASSA ABLOY Credential Service metadata for the phone. - */ - public DeviceAssaAbloyCredentialServiceMetadata|null $assa_abloy_credential_service_metadata, - /** - * Metadata for an ASSA ABLOY Vostio system. - */ - public DeviceAssaAbloyVostioMetadata|null $assa_abloy_vostio_metadata, - /** - * Metadata for an August device. - */ - public DeviceAugustMetadata|null $august_metadata, - /** - * The delay in seconds before the lock automatically locks after being unlocked. - */ - public float|null $auto_lock_delay_seconds, - /** - * Indicates whether automatic locking is enabled. - */ - public bool|null $auto_lock_enabled, - /** - * Climate preset modes that the thermostat supports, such as "home", "away", "wake", "sleep", "occupied", and "unoccupied". - */ - public array|null $available_climate_preset_modes, - /** - * Available [climate presets](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) for the thermostat. - */ - public array $available_climate_presets, - /** - * Fan mode settings that the thermostat supports. - */ - public array|null $available_fan_mode_settings, - /** - * HVAC mode settings that the thermostat supports. - */ - public array|null $available_hvac_mode_settings, - /** - * Metadata for an Avigilon Alta system. - */ - public DeviceAvigilonAltaMetadata|null $avigilon_alta_metadata, - /** - * Indicates whether the [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) is currently enabled for the device. To disable it, set this to `false` using [/devices/update](https://docs.seam.co/api/devices/update). - */ - public bool|null $backup_access_code_pool_enabled, - /** - * Represents the current status of the battery charge level. - */ - public DeviceBattery|null $battery, - /** - * Indicates the battery level of the device as a decimal value between 0 and 1, inclusive. - */ - public float|null $battery_level, - /** - * Metadata for a Brivo device. - */ - public DeviceBrivoMetadata|null $brivo_metadata, - /** - * Constraints on access codes for the device. Seam represents each constraint as an object with a `constraint_type` property. Depending on the constraint type, there may also be additional properties. Note that some constraints are manufacturer- or device-specific. - */ - public array $code_constraints, - /** - * Metadata for a ControlByWeb device. - */ - public DeviceControlbywebMetadata|null $controlbyweb_metadata, - /** - * Current climate setting. - */ - public DeviceCurrentClimateSetting|null $current_climate_setting, - /** - * Array of noise threshold IDs that are currently triggering. - */ - public array|null $currently_triggering_noise_threshold_ids, - /** - * @deprecated use fallback_climate_preset_key to specify a fallback climate preset instead. - */ - public DeviceDefaultClimateSetting|null $default_climate_setting, - /** - * Indicates whether the door is open. - */ - public bool|null $door_open, - /** - * Metadata for a dormakaba Oracode device. - */ - public DeviceDormakabaOracodeMetadata|null $dormakaba_oracode_metadata, - /** - * Metadata for an ecobee device. - */ - public DeviceEcobeeMetadata|null $ecobee_metadata, - /** - * Key of the [fallback climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets/setting-the-fallback-climate-preset) for the thermostat. - */ - public string|null $fallback_climate_preset_key, - /** - * @deprecated Use `current_climate_setting.fan_mode_setting` instead. - */ - public string|null $fan_mode_setting, - /** - * Metadata for a 4SUITES device. - */ - public DeviceFourSuitesMetadata|null $four_suites_metadata, - /** - * Metadata for a Genie device. - */ - public DeviceGenieMetadata|null $genie_metadata, - /** - * Indicates whether the device has direct power. - */ - public bool|null $has_direct_power, - /** - * Indicates whether the device supports native entry events. - */ - public bool|null $has_native_entry_events, - /** - * Metadata for a Honeywell Resideo device. - */ - public DeviceHoneywellResideoMetadata|null $honeywell_resideo_metadata, - /** - * Metadata for an igloo device. - */ - public DeviceIglooMetadata|null $igloo_metadata, - /** - * Metadata for an igloohome device. - */ - public DeviceIgloohomeMetadata|null $igloohome_metadata, - /** - * Alt text for the device image. - */ - public string|null $image_alt_text, - /** - * Image URL for the device. - */ - public string|null $image_url, - /** - * Indicates whether the connected HVAC system is currently cooling, as reported by the thermostat. - */ - public bool|null $is_cooling, - /** - * Indicates whether the fan in the connected HVAC system is currently running, as reported by the thermostat. - */ - public bool|null $is_fan_running, - /** - * Indicates whether the connected HVAC system is currently heating, as reported by the thermostat. - */ - public bool|null $is_heating, - /** - * Indicates whether the current thermostat settings differ from the most recent active program or schedule that Seam activated. For this condition to occur, `current_climate_setting.manual_override_allowed` must also be `true`. - */ - public bool|null $is_temporary_manual_override_active, - /** - * Metadata for a KeyNest device. - */ - public DeviceKeynestMetadata|null $keynest_metadata, - /** - * Keypad battery status. - */ - public DeviceKeypadBattery|null $keypad_battery, - /** - * Metadata for a Kisi device. - */ - public DeviceKisiMetadata|null $kisi_metadata, - /** - * Metadata for a Korelock device. - */ - public DeviceKorelockMetadata|null $korelock_metadata, - /** - * Metadata for a Kwikset device. - */ - public DeviceKwiksetMetadata|null $kwikset_metadata, - /** - * Indicates whether the lock is locked. - */ - public bool|null $locked, - /** - * Metadata for a Lockly device. - */ - public DeviceLocklyMetadata|null $lockly_metadata, - /** - * Manufacturer of the device. When a device, such as a smart lock, is connected through a smart hub, the manufacturer of the device might be different from that of the smart hub. - */ - public string|null $manufacturer, - /** - * Maximum number of active access codes that the device supports. - */ - public float|null $max_active_codes_supported, - /** - * Maximum [cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#cooling-set-point) in °C. - */ - public float|null $max_cooling_set_point_celsius, - /** - * Maximum [cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#cooling-set-point) in °F. - */ - public float|null $max_cooling_set_point_fahrenheit, - /** - * Maximum [heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#heating-set-point) in °C. - */ - public float|null $max_heating_set_point_celsius, - /** - * Maximum [heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#heating-set-point) in °F. - */ - public float|null $max_heating_set_point_fahrenheit, - /** - * Maximum number of periods that the thermostat can support per day. For example, if the thermostat supports 4 periods per day, this value is 4. - */ - public float|null $max_thermostat_daily_program_periods_per_day, - /** - * Maximum number of climate presets that the thermostat can support for weekly programming. - */ - public float|null $max_unique_climate_presets_per_thermostat_weekly_program, - /** - * Minimum [cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#cooling-set-point) in °C. - */ - public float|null $min_cooling_set_point_celsius, - /** - * Minimum [cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#cooling-set-point) in °F. - */ - public float|null $min_cooling_set_point_fahrenheit, - /** - * Minimum [temperature difference](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#minimum-heating-cooling-temperature-delta) in °C between the cooling and heating set points when in heat-cool (auto) mode. - */ - public float|null $min_heating_cooling_delta_celsius, - /** - * Minimum [temperature difference](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#minimum-heating-cooling-temperature-delta) in °F between the cooling and heating set points when in heat-cool (auto) mode. - */ - public float|null $min_heating_cooling_delta_fahrenheit, - /** - * Minimum [heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#heating-set-point) in °C. - */ - public float|null $min_heating_set_point_celsius, - /** - * Minimum [heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points#heating-set-point) in °F. - */ - public float|null $min_heating_set_point_fahrenheit, - /** - * Metadata for a Minut device. - */ - public DeviceMinutMetadata|null $minut_metadata, - /** - * Device model-related properties. - */ - public DeviceModel|null $model, - /** - * Name of the device. - * - * @deprecated use device.display_name instead - */ - public string|null $name, - /** - * Metadata for a Google Nest device. - */ - public DeviceNestMetadata|null $nest_metadata, - /** - * Indicates current noise level in decibels, if the device supports noise detection. - */ - public float|null $noise_level_decibels, - /** - * Metadata for a NoiseAware device. - */ - public DeviceNoiseawareMetadata|null $noiseaware_metadata, - /** - * Metadata for a Nuki device. - */ - public DeviceNukiMetadata|null $nuki_metadata, - /** - * Indicates whether it is currently possible to use offline access codes for the device. - * - * @deprecated use device.can_program_offline_access_codes - */ - public bool|null $offline_access_codes_enabled, - /** - * Time frames that may be requested when creating an offline access code, expressed as a list of options. The caller picks one option (by matching the requested duration when the options' duration ranges do not overlap, or by `display_name` when they do) and satisfies that one option's rules. When `undefined`, any time frame works. - */ - public array $offline_time_frame_options, - /** - * Metadata for an Omnitec device. - */ - public DeviceOmnitecMetadata|null $omnitec_metadata, - /** - * Indicates whether the device is online. - */ - public bool|null $online, - /** - * Indicates whether it is currently possible to use online access codes for the device. - * - * @deprecated use device.can_program_online_access_codes - */ - public bool|null $online_access_codes_enabled, - /** - * Time frames that may be requested when creating an online access code, expressed as a list of options. The caller picks one option (by matching the requested duration when the options' duration ranges do not overlap, or by `display_name` when they do) and satisfies that one option's rules. When `undefined`, any time frame works. - */ - public array $online_time_frame_options, - /** - * Reported relative humidity, as a value between 0 and 1, inclusive. - */ - public float|null $relative_humidity, - /** - * Metadata for a Ring device. - */ - public DeviceRingMetadata|null $ring_metadata, - /** - * Metadata for a Salto KS device. - */ - public DeviceSaltoKsMetadata|null $salto_ks_metadata, - /** - * Metada for a Salto device. - * - * @deprecated Use `salto_ks_metadata ` instead. - */ - public DeviceSaltoMetadata|null $salto_metadata, - /** - * Salto Space credential service metadata for the phone. - */ - public DeviceSaltoSpaceCredentialServiceMetadata|null $salto_space_credential_service_metadata, - /** - * Metadata for a Schlage device. - */ - public DeviceSchlageMetadata|null $schlage_metadata, - /** - * Metadata for Seam Bridge. - */ - public DeviceSeamBridgeMetadata|null $seam_bridge_metadata, - /** - * Metadata for a Sensi device. - */ - public DeviceSensiMetadata|null $sensi_metadata, - /** - * Serial number of the device. - */ - public string|null $serial_number, - /** - * Metadata for a SmartThings device. - */ - public DeviceSmartthingsMetadata|null $smartthings_metadata, - /** - * Supported code lengths for access codes. - */ - public array|null $supported_code_lengths, - /** - * @deprecated use device.properties.model.can_connect_accessory_keypad - */ - public bool|null $supports_accessory_keypad, - /** - * Indicates whether the device supports a [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes). - */ - public bool|null $supports_backup_access_code_pool, - /** - * @deprecated use offline_access_codes_enabled - */ - public bool|null $supports_offline_access_codes, - /** - * Metadata for a tado° device. - */ - public DeviceTadoMetadata|null $tado_metadata, - /** - * Metadata for a Tedee device. - */ - public DeviceTedeeMetadata|null $tedee_metadata, - /** - * Reported temperature in °C. - */ - public float|null $temperature_celsius, - /** - * Reported temperature in °F. - */ - public float|null $temperature_fahrenheit, - /** - * Current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. - */ - public DeviceTemperatureThreshold|null $temperature_threshold, - /** - * Precision of the thermostat's period in minutes. For example, if the thermostat supports 15-minute periods, this value is 15. All values are relative to the top of the hour, so for 15 minutes, the periods would be 0, 15, 30, and 45 minutes past the hour. - */ - public float|null $thermostat_daily_program_period_precision_minutes, - /** - * Configured [daily programs](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-programs) for the thermostat. - */ - public array $thermostat_daily_programs, - /** - * Current [weekly program](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-programs) for the thermostat. - */ - public DeviceThermostatWeeklyProgram|null $thermostat_weekly_program, - /** - * Metadata for a TTLock device. - */ - public DeviceTtlockMetadata|null $ttlock_metadata, - /** - * Metadata for a 2N device. - */ - public DeviceTwoNMetadata|null $two_n_metadata, - /** - * Metadata for an Ultraloq device. - */ - public DeviceUltraloqMetadata|null $ultraloq_metadata, - /** - * Metadata for an ASSA ABLOY Visionline system. - */ - public DeviceVisionlineMetadata|null $visionline_metadata, - /** - * Metadata for a Wyze device. - */ - public DeviceWyzeMetadata|null $wyze_metadata, - ) {} -} + /** + * Indicates that the Salto KS lock is in Privacy Mode. Access Codes will not unlock doors. + */ + final class SaltoKsPrivacyMode extends \Seam\Resources\Device\Warnings + { + public static function from_json(mixed $json): SaltoKsPrivacyMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Ring device. - */ -class DeviceRingMetadata -{ - public static function from_json(mixed $json): DeviceRingMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - ); - } - - public function __construct( - /** - * Device ID for a Ring device. - */ - public string|null $device_id, - /** - * Device name for a Ring device. - */ - public string|null $device_name, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for a Salto KS device. - */ -class DeviceSaltoKsMetadata -{ - public static function from_json(mixed $json): DeviceSaltoKsMetadata|null - { - if (!$json) { - return null; - } - return new self( - battery_level: $json->battery_level ?? null, - customer_reference: $json->customer_reference ?? null, - has_custom_pin_subscription: $json->has_custom_pin_subscription ?? - null, - lock_id: $json->lock_id ?? null, - lock_type: $json->lock_type ?? null, - locked_state: $json->locked_state ?? null, - model: $json->model ?? null, - site_id: $json->site_id ?? null, - site_name: $json->site_name ?? null, - ); - } - - public function __construct( - /** - * Battery level for a Salto KS device. - */ - public string|null $battery_level, - /** - * Customer reference for a Salto KS device. - */ - public string|null $customer_reference, - /** - * Indicates whether the site has a Salto KS subscription that supports custom PINs. - */ - public bool|null $has_custom_pin_subscription, - /** - * Lock ID for a Salto KS device. - */ - public string|null $lock_id, - /** - * Lock type for a Salto KS device. - */ - public string|null $lock_type, - /** - * Locked state for a Salto KS device. - */ - public string|null $locked_state, - /** - * Model for a Salto KS device. - */ - public string|null $model, - /** - * Site ID for the Salto KS site to which the device belongs. - */ - public string|null $site_id, - /** - * Site name for the Salto KS site to which the device belongs. - */ - public string|null $site_name, - ) {} -} + /** + * Indicates that the lock is in Privacy Mode. Access codes and remote unlock are blocked until Privacy Mode is disabled. + */ + final class PrivacyMode extends \Seam\Resources\Device\Warnings + { + public static function from_json(mixed $json): PrivacyMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metada for a Salto device. - */ -class DeviceSaltoMetadata -{ - public static function from_json(mixed $json): DeviceSaltoMetadata|null - { - if (!$json) { - return null; - } - return new self( - battery_level: $json->battery_level ?? null, - customer_reference: $json->customer_reference ?? null, - lock_id: $json->lock_id ?? null, - lock_type: $json->lock_type ?? null, - locked_state: $json->locked_state ?? null, - model: $json->model ?? null, - site_id: $json->site_id ?? null, - site_name: $json->site_name ?? null, - ); - } - - public function __construct( - /** - * Battery level for a Salto device. - */ - public string|null $battery_level, - /** - * Customer reference for a Salto device. - */ - public string|null $customer_reference, - /** - * Lock ID for a Salto device. - */ - public string|null $lock_id, - /** - * Lock type for a Salto device. - */ - public string|null $lock_type, - /** - * Locked state for a Salto device. - */ - public string|null $locked_state, - /** - * Model for a Salto device. - */ - public string|null $model, - /** - * Site ID for the Salto KS site to which the device belongs. - */ - public string|null $site_id, - /** - * Site name for the Salto KS site to which the device belongs. - */ - public string|null $site_name, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Salto Space credential service metadata for the phone. - */ -class DeviceSaltoSpaceCredentialServiceMetadata -{ - public static function from_json( - mixed $json, - ): DeviceSaltoSpaceCredentialServiceMetadata|null { - if (!$json) { - return null; - } - return new self(has_active_phone: $json->has_active_phone ?? null); - } - - public function __construct( - /** - * Indicates whether the credential service has an active associated phone. - */ - public bool|null $has_active_phone, - ) {} -} + /** + * Indicates that the Salto KS site has exceeded 80% of the maximum number of allowed users. Increase your subscription limit or delete some users from your site. + */ + final class SaltoKsSubscriptionLimitAlmostReached extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitAlmostReached|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Schlage device. - */ -class DeviceSchlageMetadata -{ - public static function from_json(mixed $json): DeviceSchlageMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - model: $json->model ?? null, - ); - } - - public function __construct( - /** - * Device ID for a Schlage device. - */ - public string|null $device_id, - /** - * Device name for a Schlage device. - */ - public string|null $device_name, - /** - * Model for a Schlage device. - */ - public string|null $model, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for Seam Bridge. - */ -class DeviceSeamBridgeMetadata -{ - public static function from_json(mixed $json): DeviceSeamBridgeMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_num: $json->device_num ?? null, - name: $json->name ?? null, - unlock_method: $json->unlock_method ?? null, - ); - } - - public function __construct( - /** - * Device number for Seam Bridge. - */ - public float|null $device_num, - /** - * Name for Seam Bridge. - */ - public string|null $name, - /** - * Unlock method for Seam Bridge. - */ - public string|null $unlock_method, - ) {} -} + /** + * Indicates that a change in the reported device model has been detected for this Salto KS lock, which may occur after an IQ hub reset. Access code support may be affected. See https://help.getseam.com/articles/5098842588-salto-ks-lock-loses-access-code-support for troubleshooting steps. + */ + final class SaltoKsLockAccessCodeSupportRemoved extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): SaltoKsLockAccessCodeSupportRemoved|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Sensi device. - */ -class DeviceSensiMetadata -{ - public static function from_json(mixed $json): DeviceSensiMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - dual_setpoints_not_supported: $json->dual_setpoints_not_supported ?? - null, - enforced_setpoint_range_celsius: $json->enforced_setpoint_range_celsius ?? - null, - product_type: $json->product_type ?? null, - ); - } - - public function __construct( - /** - * Device ID for a Sensi device. - */ - public string|null $device_id, - /** - * Device name for a Sensi device. - */ - public string|null $device_name, - /** - * Set to true when the device does not support the /dual-setpoints API endpoint. - */ - public bool|null $dual_setpoints_not_supported, - /** - * Enforced setpoint range in Celsius for a Sensi device, derived from an OutOfRange API error. - */ - public array|null $enforced_setpoint_range_celsius, - /** - * Product type for a Sensi device. - */ - public string|null $product_type, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for a SmartThings device. - */ -class DeviceSmartthingsMetadata -{ - public static function from_json( - mixed $json, - ): DeviceSmartthingsMetadata|null { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - location_id: $json->location_id ?? null, - model: $json->model ?? null, - ); - } - - public function __construct( - /** - * Device ID for a SmartThings device. - */ - public string|null $device_id, - /** - * Device name for a SmartThings device. - */ - public string|null $device_name, - /** - * Location ID for a SmartThings device. - */ - public string|null $location_id, - /** - * Model for a SmartThings device. - */ - public string|null $model, - ) {} -} + /** + * Indicates that an unknown issue occurred while syncing the state of the phone with the provider. This issue may affect the proper functioning of the phone. + */ + final class UnknownIssueWithPhone extends \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): UnknownIssueWithPhone|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Latest sound reading for a Minut device. - */ -class DeviceSound -{ - public static function from_json(mixed $json): DeviceSound|null - { - if (!$json) { - return null; - } - return new self(time: $json->time ?? null, value: $json->value ?? null); - } - - public function __construct( - /** - * Time of latest sound reading for a Minut device. - */ - public string|null $time, - /** - * Value of latest sound reading for a Minut device. - */ - public float|null $value, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for a tado° device. - */ -class DeviceTadoMetadata -{ - public static function from_json(mixed $json): DeviceTadoMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_type: $json->device_type ?? null, - serial_no: $json->serial_no ?? null, - ); - } - - public function __construct( - /** - * Device type for a tado° device. - */ - public string|null $device_type, - /** - * Serial number for a tado° device. - */ - public string|null $serial_no, - ) {} -} + /** + * Indicates that Seam detected that the Lockly device does not have a time zone configured. Time-bound codes may not work as expected. + */ + final class LocklyTimeZoneNotConfigured extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): LocklyTimeZoneNotConfigured|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a Tedee device. - */ -class DeviceTedeeMetadata -{ - public static function from_json(mixed $json): DeviceTedeeMetadata|null - { - if (!$json) { - return null; - } - return new self( - bridge_id: $json->bridge_id ?? null, - bridge_name: $json->bridge_name ?? null, - device_id: $json->device_id ?? null, - device_model: $json->device_model ?? null, - device_name: $json->device_name ?? null, - keypad_id: $json->keypad_id ?? null, - serial_number: $json->serial_number ?? null, - ); - } - - public function __construct( - /** - * Bridge ID for a Tedee device. - */ - public float|null $bridge_id, - /** - * Bridge name for a Tedee device. - */ - public string|null $bridge_name, - /** - * Device ID for a Tedee device. - */ - public float|null $device_id, - /** - * Device model for a Tedee device. - */ - public string|null $device_model, - /** - * Device name for a Tedee device. - */ - public string|null $device_name, - /** - * Keypad ID for a Tedee device. - */ - public float|null $keypad_id, - /** - * Serial number for a Tedee device. - */ - public string|null $serial_number, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Latest temperature reading for a Minut device. - */ -class DeviceTemperature -{ - public static function from_json(mixed $json): DeviceTemperature|null - { - if (!$json) { - return null; - } - return new self(time: $json->time ?? null, value: $json->value ?? null); - } - - public function __construct( - /** - * Time of latest temperature reading for a Minut device. - */ - public string|null $time, - /** - * Value of latest temperature reading for a Minut device. - */ - public float|null $value, - ) {} -} + /** + * Indicates that Seam does not know the time zone of the Ultraloq device. Set a time zone to enable time-bound access codes. + */ + final class UltraloqTimeZoneUnknown extends \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): UltraloqTimeZoneUnknown|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. - */ -class DeviceTemperatureThreshold -{ - public static function from_json( - mixed $json, - ): DeviceTemperatureThreshold|null { - if (!$json) { - return null; - } - return new self( - lower_limit_celsius: $json->lower_limit_celsius ?? null, - lower_limit_fahrenheit: $json->lower_limit_fahrenheit ?? null, - upper_limit_celsius: $json->upper_limit_celsius ?? null, - upper_limit_fahrenheit: $json->upper_limit_fahrenheit ?? null, - ); - } - - public function __construct( - /** - * Lower limit in °C within the current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. - */ - public float|null $lower_limit_celsius, - /** - * Lower limit in °F within the current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. - */ - public float|null $lower_limit_fahrenheit, - /** - * Upper limit in °C within the current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. - */ - public float|null $upper_limit_celsius, - /** - * Upper limit in °F within the current [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) set for the thermostat. - */ - public float|null $upper_limit_fahrenheit, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Configured [daily programs](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-programs) for the thermostat. - */ -class DeviceThermostatDailyPrograms -{ - public static function from_json( - mixed $json, - ): DeviceThermostatDailyPrograms|null { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - device_id: $json->device_id ?? null, - name: $json->name ?? null, - periods: array_map( - fn($p) => DevicePeriods::from_json($p), - $json->periods ?? [], - ), - thermostat_daily_program_id: $json->thermostat_daily_program_id ?? - null, - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Date and time at which the thermostat daily program was created. - */ - public string|null $created_at, - /** - * ID of the thermostat device on which the thermostat daily program is configured. - */ - public string|null $device_id, - /** - * User-friendly name to identify the thermostat daily program. - */ - public string|null $name, - /** - * Array of thermostat daily program periods. - */ - public array $periods, - /** - * ID of the thermostat daily program. - */ - public string|null $thermostat_daily_program_id, - /** - * ID of the workspace that contains the thermostat daily program. - */ - public string|null $workspace_id, - ) {} -} + /** + * Indicates that Seam does not know the device's time zone. Set a time zone to enable time-bound access codes. + */ + final class TimeZoneUnknown extends \Seam\Resources\Device\Warnings + { + public static function from_json(mixed $json): TimeZoneUnknown|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Current [weekly program](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-programs) for the thermostat. - */ -class DeviceThermostatWeeklyProgram -{ - public static function from_json( - mixed $json, - ): DeviceThermostatWeeklyProgram|null { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - friday_program_id: $json->friday_program_id ?? null, - monday_program_id: $json->monday_program_id ?? null, - saturday_program_id: $json->saturday_program_id ?? null, - sunday_program_id: $json->sunday_program_id ?? null, - thursday_program_id: $json->thursday_program_id ?? null, - tuesday_program_id: $json->tuesday_program_id ?? null, - wednesday_program_id: $json->wednesday_program_id ?? null, - ); - } - - public function __construct( - /** - * Date and time at which the thermostat weekly program was created. - */ - public string|null $created_at, - /** - * ID of the thermostat daily program to run on Fridays. - */ - public string|null $friday_program_id, - /** - * ID of the thermostat daily program to run on Mondays. - */ - public string|null $monday_program_id, - /** - * ID of the thermostat daily program to run on Saturdays. - */ - public string|null $saturday_program_id, - /** - * ID of the thermostat daily program to run on Sundays. - */ - public string|null $sunday_program_id, - /** - * ID of the thermostat daily program to run on Thursdays. - */ - public string|null $thursday_program_id, - /** - * ID of the thermostat daily program to run on Tuesdays. - */ - public string|null $tuesday_program_id, - /** - * ID of the thermostat daily program to run on Wednesdays. - */ - public string|null $wednesday_program_id, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Fixed start/end time pairings the caller chooses from. Mutually exclusive with `matching_start_end_time`. - */ -class DeviceTimePairs -{ - public static function from_json(mixed $json): DeviceTimePairs|null - { - if (!$json) { - return null; - } - return new self( - display_name: $json->display_name ?? null, - end_time: $json->end_time ?? null, - start_time: $json->start_time ?? null, - ); - } - - public function __construct( - /** - * Label for the start/end time pairing. - */ - public string|null $display_name, - /** - * End time of day as a 24-hour `HH:MM` value, interpreted in the option's `time_zone`. An `end_time` earlier on the clock than `start_time` means the end falls on a later date. - */ - public string|null $end_time, - /** - * Start time of day as a 24-hour `HH:MM` value, interpreted in the option's `time_zone`. - */ - public string|null $start_time, - ) {} -} + /** + * Indicates that the device's configured time zone does not match its hardware UTC offset. Time-bound access codes may activate at the wrong local time. + */ + final class TimeZoneMismatch extends \Seam\Resources\Device\Warnings + { + public static function from_json(mixed $json): TimeZoneMismatch|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for a TTLock device. - */ -class DeviceTtlockMetadata -{ - public static function from_json(mixed $json): DeviceTtlockMetadata|null - { - if (!$json) { - return null; - } - return new self( - feature_value: $json->feature_value ?? null, - features: isset($json->features) - ? DeviceFeatures::from_json($json->features) - : null, - has_gateway: $json->has_gateway ?? null, - lock_alias: $json->lock_alias ?? null, - lock_id: $json->lock_id ?? null, - timezone_raw_offset_ms: $json->timezone_raw_offset_ms ?? null, - wireless_keypads: array_map( - fn($w) => DeviceWirelessKeypads::from_json($w), - $json->wireless_keypads ?? [], - ), - ); - } - - public function __construct( - /** - * Feature value for a TTLock device. - */ - public string|null $feature_value, - /** - * Features for a TTLock device. - */ - public DeviceFeatures|null $features, - /** - * Indicates whether a TTLock device has a gateway. - */ - public bool|null $has_gateway, - /** - * Lock alias for a TTLock device. - */ - public string|null $lock_alias, - /** - * Lock ID for a TTLock device. - */ - public float|null $lock_id, - /** - * Lock-side timezone offset in milliseconds east of UTC, as configured in the TTLock app. Source of truth for the lock's wall-clock interpretation of access code start/end times — a misconfigured value here is the typical cause of customer "codes offset by N hours" reports. Diagnostic only; Seam does not convert times based on this value. - */ - public float|null $timezone_raw_offset_ms, - /** - * Wireless keypads for a TTLock device. - */ - public array $wireless_keypads, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for a 2N device. - */ -class DeviceTwoNMetadata -{ - public static function from_json(mixed $json): DeviceTwoNMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - ); - } - - public function __construct( - /** - * Device ID for a 2N device. - */ - public float|null $device_id, - /** - * Device name for a 2N device. - */ - public string|null $device_name, - ) {} -} + /** + * Indicates that the 2N device does not have a time zone configured. Configure a time zone on the device to enable access codes. + */ + final class TwoNDeviceMissingTimezone extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): TwoNDeviceMissingTimezone|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Metadata for an Ultraloq device. - */ -class DeviceUltraloqMetadata -{ - public static function from_json(mixed $json): DeviceUltraloqMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_name: $json->device_name ?? null, - device_type: $json->device_type ?? null, - time_zone: $json->time_zone ?? null, - ); - } - - public function __construct( - /** - * Device ID for an Ultraloq device. - */ - public string|null $device_id, - /** - * Device name for an Ultraloq device. - */ - public string|null $device_name, - /** - * Device type for an Ultraloq device. - */ - public string|null $device_type, - /** - * IANA timezone for the Ultraloq device. - */ - public string|null $time_zone, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a hub or relay must be connected to unlock additional capabilities such as remote unlock. + */ + final class HubRequiredForAdditionalCapabilities extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): HubRequiredForAdditionalCapabilities|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for an ASSA ABLOY Visionline system. - */ -class DeviceVisionlineMetadata -{ - public static function from_json(mixed $json): DeviceVisionlineMetadata|null + /** + * Indicates a provider-specific issue that may affect device functionality. + */ + final class ProviderIssue extends \Seam\Resources\Device\Warnings { - if (!$json) { - return null; + public static function from_json(mixed $json): ProviderIssue|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self(encoder_id: $json->encoder_id ?? null); } - public function __construct( - /** - * Encoder ID for an ASSA ABLOY Visionline system. - */ - public string|null $encoder_id, - ) {} -} + /** + * Indicates that the key is in a locker that does not support the access codes API. + */ + final class KeynestUnsupportedLocker extends \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): KeynestUnsupportedLocker|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } -/** - * Array of warnings associated with the device. Each warning object within the array contains two fields: `warning_code` and `message`. `warning_code` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - */ -class DeviceWarnings -{ - public static function from_json(mixed $json): DeviceWarnings|null - { - if (!$json) { - return null; - } - return new self( - active_access_code_count: $json->active_access_code_count ?? null, - created_at: $json->created_at ?? null, - max_active_access_code_count: $json->max_active_access_code_count ?? - null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Number of active access codes on the device when the warning was set. - */ - public int|null $active_access_code_count, - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Maximum number of active access codes supported by the device. - */ - public int|null $max_active_access_code_count, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Wireless keypads for a TTLock device. - */ -class DeviceWirelessKeypads -{ - public static function from_json(mixed $json): DeviceWirelessKeypads|null - { - if (!$json) { - return null; - } - return new self( - wireless_keypad_id: $json->wireless_keypad_id ?? null, - wireless_keypad_name: $json->wireless_keypad_name ?? null, - ); - } - - public function __construct( - /** - * ID for a wireless keypad for a TTLock device. - */ - public float|null $wireless_keypad_id, - /** - * Name for a wireless keypad for a TTLock device. - */ - public string|null $wireless_keypad_name, - ) {} -} + /** + * Indicates that the accessory keypad exists, but is not linked to the Igloohome Bridge. Online access code programming will fail until the keypad is linked to the Igloohome Bridge in the Igloohome app. + */ + final class AccessoryKeypadSetupRequired extends + \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): AccessoryKeypadSetupRequired|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the device may optimistically be reported as online because the provider does not reliably report its online status. + */ + final class UnreliableOnlineStatus extends \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): UnreliableOnlineStatus|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } -/** - * Metadata for a Wyze device. - */ -class DeviceWyzeMetadata -{ - public static function from_json(mixed $json): DeviceWyzeMetadata|null - { - if (!$json) { - return null; - } - return new self( - device_id: $json->device_id ?? null, - device_info_model: $json->device_info_model ?? null, - device_name: $json->device_name ?? null, - keypad_uuid: $json->keypad_uuid ?? null, - locker_status_hardlock: $json->locker_status_hardlock ?? null, - product_model: $json->product_model ?? null, - product_name: $json->product_name ?? null, - product_type: $json->product_type ?? null, - ); - } - - public function __construct( - /** - * Device ID for a Wyze device. - */ - public string|null $device_id, - /** - * Device information model for a Wyze device. - */ - public string|null $device_info_model, - /** - * Device name for a Wyze device. - */ - public string|null $device_name, - /** - * Keypad UUID for a Wyze device. - */ - public string|null $keypad_uuid, - /** - * Locker status (hardlock) for a Wyze device. - */ - public float|null $locker_status_hardlock, - /** - * Product model for a Wyze device. - */ - public string|null $product_model, - /** - * Product name for a Wyze device. - */ - public string|null $product_name, - /** - * Product type for a Wyze device. - */ - public string|null $product_type, - ) {} + /** + * Indicates that the device has reached its maximum number of active access codes. Delete existing codes before creating new ones. + */ + final class MaxAccessCodesReached extends \Seam\Resources\Device\Warnings + { + public static function from_json( + mixed $json, + ): MaxAccessCodesReached|null { + if (!$json) { + return null; + } + return new self( + active_access_code_count: $json->active_access_code_count ?? + null, + created_at: $json->created_at ?? null, + max_active_access_code_count: $json->max_active_access_code_count ?? + null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Number of active access codes on the device when the warning was set. + */ + public int|null $active_access_code_count, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Maximum number of active access codes supported by the device. + */ + public int|null $max_active_access_code_count, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\Device\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case PARTIAL_BACKUP_ACCESS_CODE_POOL = "partial_backup_access_code_pool"; + case MANY_ACTIVE_BACKUP_CODES = "many_active_backup_codes"; + case THIRD_PARTY_INTEGRATION_DETECTED = "third_party_integration_detected"; + case TTLOCK_LOCK_GATEWAY_UNLOCKING_NOT_ENABLED = "ttlock_lock_gateway_unlocking_not_enabled"; + case TTLOCK_WEAK_GATEWAY_SIGNAL = "ttlock_weak_gateway_signal"; + case POWER_SAVING_MODE = "power_saving_mode"; + case TEMPERATURE_THRESHOLD_EXCEEDED = "temperature_threshold_exceeded"; + case DEVICE_COMMUNICATION_DEGRADED = "device_communication_degraded"; + case SCHEDULED_MAINTENANCE_WINDOW = "scheduled_maintenance_window"; + case DEVICE_HAS_FLAKY_CONNECTION = "device_has_flaky_connection"; + case SALTO_KS_OFFICE_MODE = "salto_ks_office_mode"; + case SALTO_KS_PRIVACY_MODE = "salto_ks_privacy_mode"; + case PRIVACY_MODE = "privacy_mode"; + case SALTO_KS_SUBSCRIPTION_LIMIT_ALMOST_REACHED = "salto_ks_subscription_limit_almost_reached"; + case SALTO_KS_LOCK_ACCESS_CODE_SUPPORT_REMOVED = "salto_ks_lock_access_code_support_removed"; + case UNKNOWN_ISSUE_WITH_PHONE = "unknown_issue_with_phone"; + case LOCKLY_TIME_ZONE_NOT_CONFIGURED = "lockly_time_zone_not_configured"; + case ULTRALOQ_TIME_ZONE_UNKNOWN = "ultraloq_time_zone_unknown"; + case TIME_ZONE_UNKNOWN = "time_zone_unknown"; + case TIME_ZONE_MISMATCH = "time_zone_mismatch"; + case TWO_N_DEVICE_MISSING_TIMEZONE = "two_n_device_missing_timezone"; + case HUB_REQUIRED_FOR_ADDITIONAL_CAPABILITIES = "hub_required_for_additional_capabilities"; + case PROVIDER_ISSUE = "provider_issue"; + case KEYNEST_UNSUPPORTED_LOCKER = "keynest_unsupported_locker"; + case ACCESSORY_KEYPAD_SETUP_REQUIRED = "accessory_keypad_setup_required"; + case UNRELIABLE_ONLINE_STATUS = "unreliable_online_status"; + case MAX_ACCESS_CODES_REACHED = "max_access_codes_reached"; + } } diff --git a/src/Resources/DeviceProvider.php b/src/Resources/DeviceProvider.php index 2f1539f6..17529829 100644 --- a/src/Resources/DeviceProvider.php +++ b/src/Resources/DeviceProvider.php @@ -1,148 +1,222 @@ device_provider_name ?? null, + display_name: $json->display_name ?? null, + image_url: $json->image_url ?? null, + provider_categories: $json->provider_categories ?? null, + can_configure_auto_lock: $json->can_configure_auto_lock ?? null, + can_hvac_cool: $json->can_hvac_cool ?? null, + can_hvac_heat: $json->can_hvac_heat ?? null, + can_hvac_heat_cool: $json->can_hvac_heat_cool ?? null, + can_program_offline_access_codes: $json->can_program_offline_access_codes ?? + null, + can_program_online_access_codes: $json->can_program_online_access_codes ?? + null, + can_program_thermostat_programs_as_different_each_day: $json->can_program_thermostat_programs_as_different_each_day ?? + null, + can_program_thermostat_programs_as_same_each_day: $json->can_program_thermostat_programs_as_same_each_day ?? + null, + can_program_thermostat_programs_as_weekday_weekend: $json->can_program_thermostat_programs_as_weekday_weekend ?? + null, + can_remotely_lock: $json->can_remotely_lock ?? null, + can_remotely_unlock: $json->can_remotely_unlock ?? null, + can_run_thermostat_programs: $json->can_run_thermostat_programs ?? + null, + can_simulate_connection: $json->can_simulate_connection ?? null, + can_simulate_disconnection: $json->can_simulate_disconnection ?? + null, + can_simulate_hub_connection: $json->can_simulate_hub_connection ?? + null, + can_simulate_hub_disconnection: $json->can_simulate_hub_disconnection ?? + null, + can_simulate_paid_subscription: $json->can_simulate_paid_subscription ?? + null, + can_simulate_removal: $json->can_simulate_removal ?? null, + can_turn_off_hvac: $json->can_turn_off_hvac ?? null, + can_unlock_with_code: $json->can_unlock_with_code ?? null, + ); } - return new self( - can_configure_auto_lock: $json->can_configure_auto_lock ?? null, - can_hvac_cool: $json->can_hvac_cool ?? null, - can_hvac_heat: $json->can_hvac_heat ?? null, - can_hvac_heat_cool: $json->can_hvac_heat_cool ?? null, - can_program_offline_access_codes: $json->can_program_offline_access_codes ?? - null, - can_program_online_access_codes: $json->can_program_online_access_codes ?? - null, - can_program_thermostat_programs_as_different_each_day: $json->can_program_thermostat_programs_as_different_each_day ?? - null, - can_program_thermostat_programs_as_same_each_day: $json->can_program_thermostat_programs_as_same_each_day ?? - null, - can_program_thermostat_programs_as_weekday_weekend: $json->can_program_thermostat_programs_as_weekday_weekend ?? - null, - can_remotely_lock: $json->can_remotely_lock ?? null, - can_remotely_unlock: $json->can_remotely_unlock ?? null, - can_run_thermostat_programs: $json->can_run_thermostat_programs ?? - null, - can_simulate_connection: $json->can_simulate_connection ?? null, - can_simulate_disconnection: $json->can_simulate_disconnection ?? - null, - can_simulate_hub_connection: $json->can_simulate_hub_connection ?? - null, - can_simulate_hub_disconnection: $json->can_simulate_hub_disconnection ?? - null, - can_simulate_paid_subscription: $json->can_simulate_paid_subscription ?? - null, - can_simulate_removal: $json->can_simulate_removal ?? null, - can_turn_off_hvac: $json->can_turn_off_hvac ?? null, - can_unlock_with_code: $json->can_unlock_with_code ?? null, - device_provider_name: $json->device_provider_name ?? null, - display_name: $json->display_name ?? null, - image_url: $json->image_url ?? null, - provider_categories: $json->provider_categories ?? null, - ); + + public function __construct( + /** + * Name of the device provider. + * + * @var value-of<\Seam\Resources\DeviceProvider\DeviceProviderName>|string|null + */ + public string|null $device_provider_name, + /** + * Display name for the device provider. + */ + public string|null $display_name, + /** + * Image URL for the device provider. + */ + public string|null $image_url, + /** + * List of provider categories to which the device provider belongs, such as `stable`, `consumer_smartlocks`, `thermostats`, and so on. + * + * @var list|null + */ + public array|null $provider_categories, + /** + * Indicates whether the lock supports configuring automatic locking. + */ + public bool|null $can_configure_auto_lock = null, + /** + * Indicates whether the thermostat supports cooling. + */ + public bool|null $can_hvac_cool = null, + /** + * Indicates whether the thermostat supports heating. + */ + public bool|null $can_hvac_heat = null, + /** + * Indicates whether the thermostat supports simultaneous heating and cooling. + */ + public bool|null $can_hvac_heat_cool = null, + /** + * Indicates whether the device supports programming offline access codes. + */ + public bool|null $can_program_offline_access_codes = null, + /** + * Indicates whether the device supports programming online access codes. + */ + public bool|null $can_program_online_access_codes = null, + /** + * Indicates whether the thermostat supports different climate programs for each day of the week. + */ + public bool|null $can_program_thermostat_programs_as_different_each_day = null, + /** + * Indicates whether the thermostat supports a single climate program applied to every day. + */ + public bool|null $can_program_thermostat_programs_as_same_each_day = null, + /** + * Indicates whether the thermostat supports weekday/weekend climate programs. + */ + public bool|null $can_program_thermostat_programs_as_weekday_weekend = null, + /** + * Indicates whether the device supports remote locking. + */ + public bool|null $can_remotely_lock = null, + /** + * Indicates whether the device supports remote unlocking. + */ + public bool|null $can_remotely_unlock = null, + /** + * Indicates whether the thermostat supports running climate programs. + */ + public bool|null $can_run_thermostat_programs = null, + /** + * Indicates whether the device supports simulating connection in a sandbox. + */ + public bool|null $can_simulate_connection = null, + /** + * Indicates whether the device supports simulating disconnection in a sandbox. + */ + public bool|null $can_simulate_disconnection = null, + /** + * Indicates whether the hub supports simulating connection in a sandbox. + */ + public bool|null $can_simulate_hub_connection = null, + /** + * Indicates whether the hub supports simulating disconnection in a sandbox. + */ + public bool|null $can_simulate_hub_disconnection = null, + /** + * Indicates whether the device supports simulating a paid subscription in a sandbox. + */ + public bool|null $can_simulate_paid_subscription = null, + /** + * Indicates whether the device supports simulating removal in a sandbox. + */ + public bool|null $can_simulate_removal = null, + /** + * Indicates whether the thermostat can be turned off. + */ + public bool|null $can_turn_off_hvac = null, + /** + * Indicates whether the lock supports unlocking with an access code. + */ + public bool|null $can_unlock_with_code = null, + ) {} } +} - public function __construct( - /** - * Indicates whether the lock supports configuring automatic locking. - */ - public bool|null $can_configure_auto_lock, - /** - * Indicates whether the thermostat supports cooling. - */ - public bool|null $can_hvac_cool, - /** - * Indicates whether the thermostat supports heating. - */ - public bool|null $can_hvac_heat, - /** - * Indicates whether the thermostat supports simultaneous heating and cooling. - */ - public bool|null $can_hvac_heat_cool, - /** - * Indicates whether the device supports programming offline access codes. - */ - public bool|null $can_program_offline_access_codes, - /** - * Indicates whether the device supports programming online access codes. - */ - public bool|null $can_program_online_access_codes, - /** - * Indicates whether the thermostat supports different climate programs for each day of the week. - */ - public bool|null $can_program_thermostat_programs_as_different_each_day, - /** - * Indicates whether the thermostat supports a single climate program applied to every day. - */ - public bool|null $can_program_thermostat_programs_as_same_each_day, - /** - * Indicates whether the thermostat supports weekday/weekend climate programs. - */ - public bool|null $can_program_thermostat_programs_as_weekday_weekend, - /** - * Indicates whether the device supports remote locking. - */ - public bool|null $can_remotely_lock, - /** - * Indicates whether the device supports remote unlocking. - */ - public bool|null $can_remotely_unlock, - /** - * Indicates whether the thermostat supports running climate programs. - */ - public bool|null $can_run_thermostat_programs, - /** - * Indicates whether the device supports simulating connection in a sandbox. - */ - public bool|null $can_simulate_connection, - /** - * Indicates whether the device supports simulating disconnection in a sandbox. - */ - public bool|null $can_simulate_disconnection, - /** - * Indicates whether the hub supports simulating connection in a sandbox. - */ - public bool|null $can_simulate_hub_connection, - /** - * Indicates whether the hub supports simulating disconnection in a sandbox. - */ - public bool|null $can_simulate_hub_disconnection, - /** - * Indicates whether the device supports simulating a paid subscription in a sandbox. - */ - public bool|null $can_simulate_paid_subscription, - /** - * Indicates whether the device supports simulating removal in a sandbox. - */ - public bool|null $can_simulate_removal, - /** - * Indicates whether the thermostat can be turned off. - */ - public bool|null $can_turn_off_hvac, - /** - * Indicates whether the lock supports unlocking with an access code. - */ - public bool|null $can_unlock_with_code, - /** - * Name of the device provider. - */ - public string|null $device_provider_name, - /** - * Display name for the device provider. - */ - public string|null $display_name, - /** - * Image URL for the device provider. - */ - public string|null $image_url, - /** - * List of provider categories to which the device provider belongs, such as `stable`, `consumer_smartlocks`, `thermostats`, and so on. - */ - public array|null $provider_categories, - ) {} +namespace Seam\Resources\DeviceProvider { + enum DeviceProviderName: string + { + case HOTEK = "hotek"; + case DORMAKABA_COMMUNITY = "dormakaba_community"; + case LEGIC_CONNECT = "legic_connect"; + case AKUVOX = "akuvox"; + case AUGUST = "august"; + case AVIGILON_ALTA = "avigilon_alta"; + case BRIVO = "brivo"; + case BUTTERFLYMX = "butterflymx"; + case SCHLAGE = "schlage"; + case SMARTTHINGS = "smartthings"; + case YALE = "yale"; + case GENIE = "genie"; + case DOORKING = "doorking"; + case SALTO = "salto"; + case SALTO_KS = "salto_ks"; + case SALTO_KS_ACCEPT = "salto_ks_accept"; + case LOCKLY = "lockly"; + case TTLOCK = "ttlock"; + case LINEAR = "linear"; + case NOISEAWARE = "noiseaware"; + case NUKI = "nuki"; + case IGLOO = "igloo"; + case KWIKSET = "kwikset"; + case MINUT = "minut"; + case MY_2N = "my_2n"; + case CONTROLBYWEB = "controlbyweb"; + case NEST = "nest"; + case IGLOOHOME = "igloohome"; + case ECOBEE = "ecobee"; + case FOUR_SUITES = "four_suites"; + case DORMAKABA_ORACODE = "dormakaba_oracode"; + case PTI = "pti"; + case WYZE = "wyze"; + case SEAM_PASSPORT = "seam_passport"; + case VISIONLINE = "visionline"; + case ASSA_ABLOY_CREDENTIAL_SERVICE = "assa_abloy_credential_service"; + case TEDEE = "tedee"; + case HONEYWELL_RESIDEO = "honeywell_resideo"; + case FIRST_ALERT = "first_alert"; + case LATCH = "latch"; + case AKILES = "akiles"; + case ASSA_ABLOY_VOSTIO = "assa_abloy_vostio"; + case ASSA_ABLOY_VOSTIO_CREDENTIAL_SERVICE = "assa_abloy_vostio_credential_service"; + case TADO = "tado"; + case SALTO_SPACE = "salto_space"; + case SENSI = "sensi"; + case KEYNEST = "keynest"; + case KORELOCK = "korelock"; + case KEYINCODE = "keyincode"; + case DORMAKABA_AMBIANCE = "dormakaba_ambiance"; + case ULTRALOQ = "ultraloq"; + case YACAN = "yacan"; + case DUSAW = "dusaw"; + case SIFELY = "sifely"; + case THIRTY_THREE_LOCK = "thirty_three_lock"; + case RING = "ring"; + case ICAL = "ical"; + case LODGIFY = "lodgify"; + case HOSTAWAY = "hostaway"; + case GUESTY = "guesty"; + case ACUITY_SCHEDULING = "acuity_scheduling"; + case OMNITEC = "omnitec"; + case KISI = "kisi"; + case AQARA = "aqara"; + } } diff --git a/src/Resources/Event.php b/src/Resources/Event.php index 9277ed2f..24816fc4 100644 --- a/src/Resources/Event.php +++ b/src/Resources/Event.php @@ -1,946 +1,12990 @@ EventAccessCodeErrors::from_json($a), - $json->access_code_errors ?? [], - ), - access_code_id: $json->access_code_id ?? null, - access_code_is_managed: $json->access_code_is_managed ?? null, - access_code_warnings: array_map( - fn($a) => EventAccessCodeWarnings::from_json($a), - $json->access_code_warnings ?? [], - ), - access_grant_id: $json->access_grant_id ?? null, - access_grant_ids: $json->access_grant_ids ?? null, - access_grant_key: $json->access_grant_key ?? null, - access_grant_keys: $json->access_grant_keys ?? null, - access_method_id: $json->access_method_id ?? null, - acs_access_group_id: $json->acs_access_group_id ?? null, - acs_credential_id: $json->acs_credential_id ?? null, - acs_encoder_id: $json->acs_encoder_id ?? null, - acs_entrance_id: $json->acs_entrance_id ?? null, - acs_entrance_ids: $json->acs_entrance_ids ?? null, - acs_system_errors: array_map( - fn($a) => EventAcsSystemErrors::from_json($a), - $json->acs_system_errors ?? [], - ), - acs_system_id: $json->acs_system_id ?? null, - acs_system_warnings: array_map( - fn($a) => EventAcsSystemWarnings::from_json($a), - $json->acs_system_warnings ?? [], - ), - acs_user_id: $json->acs_user_id ?? null, - action_attempt_id: $json->action_attempt_id ?? null, - action_type: $json->action_type ?? null, - activation_reason: $json->activation_reason ?? null, - backup_access_code_id: $json->backup_access_code_id ?? null, - battery_level: $json->battery_level ?? null, - battery_status: $json->battery_status ?? null, - change_reason: $json->change_reason ?? null, - changed_properties: array_map( - fn($c) => EventChangedProperties::from_json($c), - $json->changed_properties ?? [], - ), - client_session_id: $json->client_session_id ?? null, - climate_preset_key: $json->climate_preset_key ?? null, - code: $json->code ?? null, - connect_webview_id: $json->connect_webview_id ?? null, - connected_account_custom_metadata: $json->connected_account_custom_metadata ?? - null, - connected_account_errors: array_map( - fn($c) => EventConnectedAccountErrors::from_json($c), - $json->connected_account_errors ?? [], - ), - connected_account_id: $json->connected_account_id ?? null, - connected_account_type: $json->connected_account_type ?? null, - connected_account_warnings: array_map( - fn($c) => EventConnectedAccountWarnings::from_json($c), - $json->connected_account_warnings ?? [], - ), - cooling_set_point_celsius: $json->cooling_set_point_celsius ?? null, - cooling_set_point_fahrenheit: $json->cooling_set_point_fahrenheit ?? - null, - created_at: $json->created_at ?? null, - customer_key: $json->customer_key ?? null, - description: $json->description ?? null, - desired_temperature_celsius: $json->desired_temperature_celsius ?? - null, - desired_temperature_fahrenheit: $json->desired_temperature_fahrenheit ?? - null, - device_custom_metadata: $json->device_custom_metadata ?? null, - device_errors: array_map( - fn($d) => EventDeviceErrors::from_json($d), - $json->device_errors ?? [], - ), - device_id: $json->device_id ?? null, - device_ids: $json->device_ids ?? null, - device_name: $json->device_name ?? null, - device_warnings: array_map( - fn($d) => EventDeviceWarnings::from_json($d), - $json->device_warnings ?? [], - ), - ends_at: $json->ends_at ?? null, - error_code: $json->error_code ?? null, - error_message: $json->error_message ?? null, - event_description: $json->event_description ?? null, - event_id: $json->event_id ?? null, - event_type: $json->event_type ?? null, - fan_mode_setting: $json->fan_mode_setting ?? null, - from: isset($json->from) ? EventFrom::from_json($json->from) : null, - heating_set_point_celsius: $json->heating_set_point_celsius ?? null, - heating_set_point_fahrenheit: $json->heating_set_point_fahrenheit ?? - null, - hvac_mode_setting: $json->hvac_mode_setting ?? null, - image_url: $json->image_url ?? null, - is_backup_code: $json->is_backup_code ?? null, - is_fallback_climate_preset: $json->is_fallback_climate_preset ?? - null, - is_via_bluetooth: $json->is_via_bluetooth ?? null, - is_via_nfc: $json->is_via_nfc ?? null, - lower_limit_celsius: $json->lower_limit_celsius ?? null, - lower_limit_fahrenheit: $json->lower_limit_fahrenheit ?? null, - method: $json->method ?? null, - minut_metadata: $json->minut_metadata ?? null, - missing_device_ids: $json->missing_device_ids ?? null, - motion_sub_type: $json->motion_sub_type ?? null, - noise_level_decibels: $json->noise_level_decibels ?? null, - noise_level_nrs: $json->noise_level_nrs ?? null, - noise_threshold_id: $json->noise_threshold_id ?? null, - noise_threshold_name: $json->noise_threshold_name ?? null, - noiseaware_metadata: $json->noiseaware_metadata ?? null, - occurred_at: $json->occurred_at ?? null, - reason: isset($json->reason) - ? EventReason::from_json($json->reason) - : null, - requested_mutations: array_map( - fn($r) => EventRequestedMutations::from_json($r), - $json->requested_mutations ?? [], - ), - space_id: $json->space_id ?? null, - space_key: $json->space_key ?? null, - starts_at: $json->starts_at ?? null, - status: $json->status ?? null, - temperature_celsius: $json->temperature_celsius ?? null, - temperature_fahrenheit: $json->temperature_fahrenheit ?? null, - thermostat_schedule_id: $json->thermostat_schedule_id ?? null, - to: isset($json->to) ? EventTo::from_json($json->to) : null, - upper_limit_celsius: $json->upper_limit_celsius ?? null, - upper_limit_fahrenheit: $json->upper_limit_fahrenheit ?? null, - user_identity_id: $json->user_identity_id ?? null, - video_url: $json->video_url ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Errors associated with the access code. - */ - public array $access_code_errors, - /** - * ID of the affected access code. - */ - public string|null $access_code_id, - /** - * Whether the access code is managed by Seam (true) or unmanaged (false). Only present when access_code_id is set. - */ - public bool|null $access_code_is_managed, - /** - * Warnings associated with the access code. - */ - public array $access_code_warnings, - /** - * ID of the affected Access Grant. - */ - public string|null $access_grant_id, - /** - * IDs of the access grants associated with this access method. - */ - public array|null $access_grant_ids, - /** - * Key of the affected Access Grant (if present). - */ - public string|null $access_grant_key, - /** - * Keys of the access grants associated with this access method (if present). - */ - public array|null $access_grant_keys, - /** - * ID of the affected access method. - */ - public string|null $access_method_id, - /** - * ID of the affected access group. - */ - public string|null $acs_access_group_id, - /** - * ID of the affected credential. - */ - public string|null $acs_credential_id, - /** - * ID of the affected encoder. - */ - public string|null $acs_encoder_id, - /** - * ID of the affected [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). - */ - public string|null $acs_entrance_id, - /** - * IDs of all ACS entrances currently attached to the space. - */ - public array|null $acs_entrance_ids, - /** - * Errors associated with the access control system. - */ - public array $acs_system_errors, - /** - * ID of the access system. - */ - public string|null $acs_system_id, - /** - * Warnings associated with the access control system. - */ - public array $acs_system_warnings, - /** - * ID of the affected access system user. - */ - public string|null $acs_user_id, - /** - * ID of the affected action attempt. - */ - public string|null $action_attempt_id, - /** - * Type of the action. - */ - public string|null $action_type, - /** - * The reason the camera was activated. - */ - public string|null $activation_reason, - /** - * ID of the backup access code that was pulled from the pool. - */ - public string|null $backup_access_code_id, - /** - * Number in the range 0 to 1.0 indicating the amount of battery in the affected device, as reported by the device. - */ - public float|null $battery_level, - /** - * Battery status of the affected device, calculated from the numeric `battery_level` value. - */ - public string|null $battery_status, - /** - * Human-readable reason for the change (e.g. `ongoing code auto-renewed`). - */ - public string|null $change_reason, - /** - * List of properties that changed on the access code. - */ - public array $changed_properties, - /** - * ID of the affected client session. - */ - public string|null $client_session_id, - /** - * Key of the climate preset that was activated. - */ - public string|null $climate_preset_key, - /** - * Code for the affected access code. - */ - public string|null $code, - /** - * ID of the Connect Webview associated with the event. - */ - public string|null $connect_webview_id, - /** - * Custom metadata of the connected account, present when connected_account_id is provided. - */ - public mixed $connected_account_custom_metadata, - /** - * Errors associated with the connected account. - */ - public array $connected_account_errors, - /** - * ID of the connected account associated with the affected access code. - */ - public string|null $connected_account_id, - /** - * undocumented: Unreleased. - */ - public string|null $connected_account_type, - /** - * Warnings associated with the connected account. - */ - public array $connected_account_warnings, - /** - * Temperature to which the thermostat should cool (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $cooling_set_point_celsius, - /** - * Temperature to which the thermostat should cool (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $cooling_set_point_fahrenheit, - /** - * Date and time at which the event was created. - */ - public string|null $created_at, - /** - * The customer key associated with this connected account, if any. - */ - public string|null $customer_key, - /** - * Human-readable description of the change and its source. - */ - public string|null $description, - /** - * Desired temperature, in °C, defined by the affected thermostat's cooling or heating set point. - */ - public float|null $desired_temperature_celsius, - /** - * Desired temperature, in °F, defined by the affected thermostat's cooling or heating set point. - */ - public float|null $desired_temperature_fahrenheit, - /** - * Custom metadata of the device, present when device_id is provided. - */ - public mixed $device_custom_metadata, - /** - * Errors associated with the device. - */ - public array $device_errors, - /** - * ID of the device associated with the affected access code. - */ - public string|null $device_id, - /** - * IDs of all devices currently attached to the space. - */ - public array|null $device_ids, - /** - * Name of the deleted device, captured at deletion time. The device record no longer exists when this event fires, so the name is preserved here. Null when the device had no resolvable name. - */ - public string|null $device_name, - /** - * Warnings associated with the device. - */ - public array $device_warnings, - /** - * The new end time for the access grant. - */ - public string|null $ends_at, - /** - * Error code associated with the disconnection event, if any. - */ - public string|null $error_code, - /** - * Description of why the access methods could not be created. - */ - public string|null $error_message, - /** - * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. - */ - public string|null $event_description, - /** - * ID of the event. - */ - public string|null $event_id, - public string|null $event_type, - /** - * Desired [fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings), such as `on`, `auto`, or `circulate`. - */ - public string|null $fan_mode_setting, - /** - * Previous access code name configuration. - */ - public EventFrom|null $from, - /** - * Temperature to which the thermostat should heat (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $heating_set_point_celsius, - /** - * Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). - */ - public float|null $heating_set_point_fahrenheit, - /** - * Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. - */ - public string|null $hvac_mode_setting, - /** - * URL to a thumbnail image captured at the time of activation. - */ - public string|null $image_url, - /** - * Indicates whether the code is a backup code (only present when mode is 'code' and a backup code was used). - */ - public bool|null $is_backup_code, - /** - * Indicates whether the climate preset that was activated is the fallback climate preset for the thermostat. - */ - public bool|null $is_fallback_climate_preset, - /** - * Whether the lock action was performed over Bluetooth by a remote client (such as the provider's mobile app), rather than a direct physical interaction or a Seam-initiated remote action. - */ - public bool|null $is_via_bluetooth, - /** - * Whether the lock action was performed by an NFC credential tap (such as an Apple Home Key or an NFC key fob) presented to the lock, rather than a direct physical interaction or a Seam-initiated remote action. - */ - public bool|null $is_via_nfc, - /** - * Lower temperature limit, in °C, defined by the set threshold. - */ - public float|null $lower_limit_celsius, - /** - * Lower temperature limit, in °F, defined by the set threshold. - */ - public float|null $lower_limit_fahrenheit, - /** - * Method by which the lock was locked. `keycode`: an access code was used (see `access_code_id`). `manual`: a physical action such as a thumbturn or button press. `remote`: a remote action via an app, Bluetooth, or the Seam API (see `action_attempt_id` if Seam-initiated; see `is_via_bluetooth` or `is_via_nfc` for the transport). `automatic`: triggered automatically, for example by an auto-relock timer. `unknown`: could not be determined. - */ - public string|null $method, - /** - * Metadata from Minut. - */ - public mixed $minut_metadata, - /** - * IDs of the devices that did not receive a requested access method. Use these to identify which specific devices failed without having to fetch the Access Grant. - */ - public array|null $missing_device_ids, - /** - * Sub-type of motion detected, if available. - */ - public string|null $motion_sub_type, - /** - * Detected noise level in decibels. - */ - public float|null $noise_level_decibels, - /** - * Detected noise level in Noiseaware Noise Risk Score (NRS). - */ - public float|null $noise_level_nrs, - /** - * ID of the noise threshold that was triggered. - */ - public string|null $noise_threshold_id, - /** - * Name of the noise threshold that was triggered. - */ - public string|null $noise_threshold_name, - /** - * Metadata from Noiseaware. - */ - public mixed $noiseaware_metadata, - /** - * Date and time at which the event occurred. - */ - public string|null $occurred_at, - /** - * Why access was denied, when the provider reports a determinable cause. Omitted when unknown. - */ - public EventReason|null $reason, - /** - * Array of mutations requested on the access code, each containing the mutation type and from/to values. - */ - public array $requested_mutations, - /** - * ID of the affected space. - */ - public string|null $space_id, - /** - * Unique key for the space within the workspace. - */ - public string|null $space_key, - /** - * The new start time for the access grant. - */ - public string|null $starts_at, - /** - * Status of the action. - */ - public string|null $status, - /** - * Temperature, in °C, reported by the affected thermostat. - */ - public float|null $temperature_celsius, - /** - * Temperature, in °F, reported by the affected thermostat. - */ - public float|null $temperature_fahrenheit, - /** - * ID of the thermostat schedule that prompted the affected climate preset to be activated. - */ - public string|null $thermostat_schedule_id, - /** - * New access code name configuration. - */ - public EventTo|null $to, - /** - * Upper temperature limit, in °C, defined by the set threshold. - */ - public float|null $upper_limit_celsius, - /** - * Upper temperature limit, in °F, defined by the set threshold. - */ - public float|null $upper_limit_fahrenheit, - /** - * undocumented: Unreleased. - * --- - * ID of the user identity associated with the lock event. - */ - public string|null $user_identity_id, - /** - * URL to a short video clip captured at the time of activation. - */ - public string|null $video_url, - /** - * ID of the workspace associated with the event. - */ - public string|null $workspace_id, - ) {} +namespace Seam\Resources { + /** + * Base class for events returned by the Seam API. Known event_type values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Event + { + public static function from_json(mixed $json): Event|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->event_type ?? null) + ? \Seam\Resources\Event\EventType::tryFrom($json->event_type) + : null; + + return match ($discriminant) { + \Seam\Resources\Event\EventType::ACCESS_CODE_CREATED + => \Seam\Resources\Event\AccessCodeCreated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_CHANGED + => \Seam\Resources\Event\AccessCodeChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_NAME_CHANGED + => \Seam\Resources\Event\AccessCodeNameChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_CODE_CHANGED + => \Seam\Resources\Event\AccessCodeCodeChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_TIME_FRAME_CHANGED + => \Seam\Resources\Event\AccessCodeTimeFrameChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_MUTATIONS_REQUESTED + => \Seam\Resources\Event\AccessCodeMutationsRequested::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_SCHEDULED_ON_DEVICE + => \Seam\Resources\Event\AccessCodeScheduledOnDevice::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_SET_ON_DEVICE + => \Seam\Resources\Event\AccessCodeSetOnDevice::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_REMOVED_FROM_DEVICE + => \Seam\Resources\Event\AccessCodeRemovedFromDevice::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_DELAY_IN_SETTING_ON_DEVICE + => \Seam\Resources\Event\AccessCodeDelayInSettingOnDevice::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_FAILED_TO_SET_ON_DEVICE + => \Seam\Resources\Event\AccessCodeFailedToSetOnDevice::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_DELETED + => \Seam\Resources\Event\AccessCodeDeleted::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_DELAY_IN_REMOVING_FROM_DEVICE + => \Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_FAILED_TO_REMOVE_FROM_DEVICE + => \Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_MODIFIED_EXTERNAL_TO_SEAM + => \Seam\Resources\Event\AccessCodeModifiedExternalToSeam::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_DELETED_EXTERNAL_TO_SEAM + => \Seam\Resources\Event\AccessCodeDeletedExternalToSeam::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_BACKUP_ACCESS_CODE_PULLED + => \Seam\Resources\Event\AccessCodeBackupAccessCodePulled::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_UNMANAGED_CONVERTED_TO_MANAGED + => \Seam\Resources\Event\AccessCodeUnmanagedConvertedToManaged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_UNMANAGED_FAILED_TO_CONVERT_TO_MANAGED + => \Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_UNMANAGED_CREATED + => \Seam\Resources\Event\AccessCodeUnmanagedCreated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_CODE_UNMANAGED_REMOVED + => \Seam\Resources\Event\AccessCodeUnmanagedRemoved::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_GRANT_CREATED + => \Seam\Resources\Event\AccessGrantCreated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_GRANT_DELETED + => \Seam\Resources\Event\AccessGrantDeleted::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_GRANT_ACCESS_GRANTED_TO_ALL_DOORS + => \Seam\Resources\Event\AccessGrantAccessGrantedToAllDoors::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_GRANT_ACCESS_GRANTED_TO_DOOR + => \Seam\Resources\Event\AccessGrantAccessGrantedToDoor::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_GRANT_ACCESS_TO_DOOR_LOST + => \Seam\Resources\Event\AccessGrantAccessToDoorLost::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_GRANT_ACCESS_TIMES_CHANGED + => \Seam\Resources\Event\AccessGrantAccessTimesChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_GRANT_COULD_NOT_CREATE_REQUESTED_ACCESS_METHODS + => \Seam\Resources\Event\AccessGrantCouldNotCreateRequestedAccessMethods::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_METHOD_ISSUED + => \Seam\Resources\Event\AccessMethodIssued::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_METHOD_REVOKED + => \Seam\Resources\Event\AccessMethodRevoked::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_METHOD_CARD_ENCODING_REQUIRED + => \Seam\Resources\Event\AccessMethodCardEncodingRequired::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_METHOD_DELETED + => \Seam\Resources\Event\AccessMethodDeleted::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_METHOD_REISSUED + => \Seam\Resources\Event\AccessMethodReissued::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_METHOD_CREATED + => \Seam\Resources\Event\AccessMethodCreated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_METHOD_DELAY_IN_ISSUING + => \Seam\Resources\Event\AccessMethodDelayInIssuing::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACCESS_METHOD_FAILED_TO_ISSUE + => \Seam\Resources\Event\AccessMethodFailedToIssue::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_SYSTEM_CONNECTED + => \Seam\Resources\Event\AcsSystemConnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_SYSTEM_ADDED + => \Seam\Resources\Event\AcsSystemAdded::from_json($json), + \Seam\Resources\Event\EventType::ACS_SYSTEM_DISCONNECTED + => \Seam\Resources\Event\AcsSystemDisconnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_CREDENTIAL_DELETED + => \Seam\Resources\Event\AcsCredentialDeleted::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_CREDENTIAL_ISSUED + => \Seam\Resources\Event\AcsCredentialIssued::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_CREDENTIAL_REISSUED + => \Seam\Resources\Event\AcsCredentialReissued::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_CREDENTIAL_INVALIDATED + => \Seam\Resources\Event\AcsCredentialInvalidated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_USER_CREATED + => \Seam\Resources\Event\AcsUserCreated::from_json($json), + \Seam\Resources\Event\EventType::ACS_USER_DELETED + => \Seam\Resources\Event\AcsUserDeleted::from_json($json), + \Seam\Resources\Event\EventType::ACS_ENCODER_ADDED + => \Seam\Resources\Event\AcsEncoderAdded::from_json($json), + \Seam\Resources\Event\EventType::ACS_ENCODER_REMOVED + => \Seam\Resources\Event\AcsEncoderRemoved::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_ACCESS_GROUP_DELETED + => \Seam\Resources\Event\AcsAccessGroupDeleted::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACS_ENTRANCE_ADDED + => \Seam\Resources\Event\AcsEntranceAdded::from_json($json), + \Seam\Resources\Event\EventType::ACS_ENTRANCE_REMOVED + => \Seam\Resources\Event\AcsEntranceRemoved::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CLIENT_SESSION_DELETED + => \Seam\Resources\Event\ClientSessionDeleted::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECTED_ACCOUNT_CONNECTED + => \Seam\Resources\Event\ConnectedAccountConnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECTED_ACCOUNT_CREATED + => \Seam\Resources\Event\ConnectedAccountCreated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECTED_ACCOUNT_SUCCESSFUL_LOGIN + => \Seam\Resources\Event\ConnectedAccountSuccessfulLogin::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECTED_ACCOUNT_DISCONNECTED + => \Seam\Resources\Event\ConnectedAccountDisconnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECTED_ACCOUNT_COMPLETED_FIRST_SYNC + => \Seam\Resources\Event\ConnectedAccountCompletedFirstSync::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECTED_ACCOUNT_DELETED + => \Seam\Resources\Event\ConnectedAccountDeleted::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECTED_ACCOUNT_COMPLETED_FIRST_SYNC_AFTER_RECONNECTION + => \Seam\Resources\Event\ConnectedAccountCompletedFirstSyncAfterReconnection::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECTED_ACCOUNT_REAUTHORIZATION_REQUESTED + => \Seam\Resources\Event\ConnectedAccountReauthorizationRequested::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACTION_ATTEMPT_LOCK_DOOR_SUCCEEDED + => \Seam\Resources\Event\ActionAttemptLockDoorSucceeded::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACTION_ATTEMPT_LOCK_DOOR_FAILED + => \Seam\Resources\Event\ActionAttemptLockDoorFailed::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACTION_ATTEMPT_UNLOCK_DOOR_SUCCEEDED + => \Seam\Resources\Event\ActionAttemptUnlockDoorSucceeded::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACTION_ATTEMPT_UNLOCK_DOOR_FAILED + => \Seam\Resources\Event\ActionAttemptUnlockDoorFailed::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACTION_ATTEMPT_SIMULATE_KEYPAD_CODE_ENTRY_SUCCEEDED + => \Seam\Resources\Event\ActionAttemptSimulateKeypadCodeEntrySucceeded::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACTION_ATTEMPT_SIMULATE_KEYPAD_CODE_ENTRY_FAILED + => \Seam\Resources\Event\ActionAttemptSimulateKeypadCodeEntryFailed::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACTION_ATTEMPT_SIMULATE_MANUAL_LOCK_VIA_KEYPAD_SUCCEEDED + => \Seam\Resources\Event\ActionAttemptSimulateManualLockViaKeypadSucceeded::from_json( + $json, + ), + \Seam\Resources\Event\EventType::ACTION_ATTEMPT_SIMULATE_MANUAL_LOCK_VIA_KEYPAD_FAILED + => \Seam\Resources\Event\ActionAttemptSimulateManualLockViaKeypadFailed::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECT_WEBVIEW_LOGIN_SUCCEEDED + => \Seam\Resources\Event\ConnectWebviewLoginSucceeded::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CONNECT_WEBVIEW_LOGIN_FAILED + => \Seam\Resources\Event\ConnectWebviewLoginFailed::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_CONNECTED + => \Seam\Resources\Event\DeviceConnected::from_json($json), + \Seam\Resources\Event\EventType::DEVICE_ADDED + => \Seam\Resources\Event\DeviceAdded::from_json($json), + \Seam\Resources\Event\EventType::DEVICE_CONVERTED_TO_UNMANAGED + => \Seam\Resources\Event\DeviceConvertedToUnmanaged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_UNMANAGED_CONVERTED_TO_MANAGED + => \Seam\Resources\Event\DeviceUnmanagedConvertedToManaged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_UNMANAGED_CONNECTED + => \Seam\Resources\Event\DeviceUnmanagedConnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_DISCONNECTED + => \Seam\Resources\Event\DeviceDisconnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_UNMANAGED_DISCONNECTED + => \Seam\Resources\Event\DeviceUnmanagedDisconnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_TAMPERED + => \Seam\Resources\Event\DeviceTampered::from_json($json), + \Seam\Resources\Event\EventType::DEVICE_LOW_BATTERY + => \Seam\Resources\Event\DeviceLowBattery::from_json($json), + \Seam\Resources\Event\EventType::DEVICE_BATTERY_STATUS_CHANGED + => \Seam\Resources\Event\DeviceBatteryStatusChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_REMOVED + => \Seam\Resources\Event\DeviceRemoved::from_json($json), + \Seam\Resources\Event\EventType::DEVICE_DELETED + => \Seam\Resources\Event\DeviceDeleted::from_json($json), + \Seam\Resources\Event\EventType::DEVICE_THIRD_PARTY_INTEGRATION_DETECTED + => \Seam\Resources\Event\DeviceThirdPartyIntegrationDetected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_THIRD_PARTY_INTEGRATION_NO_LONGER_DETECTED + => \Seam\Resources\Event\DeviceThirdPartyIntegrationNoLongerDetected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_SALTO_PRIVACY_MODE_ACTIVATED + => \Seam\Resources\Event\DeviceSaltoPrivacyModeActivated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_SALTO_PRIVACY_MODE_DEACTIVATED + => \Seam\Resources\Event\DeviceSaltoPrivacyModeDeactivated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_CONNECTION_BECAME_FLAKY + => \Seam\Resources\Event\DeviceConnectionBecameFlaky::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_CONNECTION_STABILIZED + => \Seam\Resources\Event\DeviceConnectionStabilized::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_ERROR_SUBSCRIPTION_REQUIRED + => \Seam\Resources\Event\DeviceErrorSubscriptionRequired::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_ERROR_SUBSCRIPTION_REQUIRED_RESOLVED + => \Seam\Resources\Event\DeviceErrorSubscriptionRequiredResolved::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_ACCESSORY_KEYPAD_CONNECTED + => \Seam\Resources\Event\DeviceAccessoryKeypadConnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_ACCESSORY_KEYPAD_DISCONNECTED + => \Seam\Resources\Event\DeviceAccessoryKeypadDisconnected::from_json( + $json, + ), + \Seam\Resources\Event\EventType::NOISE_SENSOR_NOISE_THRESHOLD_TRIGGERED + => \Seam\Resources\Event\NoiseSensorNoiseThresholdTriggered::from_json( + $json, + ), + \Seam\Resources\Event\EventType::LOCK_LOCKED + => \Seam\Resources\Event\LockLocked::from_json($json), + \Seam\Resources\Event\EventType::LOCK_UNLOCKED + => \Seam\Resources\Event\LockUnlocked::from_json($json), + \Seam\Resources\Event\EventType::LOCK_ACCESS_DENIED + => \Seam\Resources\Event\LockAccessDenied::from_json($json), + \Seam\Resources\Event\EventType::THERMOSTAT_CLIMATE_PRESET_ACTIVATED + => \Seam\Resources\Event\ThermostatClimatePresetActivated::from_json( + $json, + ), + \Seam\Resources\Event\EventType::THERMOSTAT_MANUALLY_ADJUSTED + => \Seam\Resources\Event\ThermostatManuallyAdjusted::from_json( + $json, + ), + \Seam\Resources\Event\EventType::THERMOSTAT_TEMPERATURE_THRESHOLD_EXCEEDED + => \Seam\Resources\Event\ThermostatTemperatureThresholdExceeded::from_json( + $json, + ), + \Seam\Resources\Event\EventType::THERMOSTAT_TEMPERATURE_THRESHOLD_NO_LONGER_EXCEEDED + => \Seam\Resources\Event\ThermostatTemperatureThresholdNoLongerExceeded::from_json( + $json, + ), + \Seam\Resources\Event\EventType::THERMOSTAT_TEMPERATURE_REACHED_SET_POINT + => \Seam\Resources\Event\ThermostatTemperatureReachedSetPoint::from_json( + $json, + ), + \Seam\Resources\Event\EventType::THERMOSTAT_TEMPERATURE_CHANGED + => \Seam\Resources\Event\ThermostatTemperatureChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::DEVICE_NAME_CHANGED + => \Seam\Resources\Event\DeviceNameChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::CAMERA_ACTIVATED + => \Seam\Resources\Event\CameraActivated::from_json($json), + \Seam\Resources\Event\EventType::DEVICE_DOORBELL_RANG + => \Seam\Resources\Event\DeviceDoorbellRang::from_json( + $json, + ), + \Seam\Resources\Event\EventType::PHONE_DEACTIVATED + => \Seam\Resources\Event\PhoneDeactivated::from_json($json), + \Seam\Resources\Event\EventType::SPACE_DEVICE_MEMBERSHIP_CHANGED + => \Seam\Resources\Event\SpaceDeviceMembershipChanged::from_json( + $json, + ), + \Seam\Resources\Event\EventType::SPACE_CREATED + => \Seam\Resources\Event\SpaceCreated::from_json($json), + \Seam\Resources\Event\EventType::SPACE_DELETED + => \Seam\Resources\Event\SpaceDeleted::from_json($json), + default => new self( + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which the event was created. + */ + public string|null $created_at, + /** + * ID of the event. + */ + public string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + public string|null $event_type, + /** + * Date and time at which the event occurred. + */ + public string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + public string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + public string|null $event_description = null, + ) {} + } +} + +namespace Seam\Resources\Event { + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was created. + */ + final class AccessCodeCreated extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessCodeCreated|null + { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was changed. + */ + final class AccessCodeChanged extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessCodeChanged|null + { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + change_reason: $json->change_reason ?? null, + changed_properties: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeChanged\ChangedProperties::from_json( + $c, + ), + $json->changed_properties ?? [], + ), + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable reason for the change (e.g. `ongoing code auto-renewed`). + */ + public string|null $change_reason = null, + /** + * List of properties that changed on the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeChanged\ChangedProperties>|null + */ + public array|null $changed_properties = null, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The name of an [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was changed on the device. + */ + final class AccessCodeNameChanged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeNameChanged|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + description: $json->description ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + from: isset($json->from) + ? \Seam\Resources\Event\AccessCodeNameChanged\From::from_json( + $json->from, + ) + : null, + occurred_at: $json->occurred_at ?? null, + to: isset($json->to) + ? \Seam\Resources\Event\AccessCodeNameChanged\To::from_json( + $json->to, + ) + : null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Human-readable description of the change and its source. + */ + public string|null $description, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Previous access code name configuration. + */ + public \Seam\Resources\Event\AccessCodeNameChanged\From|null $from, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * New access code name configuration. + */ + public \Seam\Resources\Event\AccessCodeNameChanged\To|null $to, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The pin code of an [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was changed on the device. + */ + final class AccessCodeCodeChanged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeCodeChanged|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + description: $json->description ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + from: isset($json->from) + ? \Seam\Resources\Event\AccessCodeCodeChanged\From::from_json( + $json->from, + ) + : null, + occurred_at: $json->occurred_at ?? null, + to: isset($json->to) + ? \Seam\Resources\Event\AccessCodeCodeChanged\To::from_json( + $json->to, + ) + : null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Human-readable description of the change and its source. + */ + public string|null $description, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Previous pin code configuration. + */ + public \Seam\Resources\Event\AccessCodeCodeChanged\From|null $from, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * New pin code configuration. + */ + public \Seam\Resources\Event\AccessCodeCodeChanged\To|null $to, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The time frame of an [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was changed on the device. + */ + final class AccessCodeTimeFrameChanged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeTimeFrameChanged|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + description: $json->description ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + from: isset($json->from) + ? \Seam\Resources\Event\AccessCodeTimeFrameChanged\From::from_json( + $json->from, + ) + : null, + occurred_at: $json->occurred_at ?? null, + to: isset($json->to) + ? \Seam\Resources\Event\AccessCodeTimeFrameChanged\To::from_json( + $json->to, + ) + : null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Human-readable description of the change and its source. + */ + public string|null $description, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Previous time frame configuration. + */ + public \Seam\Resources\Event\AccessCodeTimeFrameChanged\From|null $from, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * New time frame configuration. + */ + public \Seam\Resources\Event\AccessCodeTimeFrameChanged\To|null $to, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Mutations were requested on an [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). This event fires at request time, before the change is confirmed on the device. + */ + final class AccessCodeMutationsRequested extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeMutationsRequested|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + requested_mutations: array_map( + fn( + $r, + ) => \Seam\Resources\Event\AccessCodeMutationsRequested\RequestedMutations::from_json( + $r, + ), + $json->requested_mutations ?? [], + ), + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Array of mutations requested on the access code, each containing the mutation type and from/to values. + * + * @var list<\Seam\Resources\Event\AccessCodeMutationsRequested\RequestedMutations> + */ + public array $requested_mutations, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was [scheduled natively](https://docs.seam.co/low-level-apis/smart-locks/access-codes#native-scheduling) on a device. + */ + final class AccessCodeScheduledOnDevice extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeScheduledOnDevice|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + code: $json->code ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * Code for the affected access code. + */ + public string|null $code, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was set on a device. + */ + final class AccessCodeSetOnDevice extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeSetOnDevice|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + code: $json->code ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * Code for the affected access code. + */ + public string|null $code, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was removed from a device. + */ + final class AccessCodeRemovedFromDevice extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeRemovedFromDevice|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * There was an unusually long delay in setting an [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) on a device. + */ + final class AccessCodeDelayInSettingOnDevice extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeDelayInSettingOnDevice|null { + if (!$json) { + return null; + } + return new self( + access_code_errors: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\AccessCodeErrors::from_json( + $a, + ), + $json->access_code_errors ?? [], + ), + access_code_id: $json->access_code_id ?? null, + access_code_warnings: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\AccessCodeWarnings::from_json( + $a, + ), + $json->access_code_warnings ?? [], + ), + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\AccessCodeErrors> + */ + public array $access_code_errors, + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * Warnings associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\AccessCodeWarnings> + */ + public array $access_code_warnings, + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInSettingOnDevice\DeviceWarnings> + */ + public array $device_warnings, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) failed to be set on a device. + */ + final class AccessCodeFailedToSetOnDevice extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeFailedToSetOnDevice|null { + if (!$json) { + return null; + } + return new self( + access_code_errors: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeFailedToSetOnDevice\AccessCodeErrors::from_json( + $a, + ), + $json->access_code_errors ?? [], + ), + access_code_id: $json->access_code_id ?? null, + access_code_warnings: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeFailedToSetOnDevice\AccessCodeWarnings::from_json( + $a, + ), + $json->access_code_warnings ?? [], + ), + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeFailedToSetOnDevice\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeFailedToSetOnDevice\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeFailedToSetOnDevice\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeFailedToSetOnDevice\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToSetOnDevice\AccessCodeErrors> + */ + public array $access_code_errors, + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * Warnings associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToSetOnDevice\AccessCodeWarnings> + */ + public array $access_code_warnings, + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToSetOnDevice\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToSetOnDevice\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToSetOnDevice\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToSetOnDevice\DeviceWarnings> + */ + public array $device_warnings, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was deleted. + */ + final class AccessCodeDeleted extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessCodeDeleted|null + { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + code: $json->code ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * Code for the affected access code. + */ + public string|null $code, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * There was an unusually long delay in removing an [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) from a device. + */ + final class AccessCodeDelayInRemovingFromDevice extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeDelayInRemovingFromDevice|null { + if (!$json) { + return null; + } + return new self( + access_code_errors: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\AccessCodeErrors::from_json( + $a, + ), + $json->access_code_errors ?? [], + ), + access_code_id: $json->access_code_id ?? null, + access_code_warnings: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\AccessCodeWarnings::from_json( + $a, + ), + $json->access_code_warnings ?? [], + ), + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\AccessCodeErrors> + */ + public array $access_code_errors, + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * Warnings associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\AccessCodeWarnings> + */ + public array $access_code_warnings, + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice\DeviceWarnings> + */ + public array $device_warnings, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) failed to be removed from a device. + */ + final class AccessCodeFailedToRemoveFromDevice extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeFailedToRemoveFromDevice|null { + if (!$json) { + return null; + } + return new self( + access_code_errors: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\AccessCodeErrors::from_json( + $a, + ), + $json->access_code_errors ?? [], + ), + access_code_id: $json->access_code_id ?? null, + access_code_warnings: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\AccessCodeWarnings::from_json( + $a, + ), + $json->access_code_warnings ?? [], + ), + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\AccessCodeErrors> + */ + public array $access_code_errors, + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * Warnings associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\AccessCodeWarnings> + */ + public array $access_code_warnings, + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice\DeviceWarnings> + */ + public array $device_warnings, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was modified outside of Seam. + */ + final class AccessCodeModifiedExternalToSeam extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeModifiedExternalToSeam|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was deleted outside of Seam. + */ + final class AccessCodeDeletedExternalToSeam extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeDeletedExternalToSeam|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [backup access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) was pulled from the backup access code pool and set on a device. + */ + final class AccessCodeBackupAccessCodePulled extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeBackupAccessCodePulled|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + backup_access_code_id: $json->backup_access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the backup access code that was pulled from the pool. + */ + public string|null $backup_access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [unmanaged access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) was converted successfully to a managed access code. + */ + final class AccessCodeUnmanagedConvertedToManaged extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeUnmanagedConvertedToManaged|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [unmanaged access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) failed to be converted to a managed access code. + */ + final class AccessCodeUnmanagedFailedToConvertToManaged extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeUnmanagedFailedToConvertToManaged|null { + if (!$json) { + return null; + } + return new self( + access_code_errors: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\AccessCodeErrors::from_json( + $a, + ), + $json->access_code_errors ?? [], + ), + access_code_id: $json->access_code_id ?? null, + access_code_warnings: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\AccessCodeWarnings::from_json( + $a, + ), + $json->access_code_warnings ?? [], + ), + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\AccessCodeErrors> + */ + public array $access_code_errors, + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * Warnings associated with the access code. + * + * @var list<\Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\AccessCodeWarnings> + */ + public array $access_code_warnings, + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged\DeviceWarnings> + */ + public array $device_warnings, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [unmanaged access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) was created on a device. + */ + final class AccessCodeUnmanagedCreated extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeUnmanagedCreated|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [unmanaged access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) was removed from a device. + */ + final class AccessCodeUnmanagedRemoved extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessCodeUnmanagedRemoved|null { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access code. + */ + public string|null $access_code_id, + /** + * ID of the connected account associated with the affected access code. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the device associated with the affected access code. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An Access Grant was created. + */ + final class AccessGrantCreated extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessGrantCreated|null + { + if (!$json) { + return null; + } + return new self( + access_grant_id: $json->access_grant_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Access Grant. + */ + public string|null $access_grant_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An Access Grant was deleted. + */ + final class AccessGrantDeleted extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessGrantDeleted|null + { + if (!$json) { + return null; + } + return new self( + access_grant_id: $json->access_grant_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Access Grant. + */ + public string|null $access_grant_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * All access requested for an Access Grant was successfully granted. + */ + final class AccessGrantAccessGrantedToAllDoors extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessGrantAccessGrantedToAllDoors|null { + if (!$json) { + return null; + } + return new self( + access_grant_id: $json->access_grant_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Access Grant. + */ + public string|null $access_grant_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Access requested as part of an Access Grant to a particular door was successfully granted. + */ + final class AccessGrantAccessGrantedToDoor extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessGrantAccessGrantedToDoor|null { + if (!$json) { + return null; + } + return new self( + access_grant_id: $json->access_grant_id ?? null, + acs_entrance_id: $json->acs_entrance_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Access Grant. + */ + public string|null $access_grant_id, + /** + * ID of the affected [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public string|null $acs_entrance_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Access to a particular door that was requested as part of an Access Grant was lost. + */ + final class AccessGrantAccessToDoorLost extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessGrantAccessToDoorLost|null { + if (!$json) { + return null; + } + return new self( + access_grant_id: $json->access_grant_id ?? null, + acs_entrance_id: $json->acs_entrance_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Access Grant. + */ + public string|null $access_grant_id, + /** + * ID of the affected [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). + */ + public string|null $acs_entrance_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An Access Grant's start or end time was changed. + */ + final class AccessGrantAccessTimesChanged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessGrantAccessTimesChanged|null { + if (!$json) { + return null; + } + return new self( + access_grant_id: $json->access_grant_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_key: $json->access_grant_key ?? null, + ends_at: $json->ends_at ?? null, + event_description: $json->event_description ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Access Grant. + */ + public string|null $access_grant_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Key of the affected Access Grant (if present). + */ + public string|null $access_grant_key = null, + /** + * The new end time for the access grant. + */ + public string|null $ends_at = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * The new start time for the access grant. + */ + public string|null $starts_at = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * One or more requested access methods could not be created for an Access Grant. + */ + final class AccessGrantCouldNotCreateRequestedAccessMethods extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessGrantCouldNotCreateRequestedAccessMethods|null { + if (!$json) { + return null; + } + return new self( + access_grant_id: $json->access_grant_id ?? null, + created_at: $json->created_at ?? null, + error_message: $json->error_message ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + missing_device_ids: $json->missing_device_ids ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Access Grant. + */ + public string|null $access_grant_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Description of why the access methods could not be created. + */ + public string|null $error_message, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * IDs of the devices that did not receive a requested access method. Use these to identify which specific devices failed without having to fetch the Access Grant. + * + * @var list|null + */ + public array|null $missing_device_ids = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An access method was issued. + */ + final class AccessMethodIssued extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessMethodIssued|null + { + if (!$json) { + return null; + } + return new self( + access_grant_ids: $json->access_grant_ids ?? null, + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_keys: $json->access_grant_keys ?? null, + code: $json->code ?? null, + event_description: $json->event_description ?? null, + is_backup_code: $json->is_backup_code ?? null, + ); + } + + public function __construct( + /** + * IDs of the access grants associated with this access method. + * + * @var list|null + */ + public array|null $access_grant_ids, + /** + * ID of the affected access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Keys of the access grants associated with this access method (if present). + * + * @var list|null + */ + public array|null $access_grant_keys = null, + /** + * The actual PIN code for code access methods (only present when mode is 'code'). + */ + public string|null $code = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Indicates whether the code is a backup code (only present when mode is 'code' and a backup code was used). + */ + public bool|null $is_backup_code = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An access method was revoked. + */ + final class AccessMethodRevoked extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessMethodRevoked|null + { + if (!$json) { + return null; + } + return new self( + access_grant_ids: $json->access_grant_ids ?? null, + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_keys: $json->access_grant_keys ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * IDs of the access grants associated with this access method. + * + * @var list|null + */ + public array|null $access_grant_ids, + /** + * ID of the affected access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Keys of the access grants associated with this access method (if present). + * + * @var list|null + */ + public array|null $access_grant_keys = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An access method representing a physical card requires encoding. + */ + final class AccessMethodCardEncodingRequired extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessMethodCardEncodingRequired|null { + if (!$json) { + return null; + } + return new self( + access_grant_ids: $json->access_grant_ids ?? null, + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_keys: $json->access_grant_keys ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * IDs of the access grants associated with this access method. + * + * @var list|null + */ + public array|null $access_grant_ids, + /** + * ID of the affected access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Keys of the access grants associated with this access method (if present). + * + * @var list|null + */ + public array|null $access_grant_keys = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An access method was deleted. + */ + final class AccessMethodDeleted extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessMethodDeleted|null + { + if (!$json) { + return null; + } + return new self( + access_grant_ids: $json->access_grant_ids ?? null, + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_keys: $json->access_grant_keys ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * IDs of the access grants associated with this access method. + * + * @var list|null + */ + public array|null $access_grant_ids, + /** + * ID of the affected access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Keys of the access grants associated with this access method (if present). + * + * @var list|null + */ + public array|null $access_grant_keys = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An access method was reissued. + */ + final class AccessMethodReissued extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessMethodReissued|null + { + if (!$json) { + return null; + } + return new self( + access_grant_ids: $json->access_grant_ids ?? null, + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_keys: $json->access_grant_keys ?? null, + code: $json->code ?? null, + event_description: $json->event_description ?? null, + is_backup_code: $json->is_backup_code ?? null, + ); + } + + public function __construct( + /** + * IDs of the access grants associated with this access method. + * + * @var list|null + */ + public array|null $access_grant_ids, + /** + * ID of the affected access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Keys of the access grants associated with this access method (if present). + * + * @var list|null + */ + public array|null $access_grant_keys = null, + /** + * The actual PIN code for code access methods (only present when mode is 'code'). + */ + public string|null $code = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Indicates whether the code is a backup code (only present when mode is 'code' and a backup code was used). + */ + public bool|null $is_backup_code = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An access method was created. + */ + final class AccessMethodCreated extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AccessMethodCreated|null + { + if (!$json) { + return null; + } + return new self( + access_grant_ids: $json->access_grant_ids ?? null, + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_keys: $json->access_grant_keys ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * IDs of the access grants associated with this access method. + * + * @var list|null + */ + public array|null $access_grant_ids, + /** + * ID of the affected access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Keys of the access grants associated with this access method (if present). + * + * @var list|null + */ + public array|null $access_grant_keys = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Seam has not yet issued this access method, even though its access grant is about to begin, so access may not be ready when the recipient arrives. Seam is still attempting to issue it, and the accompanying `delay_in_issuing` warning clears automatically once issuance succeeds. + */ + final class AccessMethodDelayInIssuing extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessMethodDelayInIssuing|null { + if (!$json) { + return null; + } + return new self( + access_grant_ids: $json->access_grant_ids ?? null, + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_keys: $json->access_grant_keys ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * IDs of the access grants associated with this access method. + * + * @var list|null + */ + public array|null $access_grant_ids, + /** + * ID of the affected access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Keys of the access grants associated with this access method (if present). + * + * @var list|null + */ + public array|null $access_grant_keys = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Seam was unable to issue this access method before its access grant started, so the recipient may be unable to access the space. This usually points to a problem that needs attention, such as an offline or disconnected device. Seam keeps retrying, and the accompanying `failed_to_issue` error clears automatically if the access method is eventually issued. + */ + final class AccessMethodFailedToIssue extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AccessMethodFailedToIssue|null { + if (!$json) { + return null; + } + return new self( + access_grant_ids: $json->access_grant_ids ?? null, + access_method_id: $json->access_method_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_grant_keys: $json->access_grant_keys ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * IDs of the access grants associated with this access method. + * + * @var list|null + */ + public array|null $access_grant_ids, + /** + * ID of the affected access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Keys of the access grants associated with this access method (if present). + * + * @var list|null + */ + public array|null $access_grant_keys = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system](https://docs.seam.co/low-level-apis/access-systems) was connected. + */ + final class AcsSystemConnected extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsSystemConnected|null + { + if (!$json) { + return null; + } + return new self( + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system](https://docs.seam.co/low-level-apis/access-systems) was added. + */ + final class AcsSystemAdded extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsSystemAdded|null + { + if (!$json) { + return null; + } + return new self( + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system](https://docs.seam.co/low-level-apis/access-systems) was disconnected. + */ + final class AcsSystemDisconnected extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AcsSystemDisconnected|null { + if (!$json) { + return null; + } + return new self( + acs_system_errors: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AcsSystemDisconnected\AcsSystemErrors::from_json( + $a, + ), + $json->acs_system_errors ?? [], + ), + acs_system_id: $json->acs_system_id ?? null, + acs_system_warnings: array_map( + fn( + $a, + ) => \Seam\Resources\Event\AcsSystemDisconnected\AcsSystemWarnings::from_json( + $a, + ), + $json->acs_system_warnings ?? [], + ), + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AcsSystemDisconnected\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\AcsSystemDisconnected\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the access control system. + * + * @var list<\Seam\Resources\Event\AcsSystemDisconnected\AcsSystemErrors> + */ + public array $acs_system_errors, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Warnings associated with the access control system. + * + * @var list<\Seam\Resources\Event\AcsSystemDisconnected\AcsSystemWarnings> + */ + public array $acs_system_warnings, + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\AcsSystemDisconnected\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\AcsSystemDisconnected\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was deleted. + */ + final class AcsCredentialDeleted extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsCredentialDeleted|null + { + if (!$json) { + return null; + } + return new self( + acs_credential_id: $json->acs_credential_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected credential. + */ + public string|null $acs_credential_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was issued. + */ + final class AcsCredentialIssued extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsCredentialIssued|null + { + if (!$json) { + return null; + } + return new self( + acs_credential_id: $json->acs_credential_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected credential. + */ + public string|null $acs_credential_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was reissued. + */ + final class AcsCredentialReissued extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AcsCredentialReissued|null { + if (!$json) { + return null; + } + return new self( + acs_credential_id: $json->acs_credential_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected credential. + */ + public string|null $acs_credential_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) was invalidated. That is, the credential cannot be used anymore. + */ + final class AcsCredentialInvalidated extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AcsCredentialInvalidated|null { + if (!$json) { + return null; + } + return new self( + acs_credential_id: $json->acs_credential_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected credential. + */ + public string|null $acs_credential_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was created. + */ + final class AcsUserCreated extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsUserCreated|null + { + if (!$json) { + return null; + } + return new self( + acs_system_id: $json->acs_system_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * ID of the affected access system user. + */ + public string|null $acs_user_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) was deleted. + */ + final class AcsUserDeleted extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsUserDeleted|null + { + if (!$json) { + return null; + } + return new self( + acs_system_id: $json->acs_system_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * ID of the affected access system user. + */ + public string|null $acs_user_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners) was added. + */ + final class AcsEncoderAdded extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsEncoderAdded|null + { + if (!$json) { + return null; + } + return new self( + acs_encoder_id: $json->acs_encoder_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected encoder. + */ + public string|null $acs_encoder_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system encoder](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners) was removed. + */ + final class AcsEncoderRemoved extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsEncoderRemoved|null + { + if (!$json) { + return null; + } + return new self( + acs_encoder_id: $json->acs_encoder_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected encoder. + */ + public string|null $acs_encoder_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An ACS access group was deleted. + */ + final class AcsAccessGroupDeleted extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): AcsAccessGroupDeleted|null { + if (!$json) { + return null; + } + return new self( + acs_access_group_id: $json->acs_access_group_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected access group. + */ + public string|null $acs_access_group_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) was added. + */ + final class AcsEntranceAdded extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsEntranceAdded|null + { + if (!$json) { + return null; + } + return new self( + acs_entrance_id: $json->acs_entrance_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected entrance. + */ + public string|null $acs_entrance_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [access system entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) was removed. + */ + final class AcsEntranceRemoved extends \Seam\Resources\Event + { + public static function from_json(mixed $json): AcsEntranceRemoved|null + { + if (!$json) { + return null; + } + return new self( + acs_entrance_id: $json->acs_entrance_id ?? null, + acs_system_id: $json->acs_system_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected entrance. + */ + public string|null $acs_entrance_id, + /** + * ID of the access system. + */ + public string|null $acs_system_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account. + */ + public string|null $connected_account_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A client session was deleted. + */ + final class ClientSessionDeleted extends \Seam\Resources\Event + { + public static function from_json(mixed $json): ClientSessionDeleted|null + { + if (!$json) { + return null; + } + return new self( + client_session_id: $json->client_session_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected client session. + */ + public string|null $client_session_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A connected account was connected for the first time or was reconnected after being disconnected. + */ + final class ConnectedAccountConnected extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectedAccountConnected|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connect_webview_id: $json->connect_webview_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected connected account. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the Connect Webview associated with the event. + */ + public string|null $connect_webview_id = null, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with this connected account, if any. + */ + public string|null $customer_key = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A connected account was created. + */ + final class ConnectedAccountCreated extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectedAccountCreated|null { + if (!$json) { + return null; + } + return new self( + connect_webview_id: $json->connect_webview_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the Connect Webview associated with the event. + */ + public string|null $connect_webview_id, + /** + * ID of the affected connected account. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A connected account had a successful login using a Connect Webview. + */ + final class ConnectedAccountSuccessfulLogin extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectedAccountSuccessfulLogin|null { + if (!$json) { + return null; + } + return new self( + connect_webview_id: $json->connect_webview_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the Connect Webview associated with the event. + */ + public string|null $connect_webview_id, + /** + * ID of the affected connected account. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A connected account was disconnected. + */ + final class ConnectedAccountDisconnected extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectedAccountDisconnected|null { + if (!$json) { + return null; + } + return new self( + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\ConnectedAccountDisconnected\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\ConnectedAccountDisconnected\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\ConnectedAccountDisconnected\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the affected connected account. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\ConnectedAccountDisconnected\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A connected account completed the first sync with Seam, and the corresponding devices or systems are now available. + */ + final class ConnectedAccountCompletedFirstSync extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectedAccountCompletedFirstSync|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected connected account. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A connected account was deleted. + */ + final class ConnectedAccountDeleted extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectedAccountDeleted|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected connected account. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with this connected account, if any. + */ + public string|null $customer_key = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A connected account completed the first sync after reconnection with Seam, and the corresponding devices or systems are now available. + */ + final class ConnectedAccountCompletedFirstSyncAfterReconnection extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectedAccountCompletedFirstSyncAfterReconnection|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected connected account. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A connected account requires reauthorization using a new Connect Webview. The account is still connected, but cannot access new features. Delaying reauthorization too long will eventually cause the Connected Account to become disconnected. + */ + final class ConnectedAccountReauthorizationRequested extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectedAccountReauthorizationRequested|null { + if (!$json) { + return null; + } + return new self( + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\ConnectedAccountReauthorizationRequested\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\ConnectedAccountReauthorizationRequested\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\ConnectedAccountReauthorizationRequested\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the affected connected account. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\ConnectedAccountReauthorizationRequested\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A lock door action attempt succeeded. + */ + final class ActionAttemptLockDoorSucceeded extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ActionAttemptLockDoorSucceeded|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + status: $json->status ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected action attempt. + */ + public string|null $action_attempt_id, + /** + * Type of the action. + */ + public string|null $action_type, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Status of the action. + */ + public string|null $status, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account associated with the action attempt, if applicable. + */ + public string|null $connected_account_id = null, + /** + * ID of the device associated with the action attempt, if applicable. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A lock door action attempt failed. + */ + final class ActionAttemptLockDoorFailed extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ActionAttemptLockDoorFailed|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + status: $json->status ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected action attempt. + */ + public string|null $action_attempt_id, + /** + * Type of the action. + */ + public string|null $action_type, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Status of the action. + */ + public string|null $status, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account associated with the action attempt, if applicable. + */ + public string|null $connected_account_id = null, + /** + * ID of the device associated with the action attempt, if applicable. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An unlock door action attempt succeeded. + */ + final class ActionAttemptUnlockDoorSucceeded extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ActionAttemptUnlockDoorSucceeded|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + status: $json->status ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected action attempt. + */ + public string|null $action_attempt_id, + /** + * Type of the action. + */ + public string|null $action_type, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Status of the action. + */ + public string|null $status, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account associated with the action attempt, if applicable. + */ + public string|null $connected_account_id = null, + /** + * ID of the device associated with the action attempt, if applicable. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An unlock door action attempt failed. + */ + final class ActionAttemptUnlockDoorFailed extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ActionAttemptUnlockDoorFailed|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + status: $json->status ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected action attempt. + */ + public string|null $action_attempt_id, + /** + * Type of the action. + */ + public string|null $action_type, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Status of the action. + */ + public string|null $status, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account associated with the action attempt, if applicable. + */ + public string|null $connected_account_id = null, + /** + * ID of the device associated with the action attempt, if applicable. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A simulate keypad code entry action attempt succeeded. + */ + final class ActionAttemptSimulateKeypadCodeEntrySucceeded extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ActionAttemptSimulateKeypadCodeEntrySucceeded|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + status: $json->status ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected action attempt. + */ + public string|null $action_attempt_id, + /** + * Type of the action. + */ + public string|null $action_type, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Status of the action. + */ + public string|null $status, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account associated with the action attempt, if applicable. + */ + public string|null $connected_account_id = null, + /** + * ID of the device associated with the action attempt, if applicable. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A simulate keypad code entry action attempt failed. + */ + final class ActionAttemptSimulateKeypadCodeEntryFailed extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ActionAttemptSimulateKeypadCodeEntryFailed|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + status: $json->status ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected action attempt. + */ + public string|null $action_attempt_id, + /** + * Type of the action. + */ + public string|null $action_type, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Status of the action. + */ + public string|null $status, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account associated with the action attempt, if applicable. + */ + public string|null $connected_account_id = null, + /** + * ID of the device associated with the action attempt, if applicable. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A simulate manual lock via keypad action attempt succeeded. + */ + final class ActionAttemptSimulateManualLockViaKeypadSucceeded extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ActionAttemptSimulateManualLockViaKeypadSucceeded|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + status: $json->status ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected action attempt. + */ + public string|null $action_attempt_id, + /** + * Type of the action. + */ + public string|null $action_type, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Status of the action. + */ + public string|null $status, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account associated with the action attempt, if applicable. + */ + public string|null $connected_account_id = null, + /** + * ID of the device associated with the action attempt, if applicable. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A simulate manual lock via keypad action attempt failed. + */ + final class ActionAttemptSimulateManualLockViaKeypadFailed extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ActionAttemptSimulateManualLockViaKeypadFailed|null { + if (!$json) { + return null; + } + return new self( + action_attempt_id: $json->action_attempt_id ?? null, + action_type: $json->action_type ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + status: $json->status ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected action attempt. + */ + public string|null $action_attempt_id, + /** + * Type of the action. + */ + public string|null $action_type, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Status of the action. + */ + public string|null $status, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the connected account associated with the action attempt, if applicable. + */ + public string|null $connected_account_id = null, + /** + * ID of the device associated with the action attempt, if applicable. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A Connect Webview login succeeded. + */ + final class ConnectWebviewLoginSucceeded extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectWebviewLoginSucceeded|null { + if (!$json) { + return null; + } + return new self( + connect_webview_id: $json->connect_webview_id ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Connect Webview. + */ + public string|null $connect_webview_id, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account; present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with this connect webview, if any. + */ + public string|null $customer_key = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A Connect Webview login failed. + */ + final class ConnectWebviewLoginFailed extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ConnectWebviewLoginFailed|null { + if (!$json) { + return null; + } + return new self( + connect_webview_id: $json->connect_webview_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the affected Connect Webview. + */ + public string|null $connect_webview_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The status of a device changed from offline to online. That is, the `device.properties.online` property changed from `false` to `true`. Note that some devices operate entirely in offline mode, so Seam never emits a `device.connected` event for these devices. + */ + final class DeviceConnected extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceConnected|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A device was added to Seam or was re-added to Seam after having been removed. + */ + final class DeviceAdded extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceAdded|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A managed device was successfully converted to an [unmanaged device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices). + */ + final class DeviceConvertedToUnmanaged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceConvertedToUnmanaged|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An [unmanaged device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices) was successfully converted to a managed device. + */ + final class DeviceUnmanagedConvertedToManaged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceUnmanagedConvertedToManaged|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The status of an [unmanaged device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices) changed from offline to online. That is, the `device.properties.online` property changed from `false` to `true`. + */ + final class DeviceUnmanagedConnected extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceUnmanagedConnected|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The status of a device changed from online to offline. That is, the `device.properties.online` property changed from `true` to `false`. + */ + final class DeviceDisconnected extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceDisconnected|null + { + if (!$json) { + return null; + } + return new self( + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceDisconnected\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceDisconnected\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceDisconnected\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceDisconnected\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + error_code: $json->error_code ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceDisconnected\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceDisconnected\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceDisconnected\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceDisconnected\DeviceWarnings> + */ + public array $device_warnings, + /** + * Error code associated with the disconnection event, if any. + * + * @var value-of<\Seam\Resources\Event\DeviceDisconnected\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The status of an [unmanaged device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices) changed from online to offline. That is, the `device.properties.online` property changed from `true` to `false`. + */ + final class DeviceUnmanagedDisconnected extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceUnmanagedDisconnected|null { + if (!$json) { + return null; + } + return new self( + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceUnmanagedDisconnected\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceUnmanagedDisconnected\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceUnmanagedDisconnected\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceUnmanagedDisconnected\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + error_code: $json->error_code ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceUnmanagedDisconnected\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceUnmanagedDisconnected\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceUnmanagedDisconnected\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceUnmanagedDisconnected\DeviceWarnings> + */ + public array $device_warnings, + /** + * Error code associated with the disconnection event, if any. + * + * @var value-of<\Seam\Resources\Event\DeviceUnmanagedDisconnected\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A device detected that it was tampered with, for example, opened or moved. + */ + final class DeviceTampered extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceTampered|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A device battery level dropped below the low threshold. + */ + final class DeviceLowBattery extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceLowBattery|null + { + if (!$json) { + return null; + } + return new self( + battery_level: $json->battery_level ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Number in the range 0 to 1.0 indicating the amount of battery in the affected device, as reported by the device. + */ + public float|null $battery_level, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A device battery status changed since the last `battery_status_changed` event. + */ + final class DeviceBatteryStatusChanged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceBatteryStatusChanged|null { + if (!$json) { + return null; + } + return new self( + battery_level: $json->battery_level ?? null, + battery_status: $json->battery_status ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Number in the range 0 to 1.0 indicating the amount of battery in the affected device, as reported by the device. + */ + public float|null $battery_level, + /** + * Battery status of the affected device, calculated from the numeric `battery_level` value. + * + * @var value-of<\Seam\Resources\Event\DeviceBatteryStatusChanged\BatteryStatus>|string|null + */ + public string|null $battery_status, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A device was removed externally from the connected account. + */ + final class DeviceRemoved extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceRemoved|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A device was deleted. + */ + final class DeviceDeleted extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceDeleted|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + device_name: $json->device_name ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Name of the deleted device, captured at deletion time. The device record no longer exists when this event fires, so the name is preserved here. Null when the device had no resolvable name. + */ + public string|null $device_name = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Seam detected that a device is using a third-party integration that will interfere with Seam device management. + */ + final class DeviceThirdPartyIntegrationDetected extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceThirdPartyIntegrationDetected|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Seam detected that a device is no longer using a third-party integration that was interfering with Seam device management. + */ + final class DeviceThirdPartyIntegrationNoLongerDetected extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceThirdPartyIntegrationNoLongerDetected|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [Salto device](https://docs.seam.co/device-and-system-integration-guides/salto-locks) activated privacy mode. + */ + final class DeviceSaltoPrivacyModeActivated extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceSaltoPrivacyModeActivated|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [Salto device](https://docs.seam.co/device-and-system-integration-guides/salto-locks) deactivated privacy mode. + */ + final class DeviceSaltoPrivacyModeDeactivated extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceSaltoPrivacyModeDeactivated|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Seam detected a flaky device connection. + */ + final class DeviceConnectionBecameFlaky extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceConnectionBecameFlaky|null { + if (!$json) { + return null; + } + return new self( + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceConnectionBecameFlaky\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceConnectionBecameFlaky\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceConnectionBecameFlaky\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceConnectionBecameFlaky\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceConnectionBecameFlaky\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceConnectionBecameFlaky\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceConnectionBecameFlaky\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceConnectionBecameFlaky\DeviceWarnings> + */ + public array $device_warnings, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Seam detected that a previously-flaky device connection stabilized. + */ + final class DeviceConnectionStabilized extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceConnectionStabilized|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A third-party subscription is required to use all device features. + */ + final class DeviceErrorSubscriptionRequired extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceErrorSubscriptionRequired|null { + if (!$json) { + return null; + } + return new self( + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceErrorSubscriptionRequired\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceErrorSubscriptionRequired\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceErrorSubscriptionRequired\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceErrorSubscriptionRequired\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceErrorSubscriptionRequired\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceErrorSubscriptionRequired\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceErrorSubscriptionRequired\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceErrorSubscriptionRequired\DeviceWarnings> + */ + public array $device_warnings, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A third-party subscription is active or no longer required to use all device features. + */ + final class DeviceErrorSubscriptionRequiredResolved extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceErrorSubscriptionRequiredResolved|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An accessory keypad was connected to a device. + */ + final class DeviceAccessoryKeypadConnected extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceAccessoryKeypadConnected|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * An accessory keypad was disconnected from a device. + */ + final class DeviceAccessoryKeypadDisconnected extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): DeviceAccessoryKeypadDisconnected|null { + if (!$json) { + return null; + } + return new self( + connected_account_errors: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceAccessoryKeypadDisconnected\ConnectedAccountErrors::from_json( + $c, + ), + $json->connected_account_errors ?? [], + ), + connected_account_id: $json->connected_account_id ?? null, + connected_account_warnings: array_map( + fn( + $c, + ) => \Seam\Resources\Event\DeviceAccessoryKeypadDisconnected\ConnectedAccountWarnings::from_json( + $c, + ), + $json->connected_account_warnings ?? [], + ), + created_at: $json->created_at ?? null, + device_errors: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceAccessoryKeypadDisconnected\DeviceErrors::from_json( + $d, + ), + $json->device_errors ?? [], + ), + device_id: $json->device_id ?? null, + device_warnings: array_map( + fn( + $d, + ) => \Seam\Resources\Event\DeviceAccessoryKeypadDisconnected\DeviceWarnings::from_json( + $d, + ), + $json->device_warnings ?? [], + ), + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Errors associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceAccessoryKeypadDisconnected\ConnectedAccountErrors> + */ + public array $connected_account_errors, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Warnings associated with the connected account. + * + * @var list<\Seam\Resources\Event\DeviceAccessoryKeypadDisconnected\ConnectedAccountWarnings> + */ + public array $connected_account_warnings, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * Errors associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceAccessoryKeypadDisconnected\DeviceErrors> + */ + public array $device_errors, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * Warnings associated with the device. + * + * @var list<\Seam\Resources\Event\DeviceAccessoryKeypadDisconnected\DeviceWarnings> + */ + public array $device_warnings, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * Extended periods of noise or noise exceeding a [threshold](https://docs.seam.co/capability-guides/noise-sensors#what-is-a-threshold) were detected. + */ + final class NoiseSensorNoiseThresholdTriggered extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): NoiseSensorNoiseThresholdTriggered|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + minut_metadata: $json->minut_metadata ?? null, + noise_level_decibels: $json->noise_level_decibels ?? null, + noise_level_nrs: $json->noise_level_nrs ?? null, + noise_threshold_id: $json->noise_threshold_id ?? null, + noise_threshold_name: $json->noise_threshold_name ?? null, + noiseaware_metadata: $json->noiseaware_metadata ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Metadata from Minut. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $minut_metadata = null, + /** + * Detected noise level in decibels. + */ + public float|null $noise_level_decibels = null, + /** + * Detected noise level in Noiseaware Noise Risk Score (NRS). + */ + public float|null $noise_level_nrs = null, + /** + * ID of the noise threshold that was triggered. + */ + public string|null $noise_threshold_id = null, + /** + * Name of the noise threshold that was triggered. + */ + public string|null $noise_threshold_name = null, + /** + * Metadata from Noiseaware. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $noiseaware_metadata = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [lock](https://docs.seam.co/low-level-apis/smart-locks) was locked. + */ + final class LockLocked extends \Seam\Resources\Event + { + public static function from_json(mixed $json): LockLocked|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + method: $json->method ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_code_id: $json->access_code_id ?? null, + access_code_is_managed: $json->access_code_is_managed ?? null, + action_attempt_id: $json->action_attempt_id ?? null, + code: $json->code ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + is_via_bluetooth: $json->is_via_bluetooth ?? null, + is_via_nfc: $json->is_via_nfc ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Method by which the lock was locked. `keycode`: an access code was used (see `access_code_id`). `manual`: a physical action such as a thumbturn or button press. `remote`: a remote action via an app, Bluetooth, or the Seam API (see `action_attempt_id` if Seam-initiated; see `is_via_bluetooth` or `is_via_nfc` for the transport). `automatic`: triggered automatically, for example by an auto-relock timer. `unknown`: could not be determined. + * + * @var value-of<\Seam\Resources\Event\LockLocked\Method>|string|null + */ + public string|null $method, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the access code that was used to lock the device. + */ + public string|null $access_code_id = null, + /** + * Whether the access code is managed by Seam (true) or unmanaged (false). Only present when access_code_id is set. + */ + public bool|null $access_code_is_managed = null, + /** + * ID of the Seam action attempt that triggered this lock. Present only when the lock was initiated through Seam (via a `LOCK_DOOR` action attempt). + */ + public string|null $action_attempt_id = null, + /** + * Code (PIN) that was used to lock the device, if known. Taken from the matched managed or unmanaged access code, or from the code reported by the provider when no access code matched. + */ + public string|null $code = null, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Whether the lock action was performed over Bluetooth by a remote client (such as the provider's mobile app), rather than a direct physical interaction or a Seam-initiated remote action. + */ + public bool|null $is_via_bluetooth = null, + /** + * Whether the lock action was performed by an NFC credential tap (such as an Apple Home Key or an NFC key fob) presented to the lock, rather than a direct physical interaction or a Seam-initiated remote action. + */ + public bool|null $is_via_nfc = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [lock](https://docs.seam.co/low-level-apis/smart-locks) was unlocked. + */ + final class LockUnlocked extends \Seam\Resources\Event + { + public static function from_json(mixed $json): LockUnlocked|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + method: $json->method ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_code_id: $json->access_code_id ?? null, + access_code_is_managed: $json->access_code_is_managed ?? null, + action_attempt_id: $json->action_attempt_id ?? null, + code: $json->code ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + is_via_bluetooth: $json->is_via_bluetooth ?? null, + is_via_nfc: $json->is_via_nfc ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Method by which the lock was unlocked. `keycode`: an [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes) was used (see `access_code_id`). `manual`: a physical action such as a thumbturn or handle press. `remote`: a remote action via an app, Bluetooth, or the Seam API (see `action_attempt_id` if Seam-initiated; see `is_via_bluetooth` or `is_via_nfc` for the transport). `automatic`: triggered automatically, for example by a time-based schedule. `unknown`: could not be determined. + * + * @var value-of<\Seam\Resources\Event\LockUnlocked\Method>|string|null + */ + public string|null $method, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the access code that was used to unlock the affected device. + */ + public string|null $access_code_id = null, + /** + * Whether the access code is managed by Seam (true) or unmanaged (false). Only present when access_code_id is set. + */ + public bool|null $access_code_is_managed = null, + /** + * ID of the Seam action attempt that triggered this unlock. Present only when the unlock was initiated through Seam (via an `UNLOCK_DOOR` action attempt). + */ + public string|null $action_attempt_id = null, + /** + * Code (PIN) that was used to unlock the affected device, if known. Taken from the matched managed or unmanaged access code, or from the code reported by the provider when no access code matched. + */ + public string|null $code = null, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * ID of the affected device. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Whether the unlock action was performed over Bluetooth by a remote client (such as the provider's mobile app), rather than a direct physical interaction or a Seam-initiated remote action. + */ + public bool|null $is_via_bluetooth = null, + /** + * Whether the unlock action was performed by an NFC credential tap (such as an Apple Home Key or an NFC key fob) presented to the lock, rather than a direct physical interaction or a Seam-initiated remote action. + */ + public bool|null $is_via_nfc = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The [lock](https://docs.seam.co/low-level-apis/smart-locks) denied access to a user after one or more consecutive invalid attempts to unlock the device. + */ + final class LockAccessDenied extends \Seam\Resources\Event + { + public static function from_json(mixed $json): LockAccessDenied|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + access_code_id: $json->access_code_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + device_id: $json->device_id ?? null, + event_description: $json->event_description ?? null, + reason: isset($json->reason) + ? \Seam\Resources\Event\LockAccessDenied\Reason::from_json( + $json->reason, + ) + : null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * ID of the access code that was used in the unlock attempts. + */ + public string|null $access_code_id = null, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * ID of the affected device. + */ + public string|null $device_id = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Why access was denied, when the provider reports a determinable cause. Omitted when unknown. + */ + public \Seam\Resources\Event\LockAccessDenied\Reason|null $reason = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A thermostat [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) was activated. + */ + final class ThermostatClimatePresetActivated extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ThermostatClimatePresetActivated|null { + if (!$json) { + return null; + } + return new self( + climate_preset_key: $json->climate_preset_key ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + is_fallback_climate_preset: $json->is_fallback_climate_preset ?? + null, + occurred_at: $json->occurred_at ?? null, + thermostat_schedule_id: $json->thermostat_schedule_id ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Key of the climate preset that was activated. + */ + public string|null $climate_preset_key, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Indicates whether the climate preset that was activated is the fallback climate preset for the thermostat. + */ + public bool|null $is_fallback_climate_preset, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the thermostat schedule that prompted the affected climate preset to be activated. + */ + public string|null $thermostat_schedule_id, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [thermostat](https://docs.seam.co/capability-guides/thermostats) was adjusted manually. + */ + final class ThermostatManuallyAdjusted extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ThermostatManuallyAdjusted|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + method: $json->method ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + cooling_set_point_celsius: $json->cooling_set_point_celsius ?? + null, + cooling_set_point_fahrenheit: $json->cooling_set_point_fahrenheit ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + fan_mode_setting: $json->fan_mode_setting ?? null, + heating_set_point_celsius: $json->heating_set_point_celsius ?? + null, + heating_set_point_fahrenheit: $json->heating_set_point_fahrenheit ?? + null, + hvac_mode_setting: $json->hvac_mode_setting ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Method used to adjust the affected thermostat manually. `seam` indicates that the Seam API, Seam CLI, or Seam Console was used to adjust the thermostat. + * + * @var value-of<\Seam\Resources\Event\ThermostatManuallyAdjusted\Method>|string|null + */ + public string|null $method, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * Temperature to which the thermostat should cool (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $cooling_set_point_celsius = null, + /** + * Temperature to which the thermostat should cool (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $cooling_set_point_fahrenheit = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Desired [fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings), such as `on`, `auto`, or `circulate`. + * + * @var value-of<\Seam\Resources\Event\ThermostatManuallyAdjusted\FanModeSetting>|string|null + */ + public string|null $fan_mode_setting = null, + /** + * Temperature to which the thermostat should heat (in °C). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $heating_set_point_celsius = null, + /** + * Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + public float|null $heating_set_point_fahrenheit = null, + /** + * Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. + * + * @var value-of<\Seam\Resources\Event\ThermostatManuallyAdjusted\HvacModeSetting>|string|null + */ + public string|null $hvac_mode_setting = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [thermostat's](https://docs.seam.co/capability-guides/thermostats) temperature reading exceeded the set [threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds). + */ + final class ThermostatTemperatureThresholdExceeded extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ThermostatTemperatureThresholdExceeded|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + lower_limit_celsius: $json->lower_limit_celsius ?? null, + lower_limit_fahrenheit: $json->lower_limit_fahrenheit ?? null, + occurred_at: $json->occurred_at ?? null, + temperature_celsius: $json->temperature_celsius ?? null, + temperature_fahrenheit: $json->temperature_fahrenheit ?? null, + upper_limit_celsius: $json->upper_limit_celsius ?? null, + upper_limit_fahrenheit: $json->upper_limit_fahrenheit ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Lower temperature limit, in °C, defined by the set threshold. + */ + public float|null $lower_limit_celsius, + /** + * Lower temperature limit, in °F, defined by the set threshold. + */ + public float|null $lower_limit_fahrenheit, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Temperature, in °C, reported by the affected thermostat. + */ + public float|null $temperature_celsius, + /** + * Temperature, in °F, reported by the affected thermostat. + */ + public float|null $temperature_fahrenheit, + /** + * Upper temperature limit, in °C, defined by the set threshold. + */ + public float|null $upper_limit_celsius, + /** + * Upper temperature limit, in °F, defined by the set threshold. + */ + public float|null $upper_limit_fahrenheit, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [thermostat's](https://docs.seam.co/capability-guides/thermostats) temperature reading no longer exceeds the set [threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds). + */ + final class ThermostatTemperatureThresholdNoLongerExceeded extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ThermostatTemperatureThresholdNoLongerExceeded|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + lower_limit_celsius: $json->lower_limit_celsius ?? null, + lower_limit_fahrenheit: $json->lower_limit_fahrenheit ?? null, + occurred_at: $json->occurred_at ?? null, + temperature_celsius: $json->temperature_celsius ?? null, + temperature_fahrenheit: $json->temperature_fahrenheit ?? null, + upper_limit_celsius: $json->upper_limit_celsius ?? null, + upper_limit_fahrenheit: $json->upper_limit_fahrenheit ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Lower temperature limit, in °C, defined by the set threshold. + */ + public float|null $lower_limit_celsius, + /** + * Lower temperature limit, in °F, defined by the set threshold. + */ + public float|null $lower_limit_fahrenheit, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Temperature, in °C, reported by the affected thermostat. + */ + public float|null $temperature_celsius, + /** + * Temperature, in °F, reported by the affected thermostat. + */ + public float|null $temperature_fahrenheit, + /** + * Upper temperature limit, in °C, defined by the set threshold. + */ + public float|null $upper_limit_celsius, + /** + * Upper temperature limit, in °F, defined by the set threshold. + */ + public float|null $upper_limit_fahrenheit, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [thermostat's](https://docs.seam.co/capability-guides/thermostats) temperature reading is within 1 °C of the configured cooling or heating [set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). + */ + final class ThermostatTemperatureReachedSetPoint extends + \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ThermostatTemperatureReachedSetPoint|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + temperature_celsius: $json->temperature_celsius ?? null, + temperature_fahrenheit: $json->temperature_fahrenheit ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + desired_temperature_celsius: $json->desired_temperature_celsius ?? + null, + desired_temperature_fahrenheit: $json->desired_temperature_fahrenheit ?? + null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Temperature, in °C, reported by the affected thermostat. + */ + public float|null $temperature_celsius, + /** + * Temperature, in °F, reported by the affected thermostat. + */ + public float|null $temperature_fahrenheit, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Desired temperature, in °C, defined by the affected thermostat's cooling or heating set point. + */ + public float|null $desired_temperature_celsius = null, + /** + * Desired temperature, in °F, defined by the affected thermostat's cooling or heating set point. + */ + public float|null $desired_temperature_fahrenheit = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A [thermostat's](https://docs.seam.co/capability-guides/thermostats) reported temperature changed by at least 1 °C. + */ + final class ThermostatTemperatureChanged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): ThermostatTemperatureChanged|null { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + temperature_celsius: $json->temperature_celsius ?? null, + temperature_fahrenheit: $json->temperature_fahrenheit ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * Temperature, in °C, reported by the affected thermostat. + */ + public float|null $temperature_celsius, + /** + * Temperature, in °F, reported by the affected thermostat. + */ + public float|null $temperature_fahrenheit, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * The name of a device was changed. + */ + final class DeviceNameChanged extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceNameChanged|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + device_name: $json->device_name ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * The new name of the affected device. + */ + public string|null $device_name, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A camera was activated, for example, by motion detection. + */ + final class CameraActivated extends \Seam\Resources\Event + { + public static function from_json(mixed $json): CameraActivated|null + { + if (!$json) { + return null; + } + return new self( + activation_reason: $json->activation_reason ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + image_url: $json->image_url ?? null, + motion_sub_type: $json->motion_sub_type ?? null, + video_url: $json->video_url ?? null, + ); + } + + public function __construct( + /** + * The reason the camera was activated. + * + * @var value-of<\Seam\Resources\Event\CameraActivated\ActivationReason>|string|null + */ + public string|null $activation_reason, + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * URL to a thumbnail image captured at the time of activation. + */ + public string|null $image_url = null, + /** + * Sub-type of motion detected, if available. + * + * @var value-of<\Seam\Resources\Event\CameraActivated\MotionSubType>|string|null + */ + public string|null $motion_sub_type = null, + /** + * URL to a short video clip captured at the time of activation. + */ + public string|null $video_url = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A doorbell button was pressed on a device. + */ + final class DeviceDoorbellRang extends \Seam\Resources\Event + { + public static function from_json(mixed $json): DeviceDoorbellRang|null + { + if (!$json) { + return null; + } + return new self( + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + connected_account_custom_metadata: $json->connected_account_custom_metadata ?? + null, + customer_key: $json->customer_key ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + image_url: $json->image_url ?? null, + video_url: $json->video_url ?? null, + ); + } + + public function __construct( + /** + * ID of the connected account associated with the event. + */ + public string|null $connected_account_id, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the connected account, present when connected_account_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $connected_account_custom_metadata = null, + /** + * The customer key associated with the device, if any. + */ + public string|null $customer_key = null, + /** + * Custom metadata of the device, present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * URL to a thumbnail image captured at the time the doorbell was pressed. + */ + public string|null $image_url = null, + /** + * URL to a short video clip captured at the time the doorbell was pressed. + */ + public string|null $video_url = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A phone device was deactivated. + */ + final class PhoneDeactivated extends \Seam\Resources\Event + { + public static function from_json(mixed $json): PhoneDeactivated|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + workspace_id: $json->workspace_id ?? null, + device_custom_metadata: $json->device_custom_metadata ?? null, + event_description: $json->event_description ?? null, + ); + } + + public function __construct( + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * ID of the affected phone device. + */ + public string|null $device_id, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Custom metadata of the device; present when device_id is provided. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $device_custom_metadata = null, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A device was added or removed from a space. + */ + final class SpaceDeviceMembershipChanged extends \Seam\Resources\Event + { + public static function from_json( + mixed $json, + ): SpaceDeviceMembershipChanged|null { + if (!$json) { + return null; + } + return new self( + acs_entrance_ids: $json->acs_entrance_ids ?? null, + created_at: $json->created_at ?? null, + device_ids: $json->device_ids ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + space_id: $json->space_id ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + space_key: $json->space_key ?? null, + ); + } + + public function __construct( + /** + * IDs of all ACS entrances currently attached to the space. + * + * @var list|null + */ + public array|null $acs_entrance_ids, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * IDs of all devices currently attached to the space. + * + * @var list|null + */ + public array|null $device_ids, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the affected space. + */ + public string|null $space_id, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Unique key for the space within the workspace. + */ + public string|null $space_key = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A space was created. + */ + final class SpaceCreated extends \Seam\Resources\Event + { + public static function from_json(mixed $json): SpaceCreated|null + { + if (!$json) { + return null; + } + return new self( + acs_entrance_ids: $json->acs_entrance_ids ?? null, + created_at: $json->created_at ?? null, + device_ids: $json->device_ids ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + space_id: $json->space_id ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + space_key: $json->space_key ?? null, + ); + } + + public function __construct( + /** + * IDs of all ACS entrances attached to the space when it was created. + * + * @var list|null + */ + public array|null $acs_entrance_ids, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * IDs of all devices attached to the space when it was created. + * + * @var list|null + */ + public array|null $device_ids, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the affected space. + */ + public string|null $space_id, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Unique key for the space within the workspace. + */ + public string|null $space_key = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + /** + * A space was deleted. + */ + final class SpaceDeleted extends \Seam\Resources\Event + { + public static function from_json(mixed $json): SpaceDeleted|null + { + if (!$json) { + return null; + } + return new self( + acs_entrance_ids: $json->acs_entrance_ids ?? null, + created_at: $json->created_at ?? null, + device_ids: $json->device_ids ?? null, + event_id: $json->event_id ?? null, + event_type: $json->event_type ?? null, + occurred_at: $json->occurred_at ?? null, + space_id: $json->space_id ?? null, + workspace_id: $json->workspace_id ?? null, + event_description: $json->event_description ?? null, + space_key: $json->space_key ?? null, + ); + } + + public function __construct( + /** + * IDs of all ACS entrances currently attached to the space when it was deleted. + * + * @var list|null + */ + public array|null $acs_entrance_ids, + /** + * Date and time at which the event was created. + */ + string|null $created_at, + /** + * IDs of all devices attached to the space when it was deleted. + * + * @var list|null + */ + public array|null $device_ids, + /** + * ID of the event. + */ + string|null $event_id, + /** + * @var value-of<\Seam\Resources\Event\EventType>|string|null + */ + string|null $event_type, + /** + * Date and time at which the event occurred. + */ + string|null $occurred_at, + /** + * ID of the affected space. + */ + public string|null $space_id, + /** + * ID of the workspace associated with the event. + */ + string|null $workspace_id, + /** + * Human-readable description of the event. Persisted when the event is created (so the creating code, including a provider, can supply a tailored description) and otherwise derived from the event. + */ + string|null $event_description = null, + /** + * Unique key for the space within the workspace. + */ + public string|null $space_key = null, + ) { + parent::__construct( + created_at: $created_at, + event_description: $event_description, + event_id: $event_id, + event_type: $event_type, + occurred_at: $occurred_at, + workspace_id: $workspace_id, + ); + } + } + + enum EventType: string + { + case ACCESS_CODE_CREATED = "access_code.created"; + case ACCESS_CODE_CHANGED = "access_code.changed"; + case ACCESS_CODE_NAME_CHANGED = "access_code.name_changed"; + case ACCESS_CODE_CODE_CHANGED = "access_code.code_changed"; + case ACCESS_CODE_TIME_FRAME_CHANGED = "access_code.time_frame_changed"; + case ACCESS_CODE_MUTATIONS_REQUESTED = "access_code.mutations_requested"; + case ACCESS_CODE_SCHEDULED_ON_DEVICE = "access_code.scheduled_on_device"; + case ACCESS_CODE_SET_ON_DEVICE = "access_code.set_on_device"; + case ACCESS_CODE_REMOVED_FROM_DEVICE = "access_code.removed_from_device"; + case ACCESS_CODE_DELAY_IN_SETTING_ON_DEVICE = "access_code.delay_in_setting_on_device"; + case ACCESS_CODE_FAILED_TO_SET_ON_DEVICE = "access_code.failed_to_set_on_device"; + case ACCESS_CODE_DELETED = "access_code.deleted"; + case ACCESS_CODE_DELAY_IN_REMOVING_FROM_DEVICE = "access_code.delay_in_removing_from_device"; + case ACCESS_CODE_FAILED_TO_REMOVE_FROM_DEVICE = "access_code.failed_to_remove_from_device"; + case ACCESS_CODE_MODIFIED_EXTERNAL_TO_SEAM = "access_code.modified_external_to_seam"; + case ACCESS_CODE_DELETED_EXTERNAL_TO_SEAM = "access_code.deleted_external_to_seam"; + case ACCESS_CODE_BACKUP_ACCESS_CODE_PULLED = "access_code.backup_access_code_pulled"; + case ACCESS_CODE_UNMANAGED_CONVERTED_TO_MANAGED = "access_code.unmanaged.converted_to_managed"; + case ACCESS_CODE_UNMANAGED_FAILED_TO_CONVERT_TO_MANAGED = "access_code.unmanaged.failed_to_convert_to_managed"; + case ACCESS_CODE_UNMANAGED_CREATED = "access_code.unmanaged.created"; + case ACCESS_CODE_UNMANAGED_REMOVED = "access_code.unmanaged.removed"; + case ACCESS_GRANT_CREATED = "access_grant.created"; + case ACCESS_GRANT_DELETED = "access_grant.deleted"; + case ACCESS_GRANT_ACCESS_GRANTED_TO_ALL_DOORS = "access_grant.access_granted_to_all_doors"; + case ACCESS_GRANT_ACCESS_GRANTED_TO_DOOR = "access_grant.access_granted_to_door"; + case ACCESS_GRANT_ACCESS_TO_DOOR_LOST = "access_grant.access_to_door_lost"; + case ACCESS_GRANT_ACCESS_TIMES_CHANGED = "access_grant.access_times_changed"; + case ACCESS_GRANT_COULD_NOT_CREATE_REQUESTED_ACCESS_METHODS = "access_grant.could_not_create_requested_access_methods"; + case ACCESS_METHOD_ISSUED = "access_method.issued"; + case ACCESS_METHOD_REVOKED = "access_method.revoked"; + case ACCESS_METHOD_CARD_ENCODING_REQUIRED = "access_method.card_encoding_required"; + case ACCESS_METHOD_DELETED = "access_method.deleted"; + case ACCESS_METHOD_REISSUED = "access_method.reissued"; + case ACCESS_METHOD_CREATED = "access_method.created"; + case ACCESS_METHOD_DELAY_IN_ISSUING = "access_method.delay_in_issuing"; + case ACCESS_METHOD_FAILED_TO_ISSUE = "access_method.failed_to_issue"; + case ACS_SYSTEM_CONNECTED = "acs_system.connected"; + case ACS_SYSTEM_ADDED = "acs_system.added"; + case ACS_SYSTEM_DISCONNECTED = "acs_system.disconnected"; + case ACS_CREDENTIAL_DELETED = "acs_credential.deleted"; + case ACS_CREDENTIAL_ISSUED = "acs_credential.issued"; + case ACS_CREDENTIAL_REISSUED = "acs_credential.reissued"; + case ACS_CREDENTIAL_INVALIDATED = "acs_credential.invalidated"; + case ACS_USER_CREATED = "acs_user.created"; + case ACS_USER_DELETED = "acs_user.deleted"; + case ACS_ENCODER_ADDED = "acs_encoder.added"; + case ACS_ENCODER_REMOVED = "acs_encoder.removed"; + case ACS_ACCESS_GROUP_DELETED = "acs_access_group.deleted"; + case ACS_ENTRANCE_ADDED = "acs_entrance.added"; + case ACS_ENTRANCE_REMOVED = "acs_entrance.removed"; + case CLIENT_SESSION_DELETED = "client_session.deleted"; + case CONNECTED_ACCOUNT_CONNECTED = "connected_account.connected"; + case CONNECTED_ACCOUNT_CREATED = "connected_account.created"; + case CONNECTED_ACCOUNT_SUCCESSFUL_LOGIN = "connected_account.successful_login"; + case CONNECTED_ACCOUNT_DISCONNECTED = "connected_account.disconnected"; + case CONNECTED_ACCOUNT_COMPLETED_FIRST_SYNC = "connected_account.completed_first_sync"; + case CONNECTED_ACCOUNT_DELETED = "connected_account.deleted"; + case CONNECTED_ACCOUNT_COMPLETED_FIRST_SYNC_AFTER_RECONNECTION = "connected_account.completed_first_sync_after_reconnection"; + case CONNECTED_ACCOUNT_REAUTHORIZATION_REQUESTED = "connected_account.reauthorization_requested"; + case ACTION_ATTEMPT_LOCK_DOOR_SUCCEEDED = "action_attempt.lock_door.succeeded"; + case ACTION_ATTEMPT_LOCK_DOOR_FAILED = "action_attempt.lock_door.failed"; + case ACTION_ATTEMPT_UNLOCK_DOOR_SUCCEEDED = "action_attempt.unlock_door.succeeded"; + case ACTION_ATTEMPT_UNLOCK_DOOR_FAILED = "action_attempt.unlock_door.failed"; + case ACTION_ATTEMPT_SIMULATE_KEYPAD_CODE_ENTRY_SUCCEEDED = "action_attempt.simulate_keypad_code_entry.succeeded"; + case ACTION_ATTEMPT_SIMULATE_KEYPAD_CODE_ENTRY_FAILED = "action_attempt.simulate_keypad_code_entry.failed"; + case ACTION_ATTEMPT_SIMULATE_MANUAL_LOCK_VIA_KEYPAD_SUCCEEDED = "action_attempt.simulate_manual_lock_via_keypad.succeeded"; + case ACTION_ATTEMPT_SIMULATE_MANUAL_LOCK_VIA_KEYPAD_FAILED = "action_attempt.simulate_manual_lock_via_keypad.failed"; + case CONNECT_WEBVIEW_LOGIN_SUCCEEDED = "connect_webview.login_succeeded"; + case CONNECT_WEBVIEW_LOGIN_FAILED = "connect_webview.login_failed"; + case DEVICE_CONNECTED = "device.connected"; + case DEVICE_ADDED = "device.added"; + case DEVICE_CONVERTED_TO_UNMANAGED = "device.converted_to_unmanaged"; + case DEVICE_UNMANAGED_CONVERTED_TO_MANAGED = "device.unmanaged.converted_to_managed"; + case DEVICE_UNMANAGED_CONNECTED = "device.unmanaged.connected"; + case DEVICE_DISCONNECTED = "device.disconnected"; + case DEVICE_UNMANAGED_DISCONNECTED = "device.unmanaged.disconnected"; + case DEVICE_TAMPERED = "device.tampered"; + case DEVICE_LOW_BATTERY = "device.low_battery"; + case DEVICE_BATTERY_STATUS_CHANGED = "device.battery_status_changed"; + case DEVICE_REMOVED = "device.removed"; + case DEVICE_DELETED = "device.deleted"; + case DEVICE_THIRD_PARTY_INTEGRATION_DETECTED = "device.third_party_integration_detected"; + case DEVICE_THIRD_PARTY_INTEGRATION_NO_LONGER_DETECTED = "device.third_party_integration_no_longer_detected"; + case DEVICE_SALTO_PRIVACY_MODE_ACTIVATED = "device.salto.privacy_mode_activated"; + case DEVICE_SALTO_PRIVACY_MODE_DEACTIVATED = "device.salto.privacy_mode_deactivated"; + case DEVICE_CONNECTION_BECAME_FLAKY = "device.connection_became_flaky"; + case DEVICE_CONNECTION_STABILIZED = "device.connection_stabilized"; + case DEVICE_ERROR_SUBSCRIPTION_REQUIRED = "device.error.subscription_required"; + case DEVICE_ERROR_SUBSCRIPTION_REQUIRED_RESOLVED = "device.error.subscription_required.resolved"; + case DEVICE_ACCESSORY_KEYPAD_CONNECTED = "device.accessory_keypad_connected"; + case DEVICE_ACCESSORY_KEYPAD_DISCONNECTED = "device.accessory_keypad_disconnected"; + case NOISE_SENSOR_NOISE_THRESHOLD_TRIGGERED = "noise_sensor.noise_threshold_triggered"; + case LOCK_LOCKED = "lock.locked"; + case LOCK_UNLOCKED = "lock.unlocked"; + case LOCK_ACCESS_DENIED = "lock.access_denied"; + case THERMOSTAT_CLIMATE_PRESET_ACTIVATED = "thermostat.climate_preset_activated"; + case THERMOSTAT_MANUALLY_ADJUSTED = "thermostat.manually_adjusted"; + case THERMOSTAT_TEMPERATURE_THRESHOLD_EXCEEDED = "thermostat.temperature_threshold_exceeded"; + case THERMOSTAT_TEMPERATURE_THRESHOLD_NO_LONGER_EXCEEDED = "thermostat.temperature_threshold_no_longer_exceeded"; + case THERMOSTAT_TEMPERATURE_REACHED_SET_POINT = "thermostat.temperature_reached_set_point"; + case THERMOSTAT_TEMPERATURE_CHANGED = "thermostat.temperature_changed"; + case DEVICE_NAME_CHANGED = "device.name_changed"; + case CAMERA_ACTIVATED = "camera.activated"; + case DEVICE_DOORBELL_RANG = "device.doorbell_rang"; + case PHONE_DEACTIVATED = "phone.deactivated"; + case SPACE_DEVICE_MEMBERSHIP_CHANGED = "space.device_membership_changed"; + case SPACE_CREATED = "space.created"; + case SPACE_DELETED = "space.deleted"; + } +} + +namespace Seam\Resources\Event\AccessCodeChanged { + /** + * List of properties that changed on the access code. + */ + class ChangedProperties + { + public static function from_json(mixed $json): ChangedProperties|null + { + if (!$json) { + return null; + } + return new self( + from: $json->from ?? null, + property: $json->property ?? null, + to: $json->to ?? null, + ); + } + + public function __construct( + /** + * Previous value of the property, or null if not set. + */ + public string|null $from, + /** + * Name of the property that changed (e.g. `code`). + */ + public string|null $property, + /** + * New value of the property, or null if cleared. + */ + public string|null $to, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeNameChanged { + /** + * Previous access code name configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(name: $json->name ?? null); + } + + public function __construct( + /** + * Previous name of the access code. + */ + public string|null $name, + ) {} + } + + /** + * New access code name configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(name: $json->name ?? null); + } + + public function __construct( + /** + * New name of the access code. + */ + public string|null $name, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeCodeChanged { + /** + * Previous pin code configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(code: $json->code ?? null); + } + + public function __construct( + /** + * Previous pin code. + */ + public string|null $code, + ) {} + } + + /** + * New pin code configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(code: $json->code ?? null); + } + + public function __construct( + /** + * New pin code. + */ + public string|null $code, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeTimeFrameChanged { + /** + * Previous time frame configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Previous end time. + */ + public string|null $ends_at, + /** + * Previous start time. + */ + public string|null $starts_at, + ) {} + } + + /** + * New time frame configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * New end time. + */ + public string|null $ends_at, + /** + * New start time. + */ + public string|null $starts_at, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeMutationsRequested { + /** + * Array of mutations requested on the access code, each containing the mutation type and from/to values. + */ + class RequestedMutations + { + public static function from_json(mixed $json): RequestedMutations|null + { + if (!$json) { + return null; + } + return new self( + mutation_code: $json->mutation_code ?? null, + from: $json->from ?? null, + to: $json->to ?? null, + ); + } + + public function __construct( + /** + * Code identifying the type of mutation requested, such as `updating_name`, `updating_code`, `updating_time_frame`, or `deleting`. + * + * @var value-of<\Seam\Resources\Event\AccessCodeMutationsRequested\RequestedMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + /** + * Previous property values before the requested change. Keys depend on the mutation type. Absent for non-property mutations like `deleting`. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $from = null, + /** + * New property values after the requested change. Keys depend on the mutation type. Absent for non-property mutations like `deleting`. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $to = null, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeMutationsRequested\RequestedMutations { + enum MutationCode: string + { + case UPDATING_NAME = "updating_name"; + case UPDATING_CODE = "updating_code"; + case UPDATING_TIME_FRAME = "updating_time_frame"; + case DELETING = "deleting"; + case CREATING = "creating"; + case DEFERRING_CREATION = "deferring_creation"; + } +} + +namespace Seam\Resources\Event\AccessCodeDelayInSettingOnDevice { + /** + * Errors associated with the access code. + */ + class AccessCodeErrors + { + public static function from_json(mixed $json): AccessCodeErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the access code. + */ + class AccessCodeWarnings + { + public static function from_json(mixed $json): AccessCodeWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeFailedToSetOnDevice { + /** + * Errors associated with the access code. + */ + class AccessCodeErrors + { + public static function from_json(mixed $json): AccessCodeErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the access code. + */ + class AccessCodeWarnings + { + public static function from_json(mixed $json): AccessCodeWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeDelayInRemovingFromDevice { + /** + * Errors associated with the access code. + */ + class AccessCodeErrors + { + public static function from_json(mixed $json): AccessCodeErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the access code. + */ + class AccessCodeWarnings + { + public static function from_json(mixed $json): AccessCodeWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeFailedToRemoveFromDevice { + /** + * Errors associated with the access code. + */ + class AccessCodeErrors + { + public static function from_json(mixed $json): AccessCodeErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the access code. + */ + class AccessCodeWarnings + { + public static function from_json(mixed $json): AccessCodeWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } +} + +namespace Seam\Resources\Event\AccessCodeUnmanagedFailedToConvertToManaged { + /** + * Errors associated with the access code. + */ + class AccessCodeErrors + { + public static function from_json(mixed $json): AccessCodeErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the access code. + */ + class AccessCodeWarnings + { + public static function from_json(mixed $json): AccessCodeWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } +} + +namespace Seam\Resources\Event\AcsSystemDisconnected { + /** + * Errors associated with the access control system. + */ + class AcsSystemErrors + { + public static function from_json(mixed $json): AcsSystemErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the access control system. + */ + class AcsSystemWarnings + { + public static function from_json(mixed $json): AcsSystemWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } +} + +namespace Seam\Resources\Event\ConnectedAccountDisconnected { + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } } -/** - * Errors associated with the access code. - */ -class EventAccessCodeErrors -{ - public static function from_json(mixed $json): EventAccessCodeErrors|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} +namespace Seam\Resources\Event\ConnectedAccountReauthorizationRequested { + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } } -/** - * Warnings associated with the access code. - */ -class EventAccessCodeWarnings -{ - public static function from_json(mixed $json): EventAccessCodeWarnings|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\Event\DeviceDisconnected { + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + enum ErrorCode: string + { + case ACCOUNT_DISCONNECTED = "account_disconnected"; + case HUB_DISCONNECTED = "hub_disconnected"; + case DEVICE_DISCONNECTED = "device_disconnected"; + } } -/** - * Errors associated with the access control system. - */ -class EventAcsSystemErrors -{ - public static function from_json(mixed $json): EventAcsSystemErrors|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} +namespace Seam\Resources\Event\DeviceUnmanagedDisconnected { + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + enum ErrorCode: string + { + case ACCOUNT_DISCONNECTED = "account_disconnected"; + case HUB_DISCONNECTED = "hub_disconnected"; + case DEVICE_DISCONNECTED = "device_disconnected"; + } } -/** - * Warnings associated with the access control system. - */ -class EventAcsSystemWarnings -{ - public static function from_json(mixed $json): EventAcsSystemWarnings|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\Event\DeviceBatteryStatusChanged { + enum BatteryStatus: string + { + case CRITICAL = "critical"; + case LOW = "low"; + case GOOD = "good"; + case FULL = "full"; + } } -/** - * List of properties that changed on the access code. - */ -class EventChangedProperties -{ - public static function from_json(mixed $json): EventChangedProperties|null - { - if (!$json) { - return null; - } - return new self( - from: $json->from ?? null, - property: $json->property ?? null, - to: $json->to ?? null, - ); - } - - public function __construct( - /** - * Previous value of the property, or null if not set. - */ - public string|null $from, - /** - * Name of the property that changed (e.g. `code`). - */ - public string|null $property, - /** - * New value of the property, or null if cleared. - */ - public string|null $to, - ) {} +namespace Seam\Resources\Event\DeviceConnectionBecameFlaky { + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } } -/** - * Errors associated with the connected account. - */ -class EventConnectedAccountErrors -{ - public static function from_json( - mixed $json, - ): EventConnectedAccountErrors|null { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} +namespace Seam\Resources\Event\DeviceErrorSubscriptionRequired { + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } } -/** - * Warnings associated with the connected account. - */ -class EventConnectedAccountWarnings -{ - public static function from_json( - mixed $json, - ): EventConnectedAccountWarnings|null { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\Event\DeviceAccessoryKeypadDisconnected { + /** + * Errors associated with the connected account. + */ + class ConnectedAccountErrors + { + public static function from_json( + mixed $json, + ): ConnectedAccountErrors|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the connected account. + */ + class ConnectedAccountWarnings + { + public static function from_json( + mixed $json, + ): ConnectedAccountWarnings|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } + + /** + * Errors associated with the device. + */ + class DeviceErrors + { + public static function from_json(mixed $json): DeviceErrors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the device. + */ + class DeviceWarnings + { + public static function from_json(mixed $json): DeviceWarnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + */ + public string|null $warning_code, + ) {} + } } -/** - * Errors associated with the device. - */ -class EventDeviceErrors -{ - public static function from_json(mixed $json): EventDeviceErrors|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} +namespace Seam\Resources\Event\LockLocked { + enum Method: string + { + case KEYCODE = "keycode"; + case MANUAL = "manual"; + case AUTOMATIC = "automatic"; + case UNKNOWN = "unknown"; + case REMOTE = "remote"; + case CARD = "card"; + } } -/** - * Warnings associated with the device. - */ -class EventDeviceWarnings -{ - public static function from_json(mixed $json): EventDeviceWarnings|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\Event\LockUnlocked { + enum Method: string + { + case KEYCODE = "keycode"; + case MANUAL = "manual"; + case AUTOMATIC = "automatic"; + case UNKNOWN = "unknown"; + case REMOTE = "remote"; + case CARD = "card"; + } } -/** - * Previous access code name configuration. - */ -class EventFrom -{ - public static function from_json(mixed $json): EventFrom|null - { - if (!$json) { - return null; - } - return new self( - code: $json->code ?? null, - ends_at: $json->ends_at ?? null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * Previous pin code. - */ - public string|null $code, - /** - * Previous end time. - */ - public string|null $ends_at, - /** - * Previous name of the access code. - */ - public string|null $name, - /** - * Previous start time. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\Event\LockAccessDenied { + /** + * Why access was denied, when the provider reports a determinable cause. Omitted when unknown. + */ + class Reason + { + public static function from_json(mixed $json): Reason|null + { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + reason_code: $json->reason_code ?? null, + ); + } + + public function __construct( + /** + * Human-readable explanation of why access was denied. + */ + public string|null $message, + /** + * Normalized reason a lock denied access. Provider-agnostic; not all providers report every value. + * + * @var value-of<\Seam\Resources\Event\LockAccessDenied\Reason\ReasonCode>|string|null + */ + public string|null $reason_code, + ) {} + } } -/** - * Why access was denied, when the provider reports a determinable cause. Omitted when unknown. - */ -class EventReason -{ - public static function from_json(mixed $json): EventReason|null - { - if (!$json) { - return null; - } - return new self( - message: $json->message ?? null, - reason_code: $json->reason_code ?? null, - ); - } - - public function __construct( - /** - * Human-readable explanation of why access was denied. - */ - public string|null $message, - /** - * Normalized reason a lock denied access. Provider-agnostic; not all providers report every value. - */ - public string|null $reason_code, - ) {} +namespace Seam\Resources\Event\LockAccessDenied\Reason { + enum ReasonCode: string + { + case UNKNOWN_CODE = "unknown_code"; + case EXPIRED_CODE = "expired_code"; + case BLOCKLISTED_CODE = "blocklisted_code"; + case TOO_MANY_ATTEMPTS = "too_many_attempts"; + case BLOCKED_BY_PRIVACY_MODE = "blocked_by_privacy_mode"; + case CREDENTIAL_ERROR = "credential_error"; + } } -/** - * Array of mutations requested on the access code, each containing the mutation type and from/to values. - */ -class EventRequestedMutations -{ - public static function from_json(mixed $json): EventRequestedMutations|null - { - if (!$json) { - return null; - } - return new self( - from: $json->from ?? null, - mutation_code: $json->mutation_code ?? null, - to: $json->to ?? null, - ); - } - - public function __construct( - /** - * Previous property values before the requested change. Keys depend on the mutation type. Absent for non-property mutations like `deleting`. - */ - public mixed $from, - /** - * Code identifying the type of mutation requested, such as `updating_name`, `updating_code`, `updating_time_frame`, or `deleting`. - */ - public string|null $mutation_code, - /** - * New property values after the requested change. Keys depend on the mutation type. Absent for non-property mutations like `deleting`. - */ - public mixed $to, - ) {} +namespace Seam\Resources\Event\ThermostatManuallyAdjusted { + enum FanModeSetting: string + { + case AUTO = "auto"; + case ON = "on"; + case CIRCULATE = "circulate"; + } + + enum HvacModeSetting: string + { + case OFF = "off"; + case HEAT = "heat"; + case COOL = "cool"; + case HEAT_COOL = "heat_cool"; + case ECO = "eco"; + } + + enum Method: string + { + case SEAM = "seam"; + case EXTERNAL = "external"; + } } -/** - * New access code name configuration. - */ -class EventTo -{ - public static function from_json(mixed $json): EventTo|null - { - if (!$json) { - return null; - } - return new self( - code: $json->code ?? null, - ends_at: $json->ends_at ?? null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - ); - } - - public function __construct( - /** - * New pin code. - */ - public string|null $code, - /** - * New end time. - */ - public string|null $ends_at, - /** - * New name of the access code. - */ - public string|null $name, - /** - * New start time. - */ - public string|null $starts_at, - ) {} +namespace Seam\Resources\Event\CameraActivated { + enum ActivationReason: string + { + case MOTION_DETECTED = "motion_detected"; + } + + enum MotionSubType: string + { + case HUMAN = "human"; + case VEHICLE = "vehicle"; + case PACKAGE = "package"; + case OTHER = "other"; + } } diff --git a/src/Resources/InstantKey.php b/src/Resources/InstantKey.php index b14e3cd5..b55c18a0 100644 --- a/src/Resources/InstantKey.php +++ b/src/Resources/InstantKey.php @@ -1,103 +1,108 @@ client_session_id ?? null, + created_at: $json->created_at ?? null, + expires_at: $json->expires_at ?? null, + instant_key_id: $json->instant_key_id ?? null, + instant_key_url: $json->instant_key_url ?? null, + user_identity_id: $json->user_identity_id ?? null, + workspace_id: $json->workspace_id ?? null, + customization: isset($json->customization) + ? \Seam\Resources\InstantKey\Customization::from_json( + $json->customization, + ) + : null, + customization_profile_id: $json->customization_profile_id ?? + null, + ); } - return new self( - client_session_id: $json->client_session_id ?? null, - created_at: $json->created_at ?? null, - customization: isset($json->customization) - ? InstantKeyCustomization::from_json($json->customization) - : null, - customization_profile_id: $json->customization_profile_id ?? null, - expires_at: $json->expires_at ?? null, - instant_key_id: $json->instant_key_id ?? null, - instant_key_url: $json->instant_key_url ?? null, - user_identity_id: $json->user_identity_id ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * ID of the client session associated with the Instant Key. - */ - public string|null $client_session_id, - /** - * Date and time at which the Instant Key was created. - */ - public string|null $created_at, - /** - * Customization applied to the Instant Key UI. - */ - public InstantKeyCustomization|null $customization, - /** - * ID of the customization profile associated with the Instant Key. - */ - public string|null $customization_profile_id, - /** - * Date and time at which the Instant Key expires. - */ - public string|null $expires_at, - /** - * ID of the Instant Key. - */ - public string|null $instant_key_id, - /** - * Shareable URL for the Instant Key. Use the URL to deliver the Instant Key to your user through a link in a text message or email or by embedding it in your web app. - */ - public string|null $instant_key_url, - /** - * ID of the user identity associated with the Instant Key. - */ - public string|null $user_identity_id, - /** - * ID of the workspace that contains the Instant Key. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * ID of the client session associated with the Instant Key. + */ + public string|null $client_session_id, + /** + * Date and time at which the Instant Key was created. + */ + public string|null $created_at, + /** + * Date and time at which the Instant Key expires. + */ + public string|null $expires_at, + /** + * ID of the Instant Key. + */ + public string|null $instant_key_id, + /** + * Shareable URL for the Instant Key. Use the URL to deliver the Instant Key to your user through a link in a text message or email or by embedding it in your web app. + */ + public string|null $instant_key_url, + /** + * ID of the user identity associated with the Instant Key. + */ + public string|null $user_identity_id, + /** + * ID of the workspace that contains the Instant Key. + */ + public string|null $workspace_id, + /** + * Customization applied to the Instant Key UI. + */ + public \Seam\Resources\InstantKey\Customization|null $customization = null, + /** + * ID of the customization profile associated with the Instant Key. + */ + public string|null $customization_profile_id = null, + ) {} + } } -/** - * Customization applied to the Instant Key UI. - */ -class InstantKeyCustomization -{ - public static function from_json(mixed $json): InstantKeyCustomization|null +namespace Seam\Resources\InstantKey { + /** + * Customization applied to the Instant Key UI. + */ + class Customization { - if (!$json) { - return null; + public static function from_json(mixed $json): Customization|null + { + if (!$json) { + return null; + } + return new self( + logo_url: $json->logo_url ?? null, + primary_color: $json->primary_color ?? null, + secondary_color: $json->secondary_color ?? null, + ); } - return new self( - logo_url: $json->logo_url ?? null, - primary_color: $json->primary_color ?? null, - secondary_color: $json->secondary_color ?? null, - ); - } - public function __construct( - /** - * URL of the logo displayed on the Instant Key. - */ - public string|null $logo_url, - /** - * Primary color used in the Instant Key UI. - */ - public string|null $primary_color, - /** - * Secondary color used in the Instant Key UI. - */ - public string|null $secondary_color, - ) {} + public function __construct( + /** + * URL of the logo displayed on the Instant Key. + */ + public string|null $logo_url = null, + /** + * Primary color used in the Instant Key UI. + */ + public string|null $primary_color = null, + /** + * Secondary color used in the Instant Key UI. + */ + public string|null $secondary_color = null, + ) {} + } } diff --git a/src/Resources/NoiseThreshold.php b/src/Resources/NoiseThreshold.php index 31a7b30d..94f75866 100644 --- a/src/Resources/NoiseThreshold.php +++ b/src/Resources/NoiseThreshold.php @@ -1,56 +1,57 @@ device_id ?? null, + ends_daily_at: $json->ends_daily_at ?? null, + name: $json->name ?? null, + noise_threshold_decibels: $json->noise_threshold_decibels ?? + null, + noise_threshold_id: $json->noise_threshold_id ?? null, + starts_daily_at: $json->starts_daily_at ?? null, + noise_threshold_nrs: $json->noise_threshold_nrs ?? null, + ); } - return new self( - device_id: $json->device_id ?? null, - ends_daily_at: $json->ends_daily_at ?? null, - name: $json->name ?? null, - noise_threshold_decibels: $json->noise_threshold_decibels ?? null, - noise_threshold_id: $json->noise_threshold_id ?? null, - noise_threshold_nrs: $json->noise_threshold_nrs ?? null, - starts_daily_at: $json->starts_daily_at ?? null, - ); - } - public function __construct( - /** - * Unique identifier for the device that contains the noise threshold. - */ - public string|null $device_id, - /** - * Time at which the noise threshold should become inactive daily. - */ - public string|null $ends_daily_at, - /** - * Name of the noise threshold. - */ - public string|null $name, - /** - * Noise level in decibels for the noise threshold. - */ - public float|null $noise_threshold_decibels, - /** - * Unique identifier for the noise threshold. - */ - public string|null $noise_threshold_id, - /** - * Noise level in Noiseaware Noise Risk Score (NRS) for the noise threshold. This parameter is only relevant for [Noiseaware sensors](https://docs.seam.co/device-and-system-integration-guides/noiseaware-sensors). - */ - public float|null $noise_threshold_nrs, - /** - * Time at which the noise threshold should become active daily. - */ - public string|null $starts_daily_at, - ) {} + public function __construct( + /** + * Unique identifier for the device that contains the noise threshold. + */ + public string|null $device_id, + /** + * Time at which the noise threshold should become inactive daily. + */ + public string|null $ends_daily_at, + /** + * Name of the noise threshold. + */ + public string|null $name, + /** + * Noise level in decibels for the noise threshold. + */ + public float|null $noise_threshold_decibels, + /** + * Unique identifier for the noise threshold. + */ + public string|null $noise_threshold_id, + /** + * Time at which the noise threshold should become active daily. + */ + public string|null $starts_daily_at, + /** + * Noise level in Noiseaware Noise Risk Score (NRS) for the noise threshold. This parameter is only relevant for [Noiseaware sensors](https://docs.seam.co/device-and-system-integration-guides/noiseaware-sensors). + */ + public float|null $noise_threshold_nrs = null, + ) {} + } } diff --git a/src/Resources/Phone.php b/src/Resources/Phone.php index 866153d1..1573cb51 100644 --- a/src/Resources/Phone.php +++ b/src/Resources/Phone.php @@ -1,267 +1,295 @@ created_at ?? null, + custom_metadata: $json->custom_metadata ?? null, + device_id: $json->device_id ?? null, + device_type: $json->device_type ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn($e) => \Seam\Resources\Phone\Errors::from_json($e), + $json->errors ?? [], + ), + properties: isset($json->properties) + ? \Seam\Resources\Phone\Properties::from_json( + $json->properties, + ) + : null, + warnings: array_map( + fn($w) => \Seam\Resources\Phone\Warnings::from_json($w), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + nickname: $json->nickname ?? null, + ); } - return new self( - created_at: $json->created_at ?? null, - custom_metadata: $json->custom_metadata ?? null, - device_id: $json->device_id ?? null, - device_type: $json->device_type ?? null, - display_name: $json->display_name ?? null, - errors: array_map( - fn($e) => PhoneErrors::from_json($e), - $json->errors ?? [], - ), - nickname: $json->nickname ?? null, - properties: isset($json->properties) - ? PhoneProperties::from_json($json->properties) - : null, - warnings: array_map( - fn($w) => PhoneWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * Date and time at which the phone was created. - */ - public string|null $created_at, - /** - * Optional [custom metadata](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) for the phone. - */ - public mixed $custom_metadata, - /** - * ID of the phone. - */ - public string|null $device_id, - /** - * Type of the phone device, such as `ios_phone` or `android_phone`. - */ - public string|null $device_type, - /** - * Display name of the phone. Defaults to `nickname` (if it is set) or `properties.appearance.name`, otherwise. Enables administrators and users to identify the phone easily, especially when there are numerous phones. - */ - public string|null $display_name, - /** - * Errors associated with the phone. - */ - public array $errors, - /** - * Optional nickname to describe the phone, settable through Seam. - */ - public string|null $nickname, - /** - * Properties of the phone. - */ - public PhoneProperties|null $properties, - /** - * Warnings associated with the phone. - */ - public array $warnings, - /** - * ID of the workspace that contains the phone. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * Date and time at which the phone was created. + */ + public string|null $created_at, + /** + * Optional [custom metadata](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) for the phone. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $custom_metadata, + /** + * ID of the phone. + */ + public string|null $device_id, + /** + * Type of the phone device, such as `ios_phone` or `android_phone`. + * + * @var value-of<\Seam\Resources\Phone\DeviceType>|string|null + */ + public string|null $device_type, + /** + * Display name of the phone. Defaults to `nickname` (if it is set) or `properties.appearance.name`, otherwise. Enables administrators and users to identify the phone easily, especially when there are numerous phones. + */ + public string|null $display_name, + /** + * Errors associated with the phone. + * + * @var list<\Seam\Resources\Phone\Errors> + */ + public array $errors, + /** + * Properties of the phone. + */ + public \Seam\Resources\Phone\Properties|null $properties, + /** + * Warnings associated with the phone. + * + * @var list<\Seam\Resources\Phone\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the phone. + */ + public string|null $workspace_id, + /** + * Optional nickname to describe the phone, settable through Seam. + */ + public string|null $nickname = null, + ) {} + } } -/** - * ASSA ABLOY Credential Service metadata for the phone. - */ -class PhoneAssaAbloyCredentialServiceMetadata -{ - public static function from_json( - mixed $json, - ): PhoneAssaAbloyCredentialServiceMetadata|null { - if (!$json) { - return null; +namespace Seam\Resources\Phone { + /** + * Errors associated with the phone. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); } - return new self( - endpoints: array_map( - fn($e) => PhoneEndpoints::from_json($e), - $json->endpoints ?? [], - ), - has_active_endpoint: $json->has_active_endpoint ?? null, - ); - } - public function __construct( - /** - * Endpoints associated with the phone. - */ - public array $endpoints, - /** - * Indicates whether the credential service has active endpoints associated with the phone. - */ - public bool|null $has_active_endpoint, - ) {} -} + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. + */ + public string|null $error_code, + /** + * Detailed description of the error. + */ + public string|null $message, + ) {} + } -/** - * Endpoints associated with the phone. - */ -class PhoneEndpoints -{ - public static function from_json(mixed $json): PhoneEndpoints|null + /** + * Properties of the phone. + */ + class Properties { - if (!$json) { - return null; + public static function from_json(mixed $json): Properties|null + { + if (!$json) { + return null; + } + return new self( + assa_abloy_credential_service_metadata: isset( + $json->assa_abloy_credential_service_metadata, + ) + ? \Seam\Resources\Phone\Properties\AssaAbloyCredentialServiceMetadata::from_json( + $json->assa_abloy_credential_service_metadata, + ) + : null, + salto_space_credential_service_metadata: isset( + $json->salto_space_credential_service_metadata, + ) + ? \Seam\Resources\Phone\Properties\SaltoSpaceCredentialServiceMetadata::from_json( + $json->salto_space_credential_service_metadata, + ) + : null, + ); } - return new self( - endpoint_id: $json->endpoint_id ?? null, - is_active: $json->is_active ?? null, - ); - } - public function __construct( - /** - * ID of the associated endpoint. - */ - public string|null $endpoint_id, - /** - * Indicated whether the endpoint is active. - */ - public bool|null $is_active, - ) {} -} + public function __construct( + /** + * ASSA ABLOY Credential Service metadata for the phone. + */ + public \Seam\Resources\Phone\Properties\AssaAbloyCredentialServiceMetadata|null $assa_abloy_credential_service_metadata = null, + /** + * Salto Space credential service metadata for the phone. + */ + public \Seam\Resources\Phone\Properties\SaltoSpaceCredentialServiceMetadata|null $salto_space_credential_service_metadata = null, + ) {} + } -/** - * Errors associated with the phone. - */ -class PhoneErrors -{ - public static function from_json(mixed $json): PhoneErrors|null + /** + * Warnings associated with the phone. + */ + class Warnings { - if (!$json) { - return null; + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. + */ + public string|null $warning_code, + ) {} } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. - */ - public string|null $error_code, - /** - * Detailed description of the error. - */ - public string|null $message, - ) {} + enum DeviceType: string + { + case IOS_PHONE = "ios_phone"; + case ANDROID_PHONE = "android_phone"; + } } -/** - * Properties of the phone. - */ -class PhoneProperties -{ - public static function from_json(mixed $json): PhoneProperties|null +namespace Seam\Resources\Phone\Properties { + /** + * ASSA ABLOY Credential Service metadata for the phone. + */ + class AssaAbloyCredentialServiceMetadata { - if (!$json) { - return null; + public static function from_json( + mixed $json, + ): AssaAbloyCredentialServiceMetadata|null { + if (!$json) { + return null; + } + return new self( + endpoints: array_map( + fn( + $e, + ) => \Seam\Resources\Phone\Properties\AssaAbloyCredentialServiceMetadata\Endpoints::from_json( + $e, + ), + $json->endpoints ?? [], + ), + has_active_endpoint: $json->has_active_endpoint ?? null, + ); } - return new self( - assa_abloy_credential_service_metadata: isset( - $json->assa_abloy_credential_service_metadata, - ) - ? PhoneAssaAbloyCredentialServiceMetadata::from_json( - $json->assa_abloy_credential_service_metadata, - ) - : null, - salto_space_credential_service_metadata: isset( - $json->salto_space_credential_service_metadata, - ) - ? PhoneSaltoSpaceCredentialServiceMetadata::from_json( - $json->salto_space_credential_service_metadata, - ) - : null, - ); - } - public function __construct( - /** - * ASSA ABLOY Credential Service metadata for the phone. - */ - public PhoneAssaAbloyCredentialServiceMetadata|null $assa_abloy_credential_service_metadata, - /** - * Salto Space credential service metadata for the phone. - */ - public PhoneSaltoSpaceCredentialServiceMetadata|null $salto_space_credential_service_metadata, - ) {} -} + public function __construct( + /** + * Endpoints associated with the phone. + * + * @var list<\Seam\Resources\Phone\Properties\AssaAbloyCredentialServiceMetadata\Endpoints>|null + */ + public array|null $endpoints = null, + /** + * Indicates whether the credential service has active endpoints associated with the phone. + */ + public bool|null $has_active_endpoint = null, + ) {} + } -/** - * Salto Space credential service metadata for the phone. - */ -class PhoneSaltoSpaceCredentialServiceMetadata -{ - public static function from_json( - mixed $json, - ): PhoneSaltoSpaceCredentialServiceMetadata|null { - if (!$json) { - return null; + /** + * Salto Space credential service metadata for the phone. + */ + class SaltoSpaceCredentialServiceMetadata + { + public static function from_json( + mixed $json, + ): SaltoSpaceCredentialServiceMetadata|null { + if (!$json) { + return null; + } + return new self(has_active_phone: $json->has_active_phone ?? null); } - return new self(has_active_phone: $json->has_active_phone ?? null); - } - public function __construct( - /** - * Indicates whether the credential service has an active associated phone. - */ - public bool|null $has_active_phone, - ) {} + public function __construct( + /** + * Indicates whether the credential service has an active associated phone. + */ + public bool|null $has_active_phone = null, + ) {} + } } -/** - * Warnings associated with the phone. - */ -class PhoneWarnings -{ - public static function from_json(mixed $json): PhoneWarnings|null +namespace Seam\Resources\Phone\Properties\AssaAbloyCredentialServiceMetadata { + /** + * Endpoints associated with the phone. + */ + class Endpoints { - if (!$json) { - return null; + public static function from_json(mixed $json): Endpoints|null + { + if (!$json) { + return null; + } + return new self( + endpoint_id: $json->endpoint_id ?? null, + is_active: $json->is_active ?? null, + ); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. - */ - public string|null $warning_code, - ) {} + public function __construct( + /** + * ID of the associated endpoint. + */ + public string|null $endpoint_id = null, + /** + * Indicated whether the endpoint is active. + */ + public bool|null $is_active = null, + ) {} + } } diff --git a/src/Resources/Space.php b/src/Resources/Space.php index 433326fa..b93e0512 100644 --- a/src/Resources/Space.php +++ b/src/Resources/Space.php @@ -1,146 +1,152 @@ acs_entrance_count ?? null, + created_at: $json->created_at ?? null, + device_count: $json->device_count ?? null, + display_name: $json->display_name ?? null, + name: $json->name ?? null, + space_id: $json->space_id ?? null, + workspace_id: $json->workspace_id ?? null, + customer_data: isset($json->customer_data) + ? \Seam\Resources\Space\CustomerData::from_json( + $json->customer_data, + ) + : null, + customer_key: $json->customer_key ?? null, + geolocation: isset($json->geolocation) + ? \Seam\Resources\Space\Geolocation::from_json( + $json->geolocation, + ) + : null, + space_key: $json->space_key ?? null, + ); } - return new self( - acs_entrance_count: $json->acs_entrance_count ?? null, - created_at: $json->created_at ?? null, - customer_data: isset($json->customer_data) - ? SpaceCustomerData::from_json($json->customer_data) - : null, - customer_key: $json->customer_key ?? null, - device_count: $json->device_count ?? null, - display_name: $json->display_name ?? null, - geolocation: isset($json->geolocation) - ? SpaceGeolocation::from_json($json->geolocation) - : null, - name: $json->name ?? null, - space_id: $json->space_id ?? null, - space_key: $json->space_key ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * Number of entrances in the space. - */ - public float|null $acs_entrance_count, - /** - * Date and time at which the space was created. - */ - public string|null $created_at, - /** - * Reservation/stay-related defaults for the space. Also carries the provider/PMS-supplied name under a `_name` key (e.g. `guesty_name`), which Seam preserves when you rename the space (read-only — managed by Seam). - */ - public SpaceCustomerData|null $customer_data, - /** - * Customer key associated with the space. - */ - public string|null $customer_key, - /** - * Number of devices in the space. - */ - public float|null $device_count, - /** - * Display name for the space. - */ - public string|null $display_name, - /** - * Geographic coordinates (latitude and longitude) of the space. - */ - public SpaceGeolocation|null $geolocation, - /** - * Name of the space. - */ - public string|null $name, - /** - * ID of the space. - */ - public string|null $space_id, - /** - * Unique key for the space within the workspace. - */ - public string|null $space_key, - /** - * ID of the workspace associated with the space. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * Number of entrances in the space. + */ + public float|null $acs_entrance_count, + /** + * Date and time at which the space was created. + */ + public string|null $created_at, + /** + * Number of devices in the space. + */ + public float|null $device_count, + /** + * Display name for the space. + */ + public string|null $display_name, + /** + * Name of the space. + */ + public string|null $name, + /** + * ID of the space. + */ + public string|null $space_id, + /** + * ID of the workspace associated with the space. + */ + public string|null $workspace_id, + /** + * Reservation/stay-related defaults for the space. Also carries the provider/PMS-supplied name under a `_name` key (e.g. `guesty_name`), which Seam preserves when you rename the space (read-only — managed by Seam). + */ + public \Seam\Resources\Space\CustomerData|null $customer_data = null, + /** + * Customer key associated with the space. + */ + public string|null $customer_key = null, + /** + * Geographic coordinates (latitude and longitude) of the space. + */ + public \Seam\Resources\Space\Geolocation|null $geolocation = null, + /** + * Unique key for the space within the workspace. + */ + public string|null $space_key = null, + ) {} + } } -/** - * Reservation/stay-related defaults for the space. Also carries the provider/PMS-supplied name under a `_name` key (e.g. `guesty_name`), which Seam preserves when you rename the space (read-only — managed by Seam). - */ -class SpaceCustomerData -{ - public static function from_json(mixed $json): SpaceCustomerData|null +namespace Seam\Resources\Space { + /** + * Reservation/stay-related defaults for the space. Also carries the provider/PMS-supplied name under a `_name` key (e.g. `guesty_name`), which Seam preserves when you rename the space (read-only — managed by Seam). + */ + class CustomerData { - if (!$json) { - return null; + public static function from_json(mixed $json): CustomerData|null + { + if (!$json) { + return null; + } + return new self( + address: $json->address ?? null, + default_checkin_time: $json->default_checkin_time ?? null, + default_checkout_time: $json->default_checkout_time ?? null, + time_zone: $json->time_zone ?? null, + ); } - return new self( - address: $json->address ?? null, - default_checkin_time: $json->default_checkin_time ?? null, - default_checkout_time: $json->default_checkout_time ?? null, - time_zone: $json->time_zone ?? null, - ); - } - public function __construct( - /** - * Postal address for the space. - */ - public string|null $address, - /** - * Default check-in time for reservations at the space, as HH:mm or HH:mm:ss. - */ - public string|null $default_checkin_time, - /** - * Default check-out time for reservations at the space, as HH:mm or HH:mm:ss. - */ - public string|null $default_checkout_time, - /** - * IANA time zone for the space, e.g. America/Los_Angeles. - */ - public string|null $time_zone, - ) {} -} + public function __construct( + /** + * Postal address for the space. + */ + public string|null $address = null, + /** + * Default check-in time for reservations at the space, as HH:mm or HH:mm:ss. + */ + public string|null $default_checkin_time = null, + /** + * Default check-out time for reservations at the space, as HH:mm or HH:mm:ss. + */ + public string|null $default_checkout_time = null, + /** + * IANA time zone for the space, e.g. America/Los_Angeles. + */ + public string|null $time_zone = null, + ) {} + } -/** - * Geographic coordinates (latitude and longitude) of the space. - */ -class SpaceGeolocation -{ - public static function from_json(mixed $json): SpaceGeolocation|null + /** + * Geographic coordinates (latitude and longitude) of the space. + */ + class Geolocation { - if (!$json) { - return null; + public static function from_json(mixed $json): Geolocation|null + { + if (!$json) { + return null; + } + return new self( + latitude: $json->latitude ?? null, + longitude: $json->longitude ?? null, + ); } - return new self( - latitude: $json->latitude ?? null, - longitude: $json->longitude ?? null, - ); - } - public function __construct( - /** - * Latitude of the space, in decimal degrees. - */ - public float|null $latitude, - /** - * Longitude of the space, in decimal degrees. - */ - public float|null $longitude, - ) {} + public function __construct( + /** + * Latitude of the space, in decimal degrees. + */ + public float|null $latitude, + /** + * Longitude of the space, in decimal degrees. + */ + public float|null $longitude, + ) {} + } } diff --git a/src/Resources/ThermostatDailyProgram.php b/src/Resources/ThermostatDailyProgram.php index 1232a323..e19bb791 100644 --- a/src/Resources/ThermostatDailyProgram.php +++ b/src/Resources/ThermostatDailyProgram.php @@ -1,84 +1,92 @@ created_at ?? null, + device_id: $json->device_id ?? null, + name: $json->name ?? null, + periods: array_map( + fn( + $p, + ) => \Seam\Resources\ThermostatDailyProgram\Periods::from_json( + $p, + ), + $json->periods ?? [], + ), + thermostat_daily_program_id: $json->thermostat_daily_program_id ?? + null, + workspace_id: $json->workspace_id ?? null, + ); } - return new self( - created_at: $json->created_at ?? null, - device_id: $json->device_id ?? null, - name: $json->name ?? null, - periods: array_map( - fn($p) => ThermostatDailyProgramPeriods::from_json($p), - $json->periods ?? [], - ), - thermostat_daily_program_id: $json->thermostat_daily_program_id ?? - null, - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * Date and time at which the thermostat daily program was created. - */ - public string|null $created_at, - /** - * ID of the thermostat device on which the thermostat daily program is configured. - */ - public string|null $device_id, - /** - * User-friendly name to identify the thermostat daily program. - */ - public string|null $name, - /** - * Array of thermostat daily program periods. - */ - public array $periods, - /** - * ID of the thermostat daily program. - */ - public string|null $thermostat_daily_program_id, - /** - * ID of the workspace that contains the thermostat daily program. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * Date and time at which the thermostat daily program was created. + */ + public string|null $created_at, + /** + * ID of the thermostat device on which the thermostat daily program is configured. + */ + public string|null $device_id, + /** + * User-friendly name to identify the thermostat daily program. + */ + public string|null $name, + /** + * Array of thermostat daily program periods. + * + * @var list<\Seam\Resources\ThermostatDailyProgram\Periods> + */ + public array $periods, + /** + * ID of the thermostat daily program. + */ + public string|null $thermostat_daily_program_id, + /** + * ID of the workspace that contains the thermostat daily program. + */ + public string|null $workspace_id, + ) {} + } } -/** - * Array of thermostat daily program periods. - */ -class ThermostatDailyProgramPeriods -{ - public static function from_json( - mixed $json, - ): ThermostatDailyProgramPeriods|null { - if (!$json) { - return null; +namespace Seam\Resources\ThermostatDailyProgram { + /** + * Array of thermostat daily program periods. + */ + class Periods + { + public static function from_json(mixed $json): Periods|null + { + if (!$json) { + return null; + } + return new self( + climate_preset_key: $json->climate_preset_key ?? null, + starts_at_time: $json->starts_at_time ?? null, + ); } - return new self( - climate_preset_key: $json->climate_preset_key ?? null, - starts_at_time: $json->starts_at_time ?? null, - ); - } - public function __construct( - /** - * Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to activate at the `starts_at_time`. - */ - public string|null $climate_preset_key, - /** - * Time at which the thermostat daily program period starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at_time, - ) {} + public function __construct( + /** + * Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to activate at the `starts_at_time`. + */ + public string|null $climate_preset_key, + /** + * Time at which the thermostat daily program period starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at_time, + ) {} + } } diff --git a/src/Resources/ThermostatSchedule.php b/src/Resources/ThermostatSchedule.php index 6c44947c..225123db 100644 --- a/src/Resources/ThermostatSchedule.php +++ b/src/Resources/ThermostatSchedule.php @@ -1,113 +1,121 @@ climate_preset_key ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + ends_at: $json->ends_at ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\ThermostatSchedule\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + name: $json->name ?? null, + starts_at: $json->starts_at ?? null, + thermostat_schedule_id: $json->thermostat_schedule_id ?? null, + workspace_id: $json->workspace_id ?? null, + is_override_allowed: $json->is_override_allowed ?? null, + max_override_period_minutes: $json->max_override_period_minutes ?? + null, + ); } - return new self( - climate_preset_key: $json->climate_preset_key ?? null, - created_at: $json->created_at ?? null, - device_id: $json->device_id ?? null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => ThermostatScheduleErrors::from_json($e), - $json->errors ?? [], - ), - is_override_allowed: $json->is_override_allowed ?? null, - max_override_period_minutes: $json->max_override_period_minutes ?? - null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - thermostat_schedule_id: $json->thermostat_schedule_id ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } - public function __construct( - /** - * Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to use for the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public string|null $climate_preset_key, - /** - * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) was created. - */ - public string|null $created_at, - /** - * ID of the desired [thermostat](https://docs.seam.co/capability-guides/thermostats) device. - */ - public string|null $device_id, - /** - * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $ends_at, - /** - * Errors associated with the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public array $errors, - /** - * Indicates whether a person at the thermostat can change the thermostat's settings after the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) starts. - */ - public bool|null $is_override_allowed, - /** - * Number of minutes for which a person at the thermostat can change the thermostat's settings after the activation of the scheduled [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). - */ - public int|null $max_override_period_minutes, - /** - * User-friendly name to identify the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public string|null $name, - /** - * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. - */ - public string|null $starts_at, - /** - * ID of the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ - public string|null $thermostat_schedule_id, - /** - * ID of the workspace that contains the thermostat schedule. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to use for the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + public string|null $climate_preset_key, + /** + * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) was created. + */ + public string|null $created_at, + /** + * ID of the desired [thermostat](https://docs.seam.co/capability-guides/thermostats) device. + */ + public string|null $device_id, + /** + * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $ends_at, + /** + * Errors associated with the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + * + * @var list<\Seam\Resources\ThermostatSchedule\Errors> + */ + public array $errors, + /** + * User-friendly name to identify the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + public string|null $name, + /** + * Date and time at which the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. + */ + public string|null $starts_at, + /** + * ID of the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + public string|null $thermostat_schedule_id, + /** + * ID of the workspace that contains the thermostat schedule. + */ + public string|null $workspace_id, + /** + * Indicates whether a person at the thermostat can change the thermostat's settings after the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules) starts. + */ + public bool|null $is_override_allowed = null, + /** + * Number of minutes for which a person at the thermostat can change the thermostat's settings after the activation of the scheduled [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). + */ + public int|null $max_override_period_minutes = null, + ) {} + } } -/** - * Errors associated with the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). - */ -class ThermostatScheduleErrors -{ - public static function from_json(mixed $json): ThermostatScheduleErrors|null +namespace Seam\Resources\ThermostatSchedule { + /** + * Errors associated with the [thermostat schedule](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules). + */ + class Errors { - if (!$json) { - return null; + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); - } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } } diff --git a/src/Resources/UnmanagedAccessCode.php b/src/Resources/UnmanagedAccessCode.php index 4804b688..35991da2 100644 --- a/src/Resources/UnmanagedAccessCode.php +++ b/src/Resources/UnmanagedAccessCode.php @@ -1,340 +1,2013 @@ access_code_id ?? null, - cannot_be_managed: $json->cannot_be_managed ?? null, - cannot_delete_unmanaged_access_code: $json->cannot_delete_unmanaged_access_code ?? - null, - code: $json->code ?? null, - created_at: $json->created_at ?? null, - device_id: $json->device_id ?? null, - dormakaba_oracode_metadata: isset($json->dormakaba_oracode_metadata) - ? UnmanagedAccessCodeDormakabaOracodeMetadata::from_json( +namespace Seam\Resources { + /** + * Represents an [unmanaged smart lock access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes). + * + * An access code is a code used for a keypad or pinpad device. Unlike physical keys, which can easily be lost or duplicated, PIN codes can be customized, tracked, and altered on the fly. + * + * When you create an access code on a device in Seam, it is created as a managed access code. Access codes that exist on a device that were not created through Seam are considered unmanaged codes. We strictly limit the operations that can be performed on unmanaged codes. + * + * Prior to using Seam to manage your devices, you may have used another lock management system to manage the access codes on your devices. Where possible, we help you keep any existing access codes on devices and transition those codes to ones managed by your Seam workspace. + * + * Not all providers support unmanaged access codes. The following providers do not support unmanaged access codes: + * + * - [Kwikset](https://docs.seam.co/device-and-system-integration-guides/kwikset-locks) + */ + class UnmanagedAccessCode + { + public static function from_json(mixed $json): UnmanagedAccessCode|null + { + if (!$json) { + return null; + } + return new self( + access_code_id: $json->access_code_id ?? null, + code: $json->code ?? null, + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\UnmanagedAccessCode\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_managed: $json->is_managed ?? null, + name: $json->name ?? null, + status: $json->status ?? null, + type: $json->type ?? null, + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\UnmanagedAccessCode\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + cannot_be_managed: $json->cannot_be_managed ?? null, + cannot_delete_unmanaged_access_code: $json->cannot_delete_unmanaged_access_code ?? + null, + dormakaba_oracode_metadata: isset( $json->dormakaba_oracode_metadata, ) - : null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => UnmanagedAccessCodeErrors::from_json($e), - $json->errors ?? [], - ), - is_managed: $json->is_managed ?? null, - name: $json->name ?? null, - starts_at: $json->starts_at ?? null, - status: $json->status ?? null, - type: $json->type ?? null, - warnings: array_map( - fn($w) => UnmanagedAccessCodeWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Unique identifier for the access code. - */ - public string|null $access_code_id, - /** - * Indicates that Seam cannot convert this unmanaged access code to a managed access code. Some providers do not support management of unmanaged access codes through API integrations. - */ - public bool|null $cannot_be_managed, - /** - * Indicates that Seam cannot delete this unmanaged access code through the provider. If this access code needs to be deleted, it will only be possible from the manufacturer app. - */ - public bool|null $cannot_delete_unmanaged_access_code, - /** - * Code used for access. Typically, a numeric or alphanumeric string. - */ - public string|null $code, - /** - * Date and time at which the access code was created. - */ - public string|null $created_at, - /** - * Unique identifier for the device associated with the access code. - */ - public string|null $device_id, - /** - * Metadata for a dormakaba Oracode unmanaged access code. Only present for unmanaged access codes from dormakaba Oracode devices. - */ - public UnmanagedAccessCodeDormakabaOracodeMetadata|null $dormakaba_oracode_metadata, - /** - * Date and time after which the time-bound access code becomes inactive. - */ - public string|null $ends_at, - /** - * Errors associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - */ - public array $errors, - /** - * Indicates that Seam does not manage the access code. - */ - public bool|null $is_managed, - /** - * Name of the access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as `first_name` and `last_name`. To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called `appearance`. This is an object with a `name` property and, optionally, `first_name` and `last_name` properties (for providers that break down a name into components). - */ - public string|null $name, - /** - * Date and time at which the time-bound access code becomes active. - */ - public string|null $starts_at, - /** - * Current status of the access code within the operational lifecycle. `set` indicates that the code is active and operational. `unset` indicates that the code exists on the provider but is not usable on the device. - */ - public string|null $status, - /** - * Type of the access code. `ongoing` access codes are active continuously until deactivated manually. `time_bound` access codes have a specific duration. - */ - public string|null $type, - /** - * Warnings associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - */ - public array $warnings, - /** - * Unique identifier for the Seam workspace associated with the access code. - */ - public string|null $workspace_id, - ) {} + ? \Seam\Resources\UnmanagedAccessCode\DormakabaOracodeMetadata::from_json( + $json->dormakaba_oracode_metadata, + ) + : null, + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier for the access code. + */ + public string|null $access_code_id, + /** + * Code used for access. Typically, a numeric or alphanumeric string. + */ + public string|null $code, + /** + * Date and time at which the access code was created. + */ + public string|null $created_at, + /** + * Unique identifier for the device associated with the access code. + */ + public string|null $device_id, + /** + * Errors associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). + * + * @var list<\Seam\Resources\UnmanagedAccessCode\Errors> + */ + public array $errors, + /** + * Indicates that Seam does not manage the access code. + */ + public false|null $is_managed, + /** + * Name of the access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as `first_name` and `last_name`. To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called `appearance`. This is an object with a `name` property and, optionally, `first_name` and `last_name` properties (for providers that break down a name into components). + */ + public string|null $name, + /** + * Current status of the access code within the operational lifecycle. `set` indicates that the code is active and operational. `unset` indicates that the code exists on the provider but is not usable on the device. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Status>|string|null + */ + public string|null $status, + /** + * Type of the access code. `ongoing` access codes are active continuously until deactivated manually. `time_bound` access codes have a specific duration. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Type>|string|null + */ + public string|null $type, + /** + * Warnings associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). + * + * @var list<\Seam\Resources\UnmanagedAccessCode\Warnings> + */ + public array $warnings, + /** + * Unique identifier for the Seam workspace associated with the access code. + */ + public string|null $workspace_id, + /** + * Indicates that Seam cannot convert this unmanaged access code to a managed access code. Some providers do not support management of unmanaged access codes through API integrations. + */ + public true|null $cannot_be_managed = null, + /** + * Indicates that Seam cannot delete this unmanaged access code through the provider. If this access code needs to be deleted, it will only be possible from the manufacturer app. + */ + public true|null $cannot_delete_unmanaged_access_code = null, + /** + * Metadata for a dormakaba Oracode unmanaged access code. Only present for unmanaged access codes from dormakaba Oracode devices. + */ + public \Seam\Resources\UnmanagedAccessCode\DormakabaOracodeMetadata|null $dormakaba_oracode_metadata = null, + /** + * Date and time after which the time-bound access code becomes inactive. + */ + public string|null $ends_at = null, + /** + * Date and time at which the time-bound access code becomes active. + */ + public string|null $starts_at = null, + ) {} + } +} + +namespace Seam\Resources\UnmanagedAccessCode { + /** + * Metadata for a dormakaba Oracode unmanaged access code. Only present for unmanaged access codes from dormakaba Oracode devices. + */ + class DormakabaOracodeMetadata + { + public static function from_json( + mixed $json, + ): DormakabaOracodeMetadata|null { + if (!$json) { + return null; + } + return new self( + is_cancellable: $json->is_cancellable ?? null, + is_early_checkin_able: $json->is_early_checkin_able ?? null, + is_extendable: $json->is_extendable ?? null, + is_overridable: $json->is_overridable ?? null, + site_name: $json->site_name ?? null, + stay_id: $json->stay_id ?? null, + user_level_id: $json->user_level_id ?? null, + user_level_name: $json->user_level_name ?? null, + ); + } + + public function __construct( + /** + * Indicates whether the stay can be cancelled via the Dormakaba Oracode API. + */ + public bool|null $is_cancellable = null, + /** + * Indicates whether early check-in is available for this stay. + */ + public bool|null $is_early_checkin_able = null, + /** + * Indicates whether the stay can be extended via the Dormakaba Oracode API. + */ + public bool|null $is_extendable = null, + /** + * Indicates whether the access code can be overridden. When false, the maximum number of overrides has been reached. + */ + public bool|null $is_overridable = null, + /** + * Dormakaba Oracode site name associated with this access code. + */ + public string|null $site_name = null, + /** + * Dormakaba Oracode stay ID associated with this access code. + */ + public float|null $stay_id = null, + /** + * Dormakaba Oracode user level ID associated with this access code. + */ + public string|null $user_level_id = null, + /** + * Dormakaba Oracode user level name associated with this access code. + */ + public string|null $user_level_name = null, + ) {} + } + + /** + * Errors associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::PROVIDER_ISSUE + => \Seam\Resources\UnmanagedAccessCode\Errors\ProviderIssue::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::FAILED_TO_SET_ON_DEVICE + => \Seam\Resources\UnmanagedAccessCode\Errors\FailedToSetOnDevice::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::FAILED_TO_REMOVE_FROM_DEVICE + => \Seam\Resources\UnmanagedAccessCode\Errors\FailedToRemoveFromDevice::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::DUPLICATE_CODE_ON_DEVICE + => \Seam\Resources\UnmanagedAccessCode\Errors\DuplicateCodeOnDevice::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::NO_SPACE_FOR_ACCESS_CODE_ON_DEVICE + => \Seam\Resources\UnmanagedAccessCode\Errors\NoSpaceForAccessCodeOnDevice::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::CONFLICTING_EXTERNAL_MODIFICATION + => \Seam\Resources\UnmanagedAccessCode\Errors\ConflictingExternalModification::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::ACCESS_CODE_INACTIVE + => \Seam\Resources\UnmanagedAccessCode\Errors\AccessCodeInactive::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::ACCOUNT_DISCONNECTED + => \Seam\Resources\UnmanagedAccessCode\Errors\AccountDisconnected::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED + => \Seam\Resources\UnmanagedAccessCode\Errors\SaltoKsSubscriptionLimitExceeded::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::INSUFFICIENT_PERMISSIONS + => \Seam\Resources\UnmanagedAccessCode\Errors\InsufficientPermissions::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::DORMAKABA_SITES_DISCONNECTED + => \Seam\Resources\UnmanagedAccessCode\Errors\DormakabaSitesDisconnected::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::DEVICE_OFFLINE + => \Seam\Resources\UnmanagedAccessCode\Errors\DeviceOffline::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::DEVICE_REMOVED + => \Seam\Resources\UnmanagedAccessCode\Errors\DeviceRemoved::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::HUB_DISCONNECTED + => \Seam\Resources\UnmanagedAccessCode\Errors\HubDisconnected::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::DEVICE_DISCONNECTED + => \Seam\Resources\UnmanagedAccessCode\Errors\DeviceDisconnected::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::EMPTY_BACKUP_ACCESS_CODE_POOL + => \Seam\Resources\UnmanagedAccessCode\Errors\EmptyBackupAccessCodePool::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::AUGUST_LOCK_NOT_AUTHORIZED + => \Seam\Resources\UnmanagedAccessCode\Errors\AugustLockNotAuthorized::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::MISSING_DEVICE_CREDENTIALS + => \Seam\Resources\UnmanagedAccessCode\Errors\MissingDeviceCredentials::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::AUXILIARY_HEAT_RUNNING + => \Seam\Resources\UnmanagedAccessCode\Errors\AuxiliaryHeatRunning::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::SUBSCRIPTION_REQUIRED + => \Seam\Resources\UnmanagedAccessCode\Errors\SubscriptionRequired::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode::BRIDGE_DISCONNECTED + => \Seam\Resources\UnmanagedAccessCode\Errors\BridgeDisconnected::from_json( + $json, + ), + default => new self( + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Warnings associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::CODE_ROTATES_PERIODICALLY + => \Seam\Resources\UnmanagedAccessCode\Warnings\CodeRotatesPeriodically::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::TIME_FRAME_ADJUSTED_FOR_UNKNOWN_TIME_ZONE + => \Seam\Resources\UnmanagedAccessCode\Warnings\TimeFrameAdjustedForUnknownTimeZone::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::EXTERNAL_MODIFICATION_IN_EFFECT + => \Seam\Resources\UnmanagedAccessCode\Warnings\ExternalModificationInEffect::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::DELAY_IN_SETTING_ON_DEVICE + => \Seam\Resources\UnmanagedAccessCode\Warnings\DelayInSettingOnDevice::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::DELAY_IN_REMOVING_FROM_DEVICE + => \Seam\Resources\UnmanagedAccessCode\Warnings\DelayInRemovingFromDevice::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::THIRD_PARTY_INTEGRATION_DETECTED + => \Seam\Resources\UnmanagedAccessCode\Warnings\ThirdPartyIntegrationDetected::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::IGLOO_ALGOPIN_MUST_BE_USED_WITHIN_24_HOURS + => \Seam\Resources\UnmanagedAccessCode\Warnings\IglooAlgopinMustBeUsedWithin_24Hours::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::MANAGEMENT_TRANSFERRED + => \Seam\Resources\UnmanagedAccessCode\Warnings\ManagementTransferred::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::USING_BACKUP_ACCESS_CODE + => \Seam\Resources\UnmanagedAccessCode\Warnings\UsingBackupAccessCode::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\UnmanagedAccessCode\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode::UNKNOWN_ISSUE_WITH_ACCESS_CODE + => \Seam\Resources\UnmanagedAccessCode\Warnings\UnknownIssueWithAccessCode::from_json( + $json, + ), + default => new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ), + }; + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at = null, + ) {} + } + + enum Status: string + { + case SET = "set"; + case VALUE_UNSET = "unset"; + } + + enum Type: string + { + case TIME_BOUND = "time_bound"; + case ONGOING = "ongoing"; + } } -/** - * Metadata for a dormakaba Oracode unmanaged access code. Only present for unmanaged access codes from dormakaba Oracode devices. - */ -class UnmanagedAccessCodeDormakabaOracodeMetadata -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessCodeDormakabaOracodeMetadata|null { - if (!$json) { - return null; - } - return new self( - is_cancellable: $json->is_cancellable ?? null, - is_early_checkin_able: $json->is_early_checkin_able ?? null, - is_extendable: $json->is_extendable ?? null, - is_overridable: $json->is_overridable ?? null, - site_name: $json->site_name ?? null, - stay_id: $json->stay_id ?? null, - user_level_id: $json->user_level_id ?? null, - user_level_name: $json->user_level_name ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the stay can be cancelled via the Dormakaba Oracode API. - */ - public bool|null $is_cancellable, - /** - * Indicates whether early check-in is available for this stay. - */ - public bool|null $is_early_checkin_able, - /** - * Indicates whether the stay can be extended via the Dormakaba Oracode API. - */ - public bool|null $is_extendable, - /** - * Indicates whether the access code can be overridden. When false, the maximum number of overrides has been reached. - */ - public bool|null $is_overridable, - /** - * Dormakaba Oracode site name associated with this access code. - */ - public string|null $site_name, - /** - * Dormakaba Oracode stay ID associated with this access code. - */ - public float|null $stay_id, - /** - * Dormakaba Oracode user level ID associated with this access code. - */ - public string|null $user_level_id, - /** - * Dormakaba Oracode user level name associated with this access code. - */ - public string|null $user_level_name, - ) {} +namespace Seam\Resources\UnmanagedAccessCode\Errors { + /** + * Indicates a provider-specific issue that prevents the access code from being set or managed. Check the error message for details. + */ + final class ProviderIssue extends \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): ProviderIssue|null + { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Failed to set code on device. + */ + final class FailedToSetOnDevice extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): FailedToSetOnDevice|null + { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Failed to remove code from device. + */ + final class FailedToRemoveFromDevice extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): FailedToRemoveFromDevice|null { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Duplicate access code detected on device. + */ + final class DuplicateCodeOnDevice extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): DuplicateCodeOnDevice|null { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + managed_access_code_id: $json->managed_access_code_id ?? null, + unmanaged_access_code_id: $json->unmanaged_access_code_id ?? + null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + /** + * ID of the managed access code that conflicts with this managed access code, when Seam can identify it. + */ + public string|null $managed_access_code_id = null, + /** + * ID of the unmanaged access code that conflicts with this managed access code, when Seam can identify it. + */ + public string|null $unmanaged_access_code_id = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * No space for access code on device. + */ + final class NoSpaceForAccessCodeOnDevice extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): NoSpaceForAccessCodeOnDevice|null { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Code was modified or removed externally after Seam successfully set it on the device. The external change conflicts with the state that Seam is trying to apply, so Seam will attempt to set the code on the device again. + */ + final class ConflictingExternalModification extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): ConflictingExternalModification|null { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + change_type: $json->change_type ?? null, + created_at: $json->created_at ?? null, + modified_fields: array_map( + fn( + $m, + ) => \Seam\Resources\UnmanagedAccessCode\Errors\ConflictingExternalModification\ModifiedFields::from_json( + $m, + ), + $json->modified_fields ?? [], + ), + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates the type of external modification. `modified` means the code's PIN or schedule was changed. `removed` means the code was deleted from the device. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ConflictingExternalModification\ChangeType>|string|null + */ + public string|null $change_type = null, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + /** + * List of fields that were changed externally, with their previous and new values. + * + * @var list<\Seam\Resources\UnmanagedAccessCode\Errors\ConflictingExternalModification\ModifiedFields>|null + */ + public array|null $modified_fields = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the access code is disabled or inactive on the device. The code exists but will not grant access until re-enabled. + */ + final class AccessCodeInactive extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): AccessCodeInactive|null + { + if (!$json) { + return null; + } + return new self( + error_code: $json->error_code ?? null, + is_access_code_error: $json->is_access_code_error ?? null, + message: $json->message ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that this is an access code error. + */ + public true|null $is_access_code_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the account is disconnected. + */ + final class AccountDisconnected extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): AccountDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the Salto site user limit has been reached. + */ + final class SaltoKsSubscriptionLimitExceeded extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that Seam's integration user does not have sufficient permissions on the provider's system to which this device belongs, so Seam cannot manage access codes or unlock the device. See the error message for specifics, then either reauthorize the connected account in Seam or grant the integration user the required permissions in the provider's system. + */ + final class InsufficientPermissions extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): InsufficientPermissions|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that one or more dormakaba sites associated with the connected account could not be connected. Contact dormakaba support. + */ + final class DormakabaSitesDisconnected extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): DormakabaSitesDisconnected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the device is offline. + */ + final class DeviceOffline extends \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): DeviceOffline|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the device has been removed. + */ + final class DeviceRemoved extends \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): DeviceRemoved|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the hub is disconnected. + */ + final class HubDisconnected extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): HubDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the device is disconnected. + */ + final class DeviceDisconnected extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): DeviceDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) is empty. + */ + final class EmptyBackupAccessCodePool extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): EmptyBackupAccessCodePool|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the user is not authorized to use the August lock. + */ + final class AugustLockNotAuthorized extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): AugustLockNotAuthorized|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that device credentials are missing. + */ + final class MissingDeviceCredentials extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json( + mixed $json, + ): MissingDeviceCredentials|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the auxiliary heat is running. + */ + final class AuxiliaryHeatRunning extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): AuxiliaryHeatRunning|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that a subscription is required to connect. + */ + final class SubscriptionRequired extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): SubscriptionRequired|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + /** + * Indicates that the Seam API cannot communicate with [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge), for example, if the Seam Bridge executable has stopped or if the computer running the Seam Bridge executable is offline. See also [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system#acs_system-errors-seam_bridge_disconnected). + */ + final class BridgeDisconnected extends + \Seam\Resources\UnmanagedAccessCode\Errors + { + public static function from_json(mixed $json): BridgeDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + public bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + public bool|null $is_connected_account_error = null, + ) { + parent::__construct(error_code: $error_code, message: $message); + } + } + + enum ErrorCode: string + { + case PROVIDER_ISSUE = "provider_issue"; + case FAILED_TO_SET_ON_DEVICE = "failed_to_set_on_device"; + case FAILED_TO_REMOVE_FROM_DEVICE = "failed_to_remove_from_device"; + case DUPLICATE_CODE_ON_DEVICE = "duplicate_code_on_device"; + case NO_SPACE_FOR_ACCESS_CODE_ON_DEVICE = "no_space_for_access_code_on_device"; + case CONFLICTING_EXTERNAL_MODIFICATION = "conflicting_external_modification"; + case ACCESS_CODE_INACTIVE = "access_code_inactive"; + case ACCOUNT_DISCONNECTED = "account_disconnected"; + case SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED = "salto_ks_subscription_limit_exceeded"; + case INSUFFICIENT_PERMISSIONS = "insufficient_permissions"; + case DORMAKABA_SITES_DISCONNECTED = "dormakaba_sites_disconnected"; + case DEVICE_OFFLINE = "device_offline"; + case DEVICE_REMOVED = "device_removed"; + case HUB_DISCONNECTED = "hub_disconnected"; + case DEVICE_DISCONNECTED = "device_disconnected"; + case EMPTY_BACKUP_ACCESS_CODE_POOL = "empty_backup_access_code_pool"; + case AUGUST_LOCK_NOT_AUTHORIZED = "august_lock_not_authorized"; + case MISSING_DEVICE_CREDENTIALS = "missing_device_credentials"; + case AUXILIARY_HEAT_RUNNING = "auxiliary_heat_running"; + case SUBSCRIPTION_REQUIRED = "subscription_required"; + case BRIDGE_DISCONNECTED = "bridge_disconnected"; + } } -/** - * Errors associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - */ -class UnmanagedAccessCodeErrors -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessCodeErrors|null { - if (!$json) { - return null; - } - return new self( - change_type: $json->change_type ?? null, - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - is_access_code_error: $json->is_access_code_error ?? null, - is_bridge_error: $json->is_bridge_error ?? null, - is_connected_account_error: $json->is_connected_account_error ?? - null, - is_device_error: $json->is_device_error ?? null, - managed_access_code_id: $json->managed_access_code_id ?? null, - message: $json->message ?? null, - modified_fields: array_map( - fn($m) => UnmanagedAccessCodeModifiedFields::from_json($m), - $json->modified_fields ?? [], - ), - unmanaged_access_code_id: $json->unmanaged_access_code_id ?? null, - ); - } - - public function __construct( - /** - * Indicates the type of external modification. `modified` means the code's PIN or schedule was changed. `removed` means the code was deleted from the device. - */ - public string|null $change_type, - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Indicates that this is an access code error. - */ - public bool|null $is_access_code_error, - /** - * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). - */ - public bool|null $is_bridge_error, - /** - * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. - */ - public bool|null $is_connected_account_error, - /** - * Indicates that the error is not a device error. - */ - public bool|null $is_device_error, - /** - * ID of the managed access code that conflicts with this managed access code, when Seam can identify it. - */ - public string|null $managed_access_code_id, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * List of fields that were changed externally, with their previous and new values. - */ - public array $modified_fields, - /** - * ID of the unmanaged access code that conflicts with this managed access code, when Seam can identify it. - */ - public string|null $unmanaged_access_code_id, - ) {} +namespace Seam\Resources\UnmanagedAccessCode\Errors\ConflictingExternalModification { + /** + * List of fields that were changed externally, with their previous and new values. + */ + class ModifiedFields + { + public static function from_json(mixed $json): ModifiedFields|null + { + if (!$json) { + return null; + } + return new self( + field: $json->field ?? null, + from: $json->from ?? null, + to: $json->to ?? null, + ); + } + + public function __construct( + /** + * The name of the field that was changed (e.g. `code`, `starts_at`, `ends_at`). + */ + public string|null $field, + /** + * The previous value of the field. + */ + public string|null $from, + /** + * The new value of the field. + */ + public string|null $to, + ) {} + } + + enum ChangeType: string + { + case MODIFIED = "modified"; + case REMOVED = "removed"; + } } -/** - * List of fields that were changed externally, with their previous and new values. - */ -class UnmanagedAccessCodeModifiedFields -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessCodeModifiedFields|null { - if (!$json) { - return null; - } - return new self( - field: $json->field ?? null, - from: $json->from ?? null, - to: $json->to ?? null, - ); - } - - public function __construct( - /** - * The name of the field that was changed (e.g. `code`, `starts_at`, `ends_at`). - */ - public string|null $field, - /** - * The previous value of the field. - */ - public string|null $from, - /** - * The new value of the field. - */ - public string|null $to, - ) {} +namespace Seam\Resources\UnmanagedAccessCode\Warnings { + /** + * The access code's PIN rotates periodically when the code is renewed. Retrieve the latest code before each use. + */ + final class CodeRotatesPeriodically extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): CodeRotatesPeriodically|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * The device's time zone is unknown and this code's time frame crosses a daylight-saving transition in at least one plausible time zone. A 1-hour safety buffer has been applied to the side of the time frame affected by the transition (`ends_at` for spring-forward, `starts_at` for fall-back) so the code stays active through the shift — the code may be usable up to 1 hour beyond your requested window. Set the device's time zone via `/devices/report_provider_metadata` to clear the buffer and guarantee exact handling. + */ + final class TimeFrameAdjustedForUnknownTimeZone extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): TimeFrameAdjustedForUnknownTimeZone|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Code was modified or removed externally after Seam successfully set it on the device. External modification is allowed for this code, so the externally modified state is being honored. + */ + final class ExternalModificationInEffect extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): ExternalModificationInEffect|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + change_type: $json->change_type ?? null, + created_at: $json->created_at ?? null, + modified_fields: array_map( + fn( + $m, + ) => \Seam\Resources\UnmanagedAccessCode\Warnings\ExternalModificationInEffect\ModifiedFields::from_json( + $m, + ), + $json->modified_fields ?? [], + ), + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Indicates the type of external modification. `modified` means the code's PIN or schedule was changed. `removed` means the code was deleted from the device. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\ExternalModificationInEffect\ChangeType>|string|null + */ + public string|null $change_type = null, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + /** + * List of fields that were changed externally, with their previous and new values. + * + * @var list<\Seam\Resources\UnmanagedAccessCode\Warnings\ExternalModificationInEffect\ModifiedFields>|null + */ + public array|null $modified_fields = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Delay in setting code on device. + */ + final class DelayInSettingOnDevice extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): DelayInSettingOnDevice|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Delay in removing code from device. + */ + final class DelayInRemovingFromDevice extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): DelayInRemovingFromDevice|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Third-party integration detected that may cause access codes to fail. + */ + final class ThirdPartyIntegrationDetected extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): ThirdPartyIntegrationDetected|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Algopins must be used within 24 hours. + */ + final class IglooAlgopinMustBeUsedWithin_24Hours extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): IglooAlgopinMustBeUsedWithin_24Hours|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Management was transferred to another workspace. + */ + final class ManagementTransferred extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): ManagementTransferred|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * A backup access code has been pulled and is being used in place of this access code. + */ + final class UsingBackupAccessCode extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): UsingBackupAccessCode|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Access code is being deleted. + */ + final class BeingDeleted extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * An unknown issue occurred with the access code. + */ + final class UnknownIssueWithAccessCode extends + \Seam\Resources\UnmanagedAccessCode\Warnings + { + public static function from_json( + mixed $json, + ): UnknownIssueWithAccessCode|null { + if (!$json) { + return null; + } + return new self( + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + created_at: $json->created_at ?? null, + ); + } + + public function __construct( + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessCode\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case CODE_ROTATES_PERIODICALLY = "code_rotates_periodically"; + case TIME_FRAME_ADJUSTED_FOR_UNKNOWN_TIME_ZONE = "time_frame_adjusted_for_unknown_time_zone"; + case EXTERNAL_MODIFICATION_IN_EFFECT = "external_modification_in_effect"; + case DELAY_IN_SETTING_ON_DEVICE = "delay_in_setting_on_device"; + case DELAY_IN_REMOVING_FROM_DEVICE = "delay_in_removing_from_device"; + case THIRD_PARTY_INTEGRATION_DETECTED = "third_party_integration_detected"; + case IGLOO_ALGOPIN_MUST_BE_USED_WITHIN_24_HOURS = "igloo_algopin_must_be_used_within_24_hours"; + case MANAGEMENT_TRANSFERRED = "management_transferred"; + case USING_BACKUP_ACCESS_CODE = "using_backup_access_code"; + case BEING_DELETED = "being_deleted"; + case UNKNOWN_ISSUE_WITH_ACCESS_CODE = "unknown_issue_with_access_code"; + } } -/** - * Warnings associated with the [access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes). - */ -class UnmanagedAccessCodeWarnings -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessCodeWarnings|null { - if (!$json) { - return null; - } - return new self( - change_type: $json->change_type ?? null, - created_at: $json->created_at ?? null, - message: $json->message ?? null, - modified_fields: array_map( - fn($m) => UnmanagedAccessCodeModifiedFields::from_json($m), - $json->modified_fields ?? [], - ), - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Indicates the type of external modification. `modified` means the code's PIN or schedule was changed. `removed` means the code was deleted from the device. - */ - public string|null $change_type, - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * List of fields that were changed externally, with their previous and new values. - */ - public array $modified_fields, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\UnmanagedAccessCode\Warnings\ExternalModificationInEffect { + /** + * List of fields that were changed externally, with their previous and new values. + */ + class ModifiedFields + { + public static function from_json(mixed $json): ModifiedFields|null + { + if (!$json) { + return null; + } + return new self( + field: $json->field ?? null, + from: $json->from ?? null, + to: $json->to ?? null, + ); + } + + public function __construct( + /** + * The name of the field that was changed (e.g. `code`, `starts_at`, `ends_at`). + */ + public string|null $field, + /** + * The previous value of the field. + */ + public string|null $from, + /** + * The new value of the field. + */ + public string|null $to, + ) {} + } + + enum ChangeType: string + { + case MODIFIED = "modified"; + case REMOVED = "removed"; + } } diff --git a/src/Resources/UnmanagedAccessGrant.php b/src/Resources/UnmanagedAccessGrant.php index 6161249d..dd1d7703 100644 --- a/src/Resources/UnmanagedAccessGrant.php +++ b/src/Resources/UnmanagedAccessGrant.php @@ -1,428 +1,1100 @@ access_grant_id ?? null, + access_method_ids: $json->access_method_ids ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + ends_at: $json->ends_at ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\UnmanagedAccessGrant\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + location_ids: $json->location_ids ?? null, + name: $json->name ?? null, + pending_mutations: array_map( + fn( + $p, + ) => \Seam\Resources\UnmanagedAccessGrant\PendingMutations::from_json( + $p, + ), + $json->pending_mutations ?? [], + ), + requested_access_methods: array_map( + fn( + $r, + ) => \Seam\Resources\UnmanagedAccessGrant\RequestedAccessMethods::from_json( + $r, + ), + $json->requested_access_methods ?? [], + ), + space_ids: $json->space_ids ?? null, + starts_at: $json->starts_at ?? null, + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\UnmanagedAccessGrant\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + reservation_key: $json->reservation_key ?? null, + user_identity_id: $json->user_identity_id ?? null, + ); } - return new self( - access_grant_id: $json->access_grant_id ?? null, - access_method_ids: $json->access_method_ids ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - ends_at: $json->ends_at ?? null, - errors: array_map( - fn($e) => UnmanagedAccessGrantErrors::from_json($e), - $json->errors ?? [], - ), - location_ids: $json->location_ids ?? null, - name: $json->name ?? null, - pending_mutations: array_map( - fn($p) => UnmanagedAccessGrantPendingMutations::from_json($p), - $json->pending_mutations ?? [], - ), - requested_access_methods: array_map( - fn($r) => UnmanagedAccessGrantRequestedAccessMethods::from_json( - $r, + + public function __construct( + /** + * ID of the Access Grant. + */ + public string|null $access_grant_id, + /** + * IDs of the access methods created for the Access Grant. + * + * @var list|null + */ + public array|null $access_method_ids, + /** + * Date and time at which the Access Grant was created. + */ + public string|null $created_at, + /** + * Display name of the Access Grant. + */ + public string|null $display_name, + /** + * Date and time at which the Access Grant ends. + */ + public string|null $ends_at, + /** + * Errors associated with the [access grant](https://docs.seam.co/use-cases/granting-access). + * + * @var list<\Seam\Resources\UnmanagedAccessGrant\Errors> + */ + public array $errors, + /** + * @var list|null + * @deprecated Use `space_ids`. + */ + public array|null $location_ids, + /** + * Name of the Access Grant. If not provided, the display name will be computed. + */ + public string|null $name, + /** + * List of pending mutations for the access grant. This shows updates that are in progress. + * + * @var list<\Seam\Resources\UnmanagedAccessGrant\PendingMutations> + */ + public array $pending_mutations, + /** + * Access methods that the user requested for the Access Grant. + * + * @var list<\Seam\Resources\UnmanagedAccessGrant\RequestedAccessMethods> + */ + public array $requested_access_methods, + /** + * IDs of the spaces to which the Access Grant gives access. + * + * @var list|null + */ + public array|null $space_ids, + /** + * Date and time at which the Access Grant starts. + */ + public string|null $starts_at, + /** + * Warnings associated with the [access grant](https://docs.seam.co/use-cases/granting-access). + * + * @var list<\Seam\Resources\UnmanagedAccessGrant\Warnings> + */ + public array $warnings, + /** + * ID of the Seam workspace associated with the Access Grant. + */ + public string|null $workspace_id, + /** + * Reservation key for the access grant. + */ + public string|null $reservation_key = null, + /** + * ID of user identity to which the Access Grant gives access. + */ + public string|null $user_identity_id = null, + ) {} + } +} + +namespace Seam\Resources\UnmanagedAccessGrant { + /** + * Errors associated with the [access grant](https://docs.seam.co/use-cases/granting-access). Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\UnmanagedAccessGrant\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedAccessGrant\Errors\ErrorCode::CANNOT_CREATE_REQUESTED_ACCESS_METHODS + => \Seam\Resources\UnmanagedAccessGrant\Errors\CannotCreateRequestedAccessMethods::from_json( + $json, ), - $json->requested_access_methods ?? [], - ), - reservation_key: $json->reservation_key ?? null, - space_ids: $json->space_ids ?? null, - starts_at: $json->starts_at ?? null, - user_identity_id: $json->user_identity_id ?? null, - warnings: array_map( - fn($w) => UnmanagedAccessGrantWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + missing_device_ids: $json->missing_device_ids ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. + * + * @var list|null + */ + public array|null $missing_device_ids = null, + ) {} } - public function __construct( - /** - * ID of the Access Grant. - */ - public string|null $access_grant_id, - /** - * IDs of the access methods created for the Access Grant. - */ - public array|null $access_method_ids, - /** - * Date and time at which the Access Grant was created. - */ - public string|null $created_at, - /** - * Display name of the Access Grant. - */ - public string|null $display_name, - /** - * Date and time at which the Access Grant ends. - */ - public string|null $ends_at, - /** - * Errors associated with the [access grant](https://docs.seam.co/use-cases/granting-access). - */ - public array $errors, - /** - * @deprecated Use `space_ids`. - */ - public array|null $location_ids, - /** - * Name of the Access Grant. If not provided, the display name will be computed. - */ - public string|null $name, - /** - * List of pending mutations for the access grant. This shows updates that are in progress. - */ - public array $pending_mutations, - /** - * Access methods that the user requested for the Access Grant. - */ - public array $requested_access_methods, - /** - * Reservation key for the access grant. - */ - public string|null $reservation_key, - /** - * IDs of the spaces to which the Access Grant gives access. - */ - public array|null $space_ids, - /** - * Date and time at which the Access Grant starts. - */ - public string|null $starts_at, - /** - * ID of user identity to which the Access Grant gives access. - */ - public string|null $user_identity_id, - /** - * Warnings associated with the [access grant](https://docs.seam.co/use-cases/granting-access). - */ - public array $warnings, - /** - * ID of the Seam workspace associated with the Access Grant. - */ - public string|null $workspace_id, - ) {} -} + /** + * List of pending mutations for the access grant. This shows updates that are in progress. Known mutation_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class PendingMutations + { + public static function from_json(mixed $json): PendingMutations|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->mutation_code ?? null) + ? \Seam\Resources\UnmanagedAccessGrant\PendingMutations\MutationCode::tryFrom( + $json->mutation_code, + ) + : null; -/** - * Errors associated with the [access grant](https://docs.seam.co/use-cases/granting-access). - */ -class UnmanagedAccessGrantErrors -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessGrantErrors|null { - if (!$json) { - return null; + return match ($discriminant) { + \Seam\Resources\UnmanagedAccessGrant\PendingMutations\MutationCode::UPDATING_SPACES + => \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingSpaces::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessGrant\PendingMutations\MutationCode::UPDATING_ACCESS_TIMES + => \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingAccessTimes::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + public string|null $created_at, + /** + * Detailed description of the mutation. + */ + public string|null $message, + /** + * Mutation code to indicate that Seam is in the process of updating the spaces (devices) associated with this access grant. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\PendingMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + ) {} + } + + /** + * Access methods that the user requested for the Access Grant. + */ + class RequestedAccessMethods + { + public static function from_json( + mixed $json, + ): RequestedAccessMethods|null { + if (!$json) { + return null; + } + return new self( + created_access_method_ids: $json->created_access_method_ids ?? + null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + mode: $json->mode ?? null, + code: $json->code ?? null, + instant_key_max_use_count: $json->instant_key_max_use_count ?? + null, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - missing_device_ids: $json->missing_device_ids ?? null, - ); + + public function __construct( + /** + * IDs of the access methods created for the requested access method. + * + * @var list|null + */ + public array|null $created_access_method_ids, + /** + * Date and time at which the requested access method was added to the Access Grant. + */ + public string|null $created_at, + /** + * Display name of the access method. + */ + public string|null $display_name, + /** + * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\RequestedAccessMethods\Mode>|string|null + */ + public string|null $mode, + /** + * Specific PIN code to use for this access method. Only applicable when mode is 'code'. + */ + public string|null $code = null, + /** + * Maximum number of times the instant key can be used. Only applicable when mode is 'mobile_key'. Defaults to 1 if not specified. + */ + public int|null $instant_key_max_use_count = null, + ) {} } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. - */ - public array|null $missing_device_ids, - ) {} + /** + * Warnings associated with the [access grant](https://docs.seam.co/use-cases/granting-access). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\UnmanagedAccessGrant\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode::UNDERPROVISIONED_ACCESS + => \Seam\Resources\UnmanagedAccessGrant\Warnings\UnderprovisionedAccess::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode::OVERPROVISIONED_ACCESS + => \Seam\Resources\UnmanagedAccessGrant\Warnings\OverprovisionedAccess::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode::UPDATING_ACCESS_TIMES + => \Seam\Resources\UnmanagedAccessGrant\Warnings\UpdatingAccessTimes::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode::REQUESTED_CODE_UNAVAILABLE + => \Seam\Resources\UnmanagedAccessGrant\Warnings\RequestedCodeUnavailable::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode::DEVICE_DOES_NOT_SUPPORT_ACCESS_CODES + => \Seam\Resources\UnmanagedAccessGrant\Warnings\DeviceDoesNotSupportAccessCodes::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode::DEVICE_TIME_CONSTRAINTS_VIOLATED + => \Seam\Resources\UnmanagedAccessGrant\Warnings\DeviceTimeConstraintsViolated::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } } -/** - * Devices whose access codes could not be revoked during reconciliation. Present when the provider does not support revoking an offline access code (e.g. Dormakaba oracode with exhausted override budget). - */ -class UnmanagedAccessGrantFailedDevices -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessGrantFailedDevices|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedAccessGrant\Errors { + /** + * Indicates that Seam could not create one or more of the requested access methods for the access grant. + */ + final class CannotCreateRequestedAccessMethods extends + \Seam\Resources\UnmanagedAccessGrant\Errors + { + public static function from_json( + mixed $json, + ): CannotCreateRequestedAccessMethods|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + missing_device_ids: $json->missing_device_ids ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * IDs of the devices that did not receive an access code at grant creation. Use these to identify which specific devices failed when the message reports a partial failure. + * + * @var list|null + */ + array|null $missing_device_ids = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + missing_device_ids: $missing_device_ids, + ); } - return new self( - device_id: $json->device_id ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); } - public function __construct( - /** - * Device whose access code could not be revoked. - */ - public string|null $device_id, - /** - * Reason the access code could not be revoked (e.g. `offline_access_code_not_revocable`). - */ - public string|null $error_code, - /** - * Human-readable description of why revocation failed. - */ - public string|null $message, - ) {} + enum ErrorCode: string + { + case CANNOT_CREATE_REQUESTED_ACCESS_METHODS = "cannot_create_requested_access_methods"; + } } -/** - * Previous location configuration. - */ -class UnmanagedAccessGrantFrom -{ - public static function from_json(mixed $json): UnmanagedAccessGrantFrom|null +namespace Seam\Resources\UnmanagedAccessGrant\PendingMutations { + /** + * Seam is in the process of updating the devices/spaces associated with this access grant. + */ + final class UpdatingSpaces extends + \Seam\Resources\UnmanagedAccessGrant\PendingMutations + { + public static function from_json(mixed $json): UpdatingSpaces|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingSpaces\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingSpaces\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous location configuration. + */ + public \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingSpaces\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of updating the spaces (devices) associated with this access grant. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New location configuration. + */ + public \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingSpaces\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of updating the access times for this access grant. + */ + final class UpdatingAccessTimes extends + \Seam\Resources\UnmanagedAccessGrant\PendingMutations { - if (!$json) { - return null; + public static function from_json(mixed $json): UpdatingAccessTimes|null + { + if (!$json) { + return null; + } + return new self( + access_method_ids: $json->access_method_ids ?? null, + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingAccessTimes\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingAccessTimes\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * IDs of the access methods being updated. + * + * @var list|null + */ + public array|null $access_method_ids, + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous access time configuration. + */ + public \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingAccessTimes\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of updating the spaces (devices) associated with this access grant. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access time configuration. + */ + public \Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingAccessTimes\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); } - return new self( - device_ids: $json->device_ids ?? null, - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); } - public function __construct( - /** - * Previous device IDs where access codes existed. - */ - public array|null $device_ids, - /** - * Previous end time for access. - */ - public string|null $ends_at, - /** - * Previous start time for access. - */ - public string|null $starts_at, - ) {} + enum MutationCode: string + { + case UPDATING_SPACES = "updating_spaces"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + } } -/** - * List of pending mutations for the access grant. This shows updates that are in progress. - */ -class UnmanagedAccessGrantPendingMutations -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessGrantPendingMutations|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingSpaces { + /** + * Previous location configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); } - return new self( - access_method_ids: $json->access_method_ids ?? null, - created_at: $json->created_at ?? null, - from: isset($json->from) - ? UnmanagedAccessGrantFrom::from_json($json->from) - : null, - message: $json->message ?? null, - mutation_code: $json->mutation_code ?? null, - to: isset($json->to) - ? UnmanagedAccessGrantTo::from_json($json->to) - : null, - ); + + public function __construct( + /** + * Previous device IDs where access codes existed. + * + * @var list|null + */ + public array|null $device_ids, + ) {} } - public function __construct( - /** - * IDs of the access methods being updated. - */ - public array|null $access_method_ids, - /** - * Date and time at which the mutation was created. - */ - public string|null $created_at, - /** - * Previous location configuration. - */ - public UnmanagedAccessGrantFrom|null $from, - /** - * Detailed description of the mutation. - */ - public string|null $message, - /** - * Mutation code to indicate that Seam is in the process of updating the spaces (devices) associated with this access grant. - */ - public string|null $mutation_code, - /** - * New location configuration. - */ - public UnmanagedAccessGrantTo|null $to, - ) {} + /** + * New location configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + device_ids: $json->device_ids ?? null, + common_code_key: $json->common_code_key ?? null, + ); + } + + public function __construct( + /** + * New device IDs where access codes should be created. + * + * @var list|null + */ + public array|null $device_ids, + /** + * Common code key to ensure PIN code reuse across devices. + */ + public string|null $common_code_key = null, + ) {} + } } -/** - * Access methods that the user requested for the Access Grant. - */ -class UnmanagedAccessGrantRequestedAccessMethods -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessGrantRequestedAccessMethods|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedAccessGrant\PendingMutations\UpdatingAccessTimes { + /** + * Previous access time configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); } - return new self( - code: $json->code ?? null, - created_access_method_ids: $json->created_access_method_ids ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - instant_key_max_use_count: $json->instant_key_max_use_count ?? null, - mode: $json->mode ?? null, - ); + + public function __construct( + /** + * Previous end time for access. + */ + public string|null $ends_at, + /** + * Previous start time for access. + */ + public string|null $starts_at, + ) {} } - public function __construct( - /** - * Specific PIN code to use for this access method. Only applicable when mode is 'code'. - */ - public string|null $code, - /** - * IDs of the access methods created for the requested access method. - */ - public array|null $created_access_method_ids, - /** - * Date and time at which the requested access method was added to the Access Grant. - */ - public string|null $created_at, - /** - * Display name of the access method. - */ - public string|null $display_name, - /** - * Maximum number of times the instant key can be used. Only applicable when mode is 'mobile_key'. Defaults to 1 if not specified. - */ - public int|null $instant_key_max_use_count, - /** - * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. - */ - public string|null $mode, - ) {} + /** + * New access time configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * New end time for access. + */ + public string|null $ends_at, + /** + * New start time for access. + */ + public string|null $starts_at, + ) {} + } } -/** - * New location configuration. - */ -class UnmanagedAccessGrantTo -{ - public static function from_json(mixed $json): UnmanagedAccessGrantTo|null +namespace Seam\Resources\UnmanagedAccessGrant\RequestedAccessMethods { + enum Mode: string { - if (!$json) { - return null; + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; + } +} + +namespace Seam\Resources\UnmanagedAccessGrant\Warnings { + /** + * Indicates that the [access grant](https://docs.seam.co/use-cases/granting-access) is being deleted. + */ + final class BeingDeleted extends + \Seam\Resources\UnmanagedAccessGrant\Warnings + { + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - common_code_key: $json->common_code_key ?? null, - device_ids: $json->device_ids ?? null, - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); } - public function __construct( - /** - * Common code key to ensure PIN code reuse across devices. - */ - public string|null $common_code_key, - /** - * New device IDs where access codes should be created. - */ - public array|null $device_ids, - /** - * New end time for access. - */ - public string|null $ends_at, - /** - * New start time for access. - */ - public string|null $starts_at, - ) {} + /** + * Indicates that the access grant should have access to more locations than it currently does. Access methods are being created for the missing locations. + */ + final class UnderprovisionedAccess extends + \Seam\Resources\UnmanagedAccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): UnderprovisionedAccess|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the access grant has access to locations it should not have. Access methods are being removed from the extra locations. + */ + final class OverprovisionedAccess extends + \Seam\Resources\UnmanagedAccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): OverprovisionedAccess|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + failed_devices: array_map( + fn( + $f, + ) => \Seam\Resources\UnmanagedAccessGrant\Warnings\OverprovisionedAccess\FailedDevices::from_json( + $f, + ), + $json->failed_devices ?? [], + ), + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * Devices whose access codes could not be revoked during reconciliation. Present when the provider does not support revoking an offline access code (e.g. Dormakaba oracode with exhausted override budget). + * + * @var list<\Seam\Resources\UnmanagedAccessGrant\Warnings\OverprovisionedAccess\FailedDevices>|null + */ + public array|null $failed_devices = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the access times for this [access grant](https://docs.seam.co/use-cases/granting-access) are being updated. + */ + final class UpdatingAccessTimes extends + \Seam\Resources\UnmanagedAccessGrant\Warnings + { + public static function from_json(mixed $json): UpdatingAccessTimes|null + { + if (!$json) { + return null; + } + return new self( + access_method_ids: $json->access_method_ids ?? null, + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * IDs of the access methods being updated. + * + * @var list|null + */ + public array|null $access_method_ids, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the requested PIN code was already in use on a device, so a different code was assigned. + */ + final class RequestedCodeUnavailable extends + \Seam\Resources\UnmanagedAccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): RequestedCodeUnavailable|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + message: $json->message ?? null, + new_code: $json->new_code ?? null, + original_code: $json->original_code ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * ID of the device where the requested code was unavailable. + */ + public string|null $device_id, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * The new PIN code that was assigned instead. + */ + public string|null $new_code, + /** + * The originally requested PIN code that was unavailable. + */ + public string|null $original_code, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a device in the access grant does not support access codes and was excluded from code materialization. + */ + final class DeviceDoesNotSupportAccessCodes extends + \Seam\Resources\UnmanagedAccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): DeviceDoesNotSupportAccessCodes|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * ID of the device that does not support access codes. + */ + public string|null $device_id, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a device in the access grant cannot program an access code for the grant's time range because of device-specific time constraints. + */ + final class DeviceTimeConstraintsViolated extends + \Seam\Resources\UnmanagedAccessGrant\Warnings + { + public static function from_json( + mixed $json, + ): DeviceTimeConstraintsViolated|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + device_id: $json->device_id ?? null, + message: $json->message ?? null, + reason: $json->reason ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * ID of the device whose time constraints the access grant violates. + */ + public string|null $device_id, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Specific reason why the grant's times are not programmable on the device. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\DeviceTimeConstraintsViolated\Reason>|string|null + */ + public string|null $reason, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessGrant\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case BEING_DELETED = "being_deleted"; + case UNDERPROVISIONED_ACCESS = "underprovisioned_access"; + case OVERPROVISIONED_ACCESS = "overprovisioned_access"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + case REQUESTED_CODE_UNAVAILABLE = "requested_code_unavailable"; + case DEVICE_DOES_NOT_SUPPORT_ACCESS_CODES = "device_does_not_support_access_codes"; + case DEVICE_TIME_CONSTRAINTS_VIOLATED = "device_time_constraints_violated"; + } } -/** - * Warnings associated with the [access grant](https://docs.seam.co/use-cases/granting-access). - */ -class UnmanagedAccessGrantWarnings -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessGrantWarnings|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedAccessGrant\Warnings\OverprovisionedAccess { + /** + * Devices whose access codes could not be revoked during reconciliation. Present when the provider does not support revoking an offline access code (e.g. Dormakaba oracode with exhausted override budget). + */ + class FailedDevices + { + public static function from_json(mixed $json): FailedDevices|null + { + if (!$json) { + return null; + } + return new self( + device_id: $json->device_id ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); } - return new self( - access_method_ids: $json->access_method_ids ?? null, - created_at: $json->created_at ?? null, - device_id: $json->device_id ?? null, - failed_devices: array_map( - fn($f) => UnmanagedAccessGrantFailedDevices::from_json($f), - $json->failed_devices ?? [], - ), - message: $json->message ?? null, - new_code: $json->new_code ?? null, - original_code: $json->original_code ?? null, - reason: $json->reason ?? null, - warning_code: $json->warning_code ?? null, - ); + + public function __construct( + /** + * Device whose access code could not be revoked. + */ + public string|null $device_id, + /** + * Reason the access code could not be revoked (e.g. `offline_access_code_not_revocable`). + */ + public string|null $error_code, + /** + * Human-readable description of why revocation failed. + */ + public string|null $message, + ) {} } +} - public function __construct( - /** - * IDs of the access methods being updated. - */ - public array|null $access_method_ids, - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * ID of the device where the requested code was unavailable. - */ - public string|null $device_id, - /** - * Devices whose access codes could not be revoked during reconciliation. Present when the provider does not support revoking an offline access code (e.g. Dormakaba oracode with exhausted override budget). - */ - public array $failed_devices, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * The new PIN code that was assigned instead. - */ - public string|null $new_code, - /** - * The originally requested PIN code that was unavailable. - */ - public string|null $original_code, - /** - * Specific reason why the grant's times are not programmable on the device. - */ - public string|null $reason, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\UnmanagedAccessGrant\Warnings\DeviceTimeConstraintsViolated { + enum Reason: string + { + case DURATION_EXCEEDS_MAX = "duration_exceeds_max"; + case TIMES_DO_NOT_MATCH_SLOTS = "times_do_not_match_slots"; + case ONGOING_NOT_SUPPORTED = "ongoing_not_supported"; + } } diff --git a/src/Resources/UnmanagedAccessMethod.php b/src/Resources/UnmanagedAccessMethod.php index b6ac79a7..f24a7e20 100644 --- a/src/Resources/UnmanagedAccessMethod.php +++ b/src/Resources/UnmanagedAccessMethod.php @@ -1,293 +1,874 @@ access_method_id ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\UnmanagedAccessMethod\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_issued: $json->is_issued ?? null, + issued_at: $json->issued_at ?? null, + mode: $json->mode ?? null, + pending_mutations: array_map( + fn( + $p, + ) => \Seam\Resources\UnmanagedAccessMethod\PendingMutations::from_json( + $p, + ), + $json->pending_mutations ?? [], + ), + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\UnmanagedAccessMethod\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + code: $json->code ?? null, + is_assignment_required: $json->is_assignment_required ?? null, + is_encoding_required: $json->is_encoding_required ?? null, + is_ready_for_assignment: $json->is_ready_for_assignment ?? null, + is_ready_for_encoding: $json->is_ready_for_encoding ?? null, + ); } - return new self( - access_method_id: $json->access_method_id ?? null, - code: $json->code ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - errors: array_map( - fn($e) => UnmanagedAccessMethodErrors::from_json($e), - $json->errors ?? [], - ), - is_assignment_required: $json->is_assignment_required ?? null, - is_encoding_required: $json->is_encoding_required ?? null, - is_issued: $json->is_issued ?? null, - is_ready_for_assignment: $json->is_ready_for_assignment ?? null, - is_ready_for_encoding: $json->is_ready_for_encoding ?? null, - issued_at: $json->issued_at ?? null, - mode: $json->mode ?? null, - pending_mutations: array_map( - fn($p) => UnmanagedAccessMethodPendingMutations::from_json($p), - $json->pending_mutations ?? [], - ), - warnings: array_map( - fn($w) => UnmanagedAccessMethodWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); + + public function __construct( + /** + * ID of the access method. + */ + public string|null $access_method_id, + /** + * Date and time at which the access method was created. + */ + public string|null $created_at, + /** + * Display name of the access method. + */ + public string|null $display_name, + /** + * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). + * + * @var list<\Seam\Resources\UnmanagedAccessMethod\Errors> + */ + public array $errors, + /** + * Indicates whether the access method has been issued. + */ + public bool|null $is_issued, + /** + * Date and time at which the access method was issued. + */ + public string|null $issued_at, + /** + * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\Mode>|string|null + */ + public string|null $mode, + /** + * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. + * + * @var list<\Seam\Resources\UnmanagedAccessMethod\PendingMutations> + */ + public array $pending_mutations, + /** + * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). + * + * @var list<\Seam\Resources\UnmanagedAccessMethod\Warnings> + */ + public array $warnings, + /** + * ID of the Seam workspace associated with the access method. + */ + public string|null $workspace_id, + /** + * The actual PIN code for code access methods. + */ + public string|null $code = null, + /** + * Indicates whether an existing card credential must be assigned to this access method before it can be issued. Only applies to card-mode access methods on systems that support credential assignment. + */ + public bool|null $is_assignment_required = null, + /** + * Indicates whether encoding with an card encoder is required to issue or reissue the plastic card associated with the access method. + */ + public bool|null $is_encoding_required = null, + /** + * Indicates whether the access method is ready for card assignment. This is true when the access method is in card mode, has not yet been issued, and the system supports credential assignment. + */ + public bool|null $is_ready_for_assignment = null, + /** + * Indicates whether the access method is ready to be encoded. This is true when the credential has been created and the card has not yet been issued. + */ + public bool|null $is_ready_for_encoding = null, + ) {} + } +} + +namespace Seam\Resources\UnmanagedAccessMethod { + /** + * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\UnmanagedAccessMethod\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedAccessMethod\Errors\ErrorCode::FAILED_TO_ISSUE + => \Seam\Resources\UnmanagedAccessMethod\Errors\FailedToIssue::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} } - public function __construct( - /** - * ID of the access method. - */ - public string|null $access_method_id, - /** - * The actual PIN code for code access methods. - */ - public string|null $code, - /** - * Date and time at which the access method was created. - */ - public string|null $created_at, - /** - * Display name of the access method. - */ - public string|null $display_name, - /** - * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). - */ - public array $errors, - /** - * Indicates whether an existing card credential must be assigned to this access method before it can be issued. Only applies to card-mode access methods on systems that support credential assignment. - */ - public bool|null $is_assignment_required, - /** - * Indicates whether encoding with an card encoder is required to issue or reissue the plastic card associated with the access method. - */ - public bool|null $is_encoding_required, - /** - * Indicates whether the access method has been issued. - */ - public bool|null $is_issued, - /** - * Indicates whether the access method is ready for card assignment. This is true when the access method is in card mode, has not yet been issued, and the system supports credential assignment. - */ - public bool|null $is_ready_for_assignment, - /** - * Indicates whether the access method is ready to be encoded. This is true when the credential has been created and the card has not yet been issued. - */ - public bool|null $is_ready_for_encoding, - /** - * Date and time at which the access method was issued. - */ - public string|null $issued_at, - /** - * Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. - */ - public string|null $mode, - /** - * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. - */ - public array $pending_mutations, - /** - * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). - */ - public array $warnings, - /** - * ID of the Seam workspace associated with the access method. - */ - public string|null $workspace_id, - ) {} + /** + * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. Known mutation_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class PendingMutations + { + public static function from_json(mixed $json): PendingMutations|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->mutation_code ?? null) + ? \Seam\Resources\UnmanagedAccessMethod\PendingMutations\MutationCode::tryFrom( + $json->mutation_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedAccessMethod\PendingMutations\MutationCode::PROVISIONING_ACCESS + => \Seam\Resources\UnmanagedAccessMethod\PendingMutations\ProvisioningAccess::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessMethod\PendingMutations\MutationCode::REVOKING_ACCESS + => \Seam\Resources\UnmanagedAccessMethod\PendingMutations\RevokingAccess::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessMethod\PendingMutations\MutationCode::UPDATING_ACCESS_TIMES + => \Seam\Resources\UnmanagedAccessMethod\PendingMutations\UpdatingAccessTimes::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + public string|null $created_at, + /** + * Detailed description of the mutation. + */ + public string|null $message, + /** + * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\PendingMutations\MutationCode>|string|null + */ + public string|null $mutation_code, + ) {} + } + + /** + * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\UnmanagedAccessMethod\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode::UPDATING_ACCESS_TIMES + => \Seam\Resources\UnmanagedAccessMethod\Warnings\UpdatingAccessTimes::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode::PULLED_BACKUP_ACCESS_CODE + => \Seam\Resources\UnmanagedAccessMethod\Warnings\PulledBackupAccessCode::from_json( + $json, + ), + \Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode::DELAY_IN_ISSUING + => \Seam\Resources\UnmanagedAccessMethod\Warnings\DelayInIssuing::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } + + enum Mode: string + { + case CODE = "code"; + case CARD = "card"; + case MOBILE_KEY = "mobile_key"; + case CLOUD_KEY = "cloud_key"; + } } -/** - * Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). - */ -class UnmanagedAccessMethodErrors -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessMethodErrors|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedAccessMethod\Errors { + /** + * Indicates that Seam was unable to issue this [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant) before its access grant started, so the recipient may be unable to access the space. This usually points to a problem that needs attention, such as an offline or disconnected device. Seam keeps retrying, and this error clears automatically if the access method is eventually issued. + */ + final class FailedToIssue extends + \Seam\Resources\UnmanagedAccessMethod\Errors + { + public static function from_json(mixed $json): FailedToIssue|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} + enum ErrorCode: string + { + case FAILED_TO_ISSUE = "failed_to_issue"; + } } -/** - * Previous device configuration. - */ -class UnmanagedAccessMethodFrom -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessMethodFrom|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedAccessMethod\PendingMutations { + /** + * Seam is in the process of provisioning access for this access method on new devices. + */ + final class ProvisioningAccess extends + \Seam\Resources\UnmanagedAccessMethod\PendingMutations + { + public static function from_json(mixed $json): ProvisioningAccess|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\UnmanagedAccessMethod\PendingMutations\ProvisioningAccess\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\UnmanagedAccessMethod\PendingMutations\ProvisioningAccess\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous device configuration. + */ + public \Seam\Resources\UnmanagedAccessMethod\PendingMutations\ProvisioningAccess\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New device configuration. + */ + public \Seam\Resources\UnmanagedAccessMethod\PendingMutations\ProvisioningAccess\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + /** + * Seam is in the process of revoking access for this access method from devices. + */ + final class RevokingAccess extends + \Seam\Resources\UnmanagedAccessMethod\PendingMutations + { + public static function from_json(mixed $json): RevokingAccess|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\UnmanagedAccessMethod\PendingMutations\RevokingAccess\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\UnmanagedAccessMethod\PendingMutations\RevokingAccess\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous device configuration. + */ + public \Seam\Resources\UnmanagedAccessMethod\PendingMutations\RevokingAccess\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New device configuration. + */ + public \Seam\Resources\UnmanagedAccessMethod\PendingMutations\RevokingAccess\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); } - return new self( - device_ids: $json->device_ids ?? null, - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); } - public function __construct( - /** - * Previous device IDs where access was provisioned. - */ - public array|null $device_ids, - /** - * Previous end time for access. - */ - public string|null $ends_at, - /** - * Previous start time for access. - */ - public string|null $starts_at, - ) {} + /** + * Seam is in the process of updating the access times for this access method. + */ + final class UpdatingAccessTimes extends + \Seam\Resources\UnmanagedAccessMethod\PendingMutations + { + public static function from_json(mixed $json): UpdatingAccessTimes|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + from: isset($json->from) + ? \Seam\Resources\UnmanagedAccessMethod\PendingMutations\UpdatingAccessTimes\From::from_json( + $json->from, + ) + : null, + message: $json->message ?? null, + mutation_code: $json->mutation_code ?? null, + to: isset($json->to) + ? \Seam\Resources\UnmanagedAccessMethod\PendingMutations\UpdatingAccessTimes\To::from_json( + $json->to, + ) + : null, + ); + } + + public function __construct( + /** + * Date and time at which the mutation was created. + */ + string|null $created_at, + /** + * Previous access time configuration. + */ + public \Seam\Resources\UnmanagedAccessMethod\PendingMutations\UpdatingAccessTimes\From|null $from, + /** + * Detailed description of the mutation. + */ + string|null $message, + /** + * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\PendingMutations\MutationCode>|string|null + */ + string|null $mutation_code, + /** + * New access time configuration. + */ + public \Seam\Resources\UnmanagedAccessMethod\PendingMutations\UpdatingAccessTimes\To|null $to, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + mutation_code: $mutation_code, + ); + } + } + + enum MutationCode: string + { + case PROVISIONING_ACCESS = "provisioning_access"; + case REVOKING_ACCESS = "revoking_access"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + } } -/** - * Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. - */ -class UnmanagedAccessMethodPendingMutations -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessMethodPendingMutations|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedAccessMethod\PendingMutations\ProvisioningAccess { + /** + * Previous device configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); } - return new self( - created_at: $json->created_at ?? null, - from: isset($json->from) - ? UnmanagedAccessMethodFrom::from_json($json->from) - : null, - message: $json->message ?? null, - mutation_code: $json->mutation_code ?? null, - to: isset($json->to) - ? UnmanagedAccessMethodTo::from_json($json->to) - : null, - ); + + public function __construct( + /** + * Previous device IDs where access was provisioned. + * + * @var list|null + */ + public array|null $device_ids, + ) {} + } + + /** + * New device configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); + } + + public function __construct( + /** + * New device IDs where access is being provisioned. + * + * @var list|null + */ + public array|null $device_ids, + ) {} + } +} + +namespace Seam\Resources\UnmanagedAccessMethod\PendingMutations\RevokingAccess { + /** + * Previous device configuration. + */ + class From + { + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); + } + + public function __construct( + /** + * Previous device IDs where access existed. + * + * @var list|null + */ + public array|null $device_ids, + ) {} } - public function __construct( - /** - * Date and time at which the mutation was created. - */ - public string|null $created_at, - /** - * Previous device configuration. - */ - public UnmanagedAccessMethodFrom|null $from, - /** - * Detailed description of the mutation. - */ - public string|null $message, - /** - * Mutation code to indicate that Seam is in the process of provisioning access for this access method on new devices. - */ - public string|null $mutation_code, - /** - * New device configuration. - */ - public UnmanagedAccessMethodTo|null $to, - ) {} + /** + * New device configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self(device_ids: $json->device_ids ?? null); + } + + public function __construct( + /** + * New device IDs where access should remain. + * + * @var list|null + */ + public array|null $device_ids, + ) {} + } } -/** - * New device configuration. - */ -class UnmanagedAccessMethodTo -{ - public static function from_json(mixed $json): UnmanagedAccessMethodTo|null +namespace Seam\Resources\UnmanagedAccessMethod\PendingMutations\UpdatingAccessTimes { + /** + * Previous access time configuration. + */ + class From { - if (!$json) { - return null; + public static function from_json(mixed $json): From|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); } - return new self( - device_ids: $json->device_ids ?? null, - ends_at: $json->ends_at ?? null, - starts_at: $json->starts_at ?? null, - ); + + public function __construct( + /** + * Previous end time for access. + */ + public string|null $ends_at, + /** + * Previous start time for access. + */ + public string|null $starts_at, + ) {} } - public function __construct( - /** - * New device IDs where access is being provisioned. - */ - public array|null $device_ids, - /** - * New end time for access. - */ - public string|null $ends_at, - /** - * New start time for access. - */ - public string|null $starts_at, - ) {} + /** + * New access time configuration. + */ + class To + { + public static function from_json(mixed $json): To|null + { + if (!$json) { + return null; + } + return new self( + ends_at: $json->ends_at ?? null, + starts_at: $json->starts_at ?? null, + ); + } + + public function __construct( + /** + * New end time for access. + */ + public string|null $ends_at, + /** + * New start time for access. + */ + public string|null $starts_at, + ) {} + } } -/** - * Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). - */ -class UnmanagedAccessMethodWarnings -{ - public static function from_json( - mixed $json, - ): UnmanagedAccessMethodWarnings|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedAccessMethod\Warnings { + /** + * Indicates that the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant) is being deleted. + */ + final class BeingDeleted extends + \Seam\Resources\UnmanagedAccessMethod\Warnings + { + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the access times for this [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant) are being updated. + */ + final class UpdatingAccessTimes extends + \Seam\Resources\UnmanagedAccessMethod\Warnings + { + public static function from_json(mixed $json): UpdatingAccessTimes|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that all attempts to create an access code on this device before the start time failed and a backup access code was used to ensure access was provided in time. + */ + final class PulledBackupAccessCode extends + \Seam\Resources\UnmanagedAccessMethod\Warnings + { + public static function from_json( + mixed $json, + ): PulledBackupAccessCode|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + original_access_method_id: $json->original_access_method_id ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + /** + * ID of the original access method from which this backup access method was split, if applicable. + */ + public string|null $original_access_method_id = null, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - original_access_method_id: $json->original_access_method_id ?? null, - warning_code: $json->warning_code ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * ID of the original access method from which this backup access method was split, if applicable. - */ - public string|null $original_access_method_id, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + /** + * Indicates that Seam has not yet issued this [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant), even though its access grant is about to begin, so access may not be ready when the recipient arrives. Seam is still attempting to issue it, and this warning clears automatically once issuance succeeds. + */ + final class DelayInIssuing extends + \Seam\Resources\UnmanagedAccessMethod\Warnings + { + public static function from_json(mixed $json): DelayInIssuing|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedAccessMethod\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case BEING_DELETED = "being_deleted"; + case UPDATING_ACCESS_TIMES = "updating_access_times"; + case PULLED_BACKUP_ACCESS_CODE = "pulled_backup_access_code"; + case DELAY_IN_ISSUING = "delay_in_issuing"; + } } diff --git a/src/Resources/UnmanagedDevice.php b/src/Resources/UnmanagedDevice.php index 0dc8fdd0..615542e4 100644 --- a/src/Resources/UnmanagedDevice.php +++ b/src/Resources/UnmanagedDevice.php @@ -1,523 +1,2709 @@ can_configure_auto_lock ?? null, - can_hvac_cool: $json->can_hvac_cool ?? null, - can_hvac_heat: $json->can_hvac_heat ?? null, - can_hvac_heat_cool: $json->can_hvac_heat_cool ?? null, - can_program_offline_access_codes: $json->can_program_offline_access_codes ?? - null, - can_program_online_access_codes: $json->can_program_online_access_codes ?? - null, - can_program_thermostat_programs_as_different_each_day: $json->can_program_thermostat_programs_as_different_each_day ?? - null, - can_program_thermostat_programs_as_same_each_day: $json->can_program_thermostat_programs_as_same_each_day ?? - null, - can_program_thermostat_programs_as_weekday_weekend: $json->can_program_thermostat_programs_as_weekday_weekend ?? - null, - can_remotely_lock: $json->can_remotely_lock ?? null, - can_remotely_unlock: $json->can_remotely_unlock ?? null, - can_run_thermostat_programs: $json->can_run_thermostat_programs ?? - null, - can_simulate_connection: $json->can_simulate_connection ?? null, - can_simulate_disconnection: $json->can_simulate_disconnection ?? - null, - can_simulate_hub_connection: $json->can_simulate_hub_connection ?? - null, - can_simulate_hub_disconnection: $json->can_simulate_hub_disconnection ?? - null, - can_simulate_paid_subscription: $json->can_simulate_paid_subscription ?? - null, - can_simulate_removal: $json->can_simulate_removal ?? null, - can_turn_off_hvac: $json->can_turn_off_hvac ?? null, - can_unlock_with_code: $json->can_unlock_with_code ?? null, - capabilities_supported: $json->capabilities_supported ?? null, - connected_account_id: $json->connected_account_id ?? null, - created_at: $json->created_at ?? null, - custom_metadata: $json->custom_metadata ?? null, - device_id: $json->device_id ?? null, - device_type: $json->device_type ?? null, - errors: array_map( - fn($e) => UnmanagedDeviceErrors::from_json($e), - $json->errors ?? [], - ), - is_managed: $json->is_managed ?? null, - location: isset($json->location) - ? UnmanagedDeviceLocation::from_json($json->location) - : null, - properties: isset($json->properties) - ? UnmanagedDeviceProperties::from_json($json->properties) - : null, - warnings: array_map( - fn($w) => UnmanagedDeviceWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); - } - - public function __construct( - /** - * Indicates whether the lock supports configuring automatic locking. - */ - public bool|null $can_configure_auto_lock, - /** - * Indicates whether the thermostat supports cooling. - */ - public bool|null $can_hvac_cool, - /** - * Indicates whether the thermostat supports heating. - */ - public bool|null $can_hvac_heat, - /** - * Indicates whether the thermostat supports simultaneous heating and cooling. - */ - public bool|null $can_hvac_heat_cool, - /** - * Indicates whether the device supports programming offline access codes. - */ - public bool|null $can_program_offline_access_codes, - /** - * Indicates whether the device supports programming online access codes. - */ - public bool|null $can_program_online_access_codes, - /** - * Indicates whether the thermostat supports different climate programs for each day of the week. - */ - public bool|null $can_program_thermostat_programs_as_different_each_day, - /** - * Indicates whether the thermostat supports a single climate program applied to every day. - */ - public bool|null $can_program_thermostat_programs_as_same_each_day, - /** - * Indicates whether the thermostat supports weekday/weekend climate programs. - */ - public bool|null $can_program_thermostat_programs_as_weekday_weekend, - /** - * Indicates whether the device supports remote locking. - */ - public bool|null $can_remotely_lock, - /** - * Indicates whether the device supports remote unlocking. - */ - public bool|null $can_remotely_unlock, - /** - * Indicates whether the thermostat supports running climate programs. - */ - public bool|null $can_run_thermostat_programs, - /** - * Indicates whether the device supports simulating connection in a sandbox. - */ - public bool|null $can_simulate_connection, - /** - * Indicates whether the device supports simulating disconnection in a sandbox. - */ - public bool|null $can_simulate_disconnection, - /** - * Indicates whether the hub supports simulating connection in a sandbox. - */ - public bool|null $can_simulate_hub_connection, - /** - * Indicates whether the hub supports simulating disconnection in a sandbox. - */ - public bool|null $can_simulate_hub_disconnection, - /** - * Indicates whether the device supports simulating a paid subscription in a sandbox. - */ - public bool|null $can_simulate_paid_subscription, - /** - * Indicates whether the device supports simulating removal in a sandbox. - */ - public bool|null $can_simulate_removal, - /** - * Indicates whether the thermostat can be turned off. - */ - public bool|null $can_turn_off_hvac, - /** - * Indicates whether the lock supports unlocking with an access code. - */ - public bool|null $can_unlock_with_code, - /** - * Collection of capabilities that the device supports when connected to Seam. Values are `access_code`, which indicates that the device can manage and utilize digital PIN codes for secure access; `lock`, which indicates that the device controls a door locking mechanism, enabling the remote opening and closing of doors and other entry points; `noise_detection`, which indicates that the device supports monitoring and responding to ambient noise levels; `thermostat`, which indicates that the device can regulate and adjust indoor temperatures; `battery`, which indicates that the device can manage battery life and health; and `phone`, which indicates that the device is a mobile device, such as a smartphone. **Important:** Superseded by [capability flags](https://docs.seam.co/capability-guides/device-and-system-capabilities#capability-flags). - */ - public array|null $capabilities_supported, - /** - * Unique identifier for the account associated with the device. - */ - public string|null $connected_account_id, - /** - * Date and time at which the device object was created. - */ - public string|null $created_at, - /** - * Set of key:value pairs. Adding custom metadata to a resource, such as a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview), [connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account), or [device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device), enables you to store custom information, like customer details or internal IDs from your application. - */ - public mixed $custom_metadata, - /** - * ID of the device. - */ - public string|null $device_id, - /** - * Type of the device. - */ - public string|null $device_type, - /** - * Array of errors associated with the device. Each error object within the array contains two fields: `error_code` and `message`. `error_code` is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. - */ - public array $errors, - /** - * Indicates that Seam does not manage the device. - */ - public bool|null $is_managed, - /** - * Location information for the device. - */ - public UnmanagedDeviceLocation|null $location, - /** - * properties of the device. - */ - public UnmanagedDeviceProperties|null $properties, - /** - * Array of warnings associated with the device. Each warning object within the array contains two fields: `warning_code` and `message`. `warning_code` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - */ - public array $warnings, - /** - * Unique identifier for the Seam workspace associated with the device. - */ - public string|null $workspace_id, - ) {} -} +namespace Seam\Resources { + /** + * Represents an [unmanaged device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices). An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any [access codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) on an unmanaged device are unmanaged. To control an unmanaged device with Seam, [convert it to a managed device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices#convert-an-unmanaged-device-to-managed). + */ + class UnmanagedDevice + { + public static function from_json(mixed $json): UnmanagedDevice|null + { + if (!$json) { + return null; + } + return new self( + capabilities_supported: $json->capabilities_supported ?? null, + connected_account_id: $json->connected_account_id ?? null, + created_at: $json->created_at ?? null, + custom_metadata: $json->custom_metadata ?? null, + device_id: $json->device_id ?? null, + device_type: $json->device_type ?? null, + errors: array_map( + fn($e) => \Seam\Resources\UnmanagedDevice\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + is_managed: $json->is_managed ?? null, + properties: isset($json->properties) + ? \Seam\Resources\UnmanagedDevice\Properties::from_json( + $json->properties, + ) + : null, + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\UnmanagedDevice\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + can_configure_auto_lock: $json->can_configure_auto_lock ?? null, + can_hvac_cool: $json->can_hvac_cool ?? null, + can_hvac_heat: $json->can_hvac_heat ?? null, + can_hvac_heat_cool: $json->can_hvac_heat_cool ?? null, + can_program_offline_access_codes: $json->can_program_offline_access_codes ?? + null, + can_program_online_access_codes: $json->can_program_online_access_codes ?? + null, + can_program_thermostat_programs_as_different_each_day: $json->can_program_thermostat_programs_as_different_each_day ?? + null, + can_program_thermostat_programs_as_same_each_day: $json->can_program_thermostat_programs_as_same_each_day ?? + null, + can_program_thermostat_programs_as_weekday_weekend: $json->can_program_thermostat_programs_as_weekday_weekend ?? + null, + can_remotely_lock: $json->can_remotely_lock ?? null, + can_remotely_unlock: $json->can_remotely_unlock ?? null, + can_run_thermostat_programs: $json->can_run_thermostat_programs ?? + null, + can_simulate_connection: $json->can_simulate_connection ?? null, + can_simulate_disconnection: $json->can_simulate_disconnection ?? + null, + can_simulate_hub_connection: $json->can_simulate_hub_connection ?? + null, + can_simulate_hub_disconnection: $json->can_simulate_hub_disconnection ?? + null, + can_simulate_paid_subscription: $json->can_simulate_paid_subscription ?? + null, + can_simulate_removal: $json->can_simulate_removal ?? null, + can_turn_off_hvac: $json->can_turn_off_hvac ?? null, + can_unlock_with_code: $json->can_unlock_with_code ?? null, + location: isset($json->location) + ? \Seam\Resources\UnmanagedDevice\Location::from_json( + $json->location, + ) + : null, + ); + } -/** - * Accessory keypad properties and state. - */ -class UnmanagedDeviceAccessoryKeypad -{ - public static function from_json( - mixed $json, - ): UnmanagedDeviceAccessoryKeypad|null { - if (!$json) { - return null; - } - return new self( - battery: isset($json->battery) - ? UnmanagedDeviceBattery::from_json($json->battery) - : null, - is_connected: $json->is_connected ?? null, - ); - } - - public function __construct( - /** - * Keypad battery properties. - */ - public UnmanagedDeviceBattery|null $battery, - /** - * Indicates if an accessory keypad is connected to the device. - */ - public bool|null $is_connected, - ) {} + public function __construct( + /** + * Collection of capabilities that the device supports when connected to Seam. Values are `access_code`, which indicates that the device can manage and utilize digital PIN codes for secure access; `lock`, which indicates that the device controls a door locking mechanism, enabling the remote opening and closing of doors and other entry points; `noise_detection`, which indicates that the device supports monitoring and responding to ambient noise levels; `thermostat`, which indicates that the device can regulate and adjust indoor temperatures; `battery`, which indicates that the device can manage battery life and health; and `phone`, which indicates that the device is a mobile device, such as a smartphone. **Important:** Superseded by [capability flags](https://docs.seam.co/capability-guides/device-and-system-capabilities#capability-flags). + * + * @var list|null + */ + public array|null $capabilities_supported, + /** + * Unique identifier for the account associated with the device. + */ + public string|null $connected_account_id, + /** + * Date and time at which the device object was created. + */ + public string|null $created_at, + /** + * Set of key:value pairs. Adding custom metadata to a resource, such as a [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview), [connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account), or [device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device), enables you to store custom information, like customer details or internal IDs from your application. + * + * @var array|\stdClass|null + */ + public array|\stdClass|null $custom_metadata, + /** + * ID of the device. + */ + public string|null $device_id, + /** + * Type of the device. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\DeviceType>|string|null + */ + public string|null $device_type, + /** + * Array of errors associated with the device. Each error object within the array contains two fields: `error_code` and `message`. `error_code` is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. + * + * @var list<\Seam\Resources\UnmanagedDevice\Errors> + */ + public array $errors, + /** + * Indicates that Seam does not manage the device. + */ + public false|null $is_managed, + /** + * properties of the device. + */ + public \Seam\Resources\UnmanagedDevice\Properties|null $properties, + /** + * Array of warnings associated with the device. Each warning object within the array contains two fields: `warning_code` and `message`. `warning_code` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. + * + * @var list<\Seam\Resources\UnmanagedDevice\Warnings> + */ + public array $warnings, + /** + * Unique identifier for the Seam workspace associated with the device. + */ + public string|null $workspace_id, + /** + * Indicates whether the lock supports configuring automatic locking. + */ + public bool|null $can_configure_auto_lock = null, + /** + * Indicates whether the thermostat supports cooling. + */ + public bool|null $can_hvac_cool = null, + /** + * Indicates whether the thermostat supports heating. + */ + public bool|null $can_hvac_heat = null, + /** + * Indicates whether the thermostat supports simultaneous heating and cooling. + */ + public bool|null $can_hvac_heat_cool = null, + /** + * Indicates whether the device supports programming offline access codes. + */ + public bool|null $can_program_offline_access_codes = null, + /** + * Indicates whether the device supports programming online access codes. + */ + public bool|null $can_program_online_access_codes = null, + /** + * Indicates whether the thermostat supports different climate programs for each day of the week. + */ + public bool|null $can_program_thermostat_programs_as_different_each_day = null, + /** + * Indicates whether the thermostat supports a single climate program applied to every day. + */ + public bool|null $can_program_thermostat_programs_as_same_each_day = null, + /** + * Indicates whether the thermostat supports weekday/weekend climate programs. + */ + public bool|null $can_program_thermostat_programs_as_weekday_weekend = null, + /** + * Indicates whether the device supports remote locking. + */ + public bool|null $can_remotely_lock = null, + /** + * Indicates whether the device supports remote unlocking. + */ + public bool|null $can_remotely_unlock = null, + /** + * Indicates whether the thermostat supports running climate programs. + */ + public bool|null $can_run_thermostat_programs = null, + /** + * Indicates whether the device supports simulating connection in a sandbox. + */ + public bool|null $can_simulate_connection = null, + /** + * Indicates whether the device supports simulating disconnection in a sandbox. + */ + public bool|null $can_simulate_disconnection = null, + /** + * Indicates whether the hub supports simulating connection in a sandbox. + */ + public bool|null $can_simulate_hub_connection = null, + /** + * Indicates whether the hub supports simulating disconnection in a sandbox. + */ + public bool|null $can_simulate_hub_disconnection = null, + /** + * Indicates whether the device supports simulating a paid subscription in a sandbox. + */ + public bool|null $can_simulate_paid_subscription = null, + /** + * Indicates whether the device supports simulating removal in a sandbox. + */ + public bool|null $can_simulate_removal = null, + /** + * Indicates whether the thermostat can be turned off. + */ + public bool|null $can_turn_off_hvac = null, + /** + * Indicates whether the lock supports unlocking with an access code. + */ + public bool|null $can_unlock_with_code = null, + /** + * Location information for the device. + */ + public \Seam\Resources\UnmanagedDevice\Location|null $location = null, + ) {} + } } -/** - * Keypad battery properties. - */ -class UnmanagedDeviceBattery -{ - public static function from_json(mixed $json): UnmanagedDeviceBattery|null +namespace Seam\Resources\UnmanagedDevice { + /** + * Array of errors associated with the device. Each error object within the array contains two fields: `error_code` and `message`. `error_code` is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::ACCOUNT_DISCONNECTED + => \Seam\Resources\UnmanagedDevice\Errors\AccountDisconnected::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED + => \Seam\Resources\UnmanagedDevice\Errors\SaltoKsSubscriptionLimitExceeded::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::INSUFFICIENT_PERMISSIONS + => \Seam\Resources\UnmanagedDevice\Errors\InsufficientPermissions::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::DORMAKABA_SITES_DISCONNECTED + => \Seam\Resources\UnmanagedDevice\Errors\DormakabaSitesDisconnected::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::DEVICE_OFFLINE + => \Seam\Resources\UnmanagedDevice\Errors\DeviceOffline::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::DEVICE_REMOVED + => \Seam\Resources\UnmanagedDevice\Errors\DeviceRemoved::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::HUB_DISCONNECTED + => \Seam\Resources\UnmanagedDevice\Errors\HubDisconnected::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::DEVICE_DISCONNECTED + => \Seam\Resources\UnmanagedDevice\Errors\DeviceDisconnected::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::EMPTY_BACKUP_ACCESS_CODE_POOL + => \Seam\Resources\UnmanagedDevice\Errors\EmptyBackupAccessCodePool::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::AUGUST_LOCK_NOT_AUTHORIZED + => \Seam\Resources\UnmanagedDevice\Errors\AugustLockNotAuthorized::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::MISSING_DEVICE_CREDENTIALS + => \Seam\Resources\UnmanagedDevice\Errors\MissingDeviceCredentials::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::AUXILIARY_HEAT_RUNNING + => \Seam\Resources\UnmanagedDevice\Errors\AuxiliaryHeatRunning::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::SUBSCRIPTION_REQUIRED + => \Seam\Resources\UnmanagedDevice\Errors\SubscriptionRequired::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Errors\ErrorCode::BRIDGE_DISCONNECTED + => \Seam\Resources\UnmanagedDevice\Errors\BridgeDisconnected::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} + } + + /** + * Location information for the device. + */ + class Location + { + public static function from_json(mixed $json): Location|null + { + if (!$json) { + return null; + } + return new self( + location_name: $json->location_name ?? null, + room_name: $json->room_name ?? null, + time_zone: $json->time_zone ?? null, + timezone: $json->timezone ?? null, + ); + } + + public function __construct( + /** + * Name of the device location. + */ + public string|null $location_name = null, + /** + * Name of the room within the device location, when the provider reports one. + */ + public string|null $room_name = null, + /** + * Time zone of the device location. + */ + public string|null $time_zone = null, + /** + * Time zone of the device location. + * + * @deprecated Use `time_zone` instead. + */ + public string|null $timezone = null, + ) {} + } + + /** + * properties of the device. + */ + class Properties + { + public static function from_json(mixed $json): Properties|null + { + if (!$json) { + return null; + } + return new self( + model: isset($json->model) + ? \Seam\Resources\UnmanagedDevice\Properties\Model::from_json( + $json->model, + ) + : null, + name: $json->name ?? null, + online: $json->online ?? null, + accessory_keypad: isset($json->accessory_keypad) + ? \Seam\Resources\UnmanagedDevice\Properties\AccessoryKeypad::from_json( + $json->accessory_keypad, + ) + : null, + battery: isset($json->battery) + ? \Seam\Resources\UnmanagedDevice\Properties\Battery::from_json( + $json->battery, + ) + : null, + battery_level: $json->battery_level ?? null, + image_alt_text: $json->image_alt_text ?? null, + image_url: $json->image_url ?? null, + manufacturer: $json->manufacturer ?? null, + offline_access_codes_enabled: $json->offline_access_codes_enabled ?? + null, + online_access_codes_enabled: $json->online_access_codes_enabled ?? + null, + ); + } + + public function __construct( + /** + * Device model-related properties. + */ + public \Seam\Resources\UnmanagedDevice\Properties\Model|null $model, + /** + * Name of the device. + * + * @deprecated use device.display_name instead + */ + public string|null $name, + /** + * Indicates whether the device is online. + */ + public bool|null $online, + /** + * Accessory keypad properties and state. + */ + public \Seam\Resources\UnmanagedDevice\Properties\AccessoryKeypad|null $accessory_keypad = null, + /** + * Represents the current status of the battery charge level. + */ + public \Seam\Resources\UnmanagedDevice\Properties\Battery|null $battery = null, + /** + * Indicates the battery level of the device as a decimal value between 0 and 1, inclusive. + */ + public float|null $battery_level = null, + /** + * Alt text for the device image. + */ + public string|null $image_alt_text = null, + /** + * Image URL for the device. + */ + public string|null $image_url = null, + /** + * Manufacturer of the device. When a device, such as a smart lock, is connected through a smart hub, the manufacturer of the device might be different from that of the smart hub. + */ + public string|null $manufacturer = null, + /** + * Indicates whether it is currently possible to use offline access codes for the device. + * + * @deprecated use device.can_program_offline_access_codes + */ + public bool|null $offline_access_codes_enabled = null, + /** + * Indicates whether it is currently possible to use online access codes for the device. + * + * @deprecated use device.can_program_online_access_codes + */ + public bool|null $online_access_codes_enabled = null, + ) {} + } + + /** + * Array of warnings associated with the device. Each warning object within the array contains two fields: `warning_code` and `message`. `warning_code` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings { - if (!$json) { - return null; + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::PARTIAL_BACKUP_ACCESS_CODE_POOL + => \Seam\Resources\UnmanagedDevice\Warnings\PartialBackupAccessCodePool::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::MANY_ACTIVE_BACKUP_CODES + => \Seam\Resources\UnmanagedDevice\Warnings\ManyActiveBackupCodes::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::THIRD_PARTY_INTEGRATION_DETECTED + => \Seam\Resources\UnmanagedDevice\Warnings\ThirdPartyIntegrationDetected::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::TTLOCK_LOCK_GATEWAY_UNLOCKING_NOT_ENABLED + => \Seam\Resources\UnmanagedDevice\Warnings\TtlockLockGatewayUnlockingNotEnabled::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::TTLOCK_WEAK_GATEWAY_SIGNAL + => \Seam\Resources\UnmanagedDevice\Warnings\TtlockWeakGatewaySignal::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::POWER_SAVING_MODE + => \Seam\Resources\UnmanagedDevice\Warnings\PowerSavingMode::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::TEMPERATURE_THRESHOLD_EXCEEDED + => \Seam\Resources\UnmanagedDevice\Warnings\TemperatureThresholdExceeded::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::DEVICE_COMMUNICATION_DEGRADED + => \Seam\Resources\UnmanagedDevice\Warnings\DeviceCommunicationDegraded::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::SCHEDULED_MAINTENANCE_WINDOW + => \Seam\Resources\UnmanagedDevice\Warnings\ScheduledMaintenanceWindow::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::DEVICE_HAS_FLAKY_CONNECTION + => \Seam\Resources\UnmanagedDevice\Warnings\DeviceHasFlakyConnection::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::SALTO_KS_OFFICE_MODE + => \Seam\Resources\UnmanagedDevice\Warnings\SaltoKsOfficeMode::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::SALTO_KS_PRIVACY_MODE + => \Seam\Resources\UnmanagedDevice\Warnings\SaltoKsPrivacyMode::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::PRIVACY_MODE + => \Seam\Resources\UnmanagedDevice\Warnings\PrivacyMode::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::SALTO_KS_SUBSCRIPTION_LIMIT_ALMOST_REACHED + => \Seam\Resources\UnmanagedDevice\Warnings\SaltoKsSubscriptionLimitAlmostReached::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::SALTO_KS_LOCK_ACCESS_CODE_SUPPORT_REMOVED + => \Seam\Resources\UnmanagedDevice\Warnings\SaltoKsLockAccessCodeSupportRemoved::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::UNKNOWN_ISSUE_WITH_PHONE + => \Seam\Resources\UnmanagedDevice\Warnings\UnknownIssueWithPhone::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::LOCKLY_TIME_ZONE_NOT_CONFIGURED + => \Seam\Resources\UnmanagedDevice\Warnings\LocklyTimeZoneNotConfigured::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::ULTRALOQ_TIME_ZONE_UNKNOWN + => \Seam\Resources\UnmanagedDevice\Warnings\UltraloqTimeZoneUnknown::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::TIME_ZONE_UNKNOWN + => \Seam\Resources\UnmanagedDevice\Warnings\TimeZoneUnknown::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::TIME_ZONE_MISMATCH + => \Seam\Resources\UnmanagedDevice\Warnings\TimeZoneMismatch::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::TWO_N_DEVICE_MISSING_TIMEZONE + => \Seam\Resources\UnmanagedDevice\Warnings\TwoNDeviceMissingTimezone::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::HUB_REQUIRED_FOR_ADDITIONAL_CAPABILITIES + => \Seam\Resources\UnmanagedDevice\Warnings\HubRequiredForAdditionalCapabilities::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::PROVIDER_ISSUE + => \Seam\Resources\UnmanagedDevice\Warnings\ProviderIssue::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::KEYNEST_UNSUPPORTED_LOCKER + => \Seam\Resources\UnmanagedDevice\Warnings\KeynestUnsupportedLocker::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::ACCESSORY_KEYPAD_SETUP_REQUIRED + => \Seam\Resources\UnmanagedDevice\Warnings\AccessoryKeypadSetupRequired::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::UNRELIABLE_ONLINE_STATUS + => \Seam\Resources\UnmanagedDevice\Warnings\UnreliableOnlineStatus::from_json( + $json, + ), + \Seam\Resources\UnmanagedDevice\Warnings\WarningCode::MAX_ACCESS_CODES_REACHED + => \Seam\Resources\UnmanagedDevice\Warnings\MaxAccessCodesReached::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; } - return new self(level: $json->level ?? null); + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} } - public function __construct(public float|null $level) {} + enum DeviceType: string + { + case AKUVOX_LOCK = "akuvox_lock"; + case AUGUST_LOCK = "august_lock"; + case BRIVO_ACCESS_POINT = "brivo_access_point"; + case BUTTERFLYMX_PANEL = "butterflymx_panel"; + case AVIGILON_ALTA_ENTRY = "avigilon_alta_entry"; + case DOORKING_LOCK = "doorking_lock"; + case GENIE_DOOR = "genie_door"; + case IGLOO_LOCK = "igloo_lock"; + case LINEAR_LOCK = "linear_lock"; + case LOCKLY_LOCK = "lockly_lock"; + case KWIKSET_LOCK = "kwikset_lock"; + case NUKI_LOCK = "nuki_lock"; + case SALTO_LOCK = "salto_lock"; + case SCHLAGE_LOCK = "schlage_lock"; + case SMARTTHINGS_LOCK = "smartthings_lock"; + case WYZE_LOCK = "wyze_lock"; + case YALE_LOCK = "yale_lock"; + case TWO_N_INTERCOM = "two_n_intercom"; + case CONTROLBYWEB_DEVICE = "controlbyweb_device"; + case TTLOCK_LOCK = "ttlock_lock"; + case IGLOOHOME_LOCK = "igloohome_lock"; + case FOUR_SUITES_DOOR = "four_suites_door"; + case DORMAKABA_ORACODE_DOOR = "dormakaba_oracode_door"; + case TEDEE_LOCK = "tedee_lock"; + case AKILES_LOCK = "akiles_lock"; + case ULTRALOQ_LOCK = "ultraloq_lock"; + case YACAN_LOCK = "yacan_lock"; + case KEYINCODE_LOCK = "keyincode_lock"; + case OMNITEC_LOCK = "omnitec_lock"; + case KISI_LOCK = "kisi_lock"; + case AQARA_LOCK = "aqara_lock"; + case KEYNEST_KEY = "keynest_key"; + case NOISEAWARE_ACTIVITY_ZONE = "noiseaware_activity_zone"; + case MINUT_SENSOR = "minut_sensor"; + case ECOBEE_THERMOSTAT = "ecobee_thermostat"; + case NEST_THERMOSTAT = "nest_thermostat"; + case HONEYWELL_RESIDEO_THERMOSTAT = "honeywell_resideo_thermostat"; + case TADO_THERMOSTAT = "tado_thermostat"; + case SENSI_THERMOSTAT = "sensi_thermostat"; + case SMARTTHINGS_THERMOSTAT = "smartthings_thermostat"; + case IOS_PHONE = "ios_phone"; + case ANDROID_PHONE = "android_phone"; + case RING_CAMERA = "ring_camera"; + } } -/** - * Array of errors associated with the device. Each error object within the array contains two fields: `error_code` and `message`. `error_code` is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. - */ -class UnmanagedDeviceErrors -{ - public static function from_json(mixed $json): UnmanagedDeviceErrors|null - { - if (!$json) { - return null; - } - return new self( - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - is_bridge_error: $json->is_bridge_error ?? null, - is_connected_account_error: $json->is_connected_account_error ?? - null, - is_device_error: $json->is_device_error ?? null, - message: $json->message ?? null, - ); - } - - public function __construct( - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). - */ - public bool|null $is_bridge_error, - /** - * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. - */ - public bool|null $is_connected_account_error, - /** - * Indicates that the error is not a device error. - */ - public bool|null $is_device_error, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} +namespace Seam\Resources\UnmanagedDevice\Errors { + /** + * Indicates that the account is disconnected. + */ + final class AccountDisconnected extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json(mixed $json): AccountDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the Salto site user limit has been reached. + */ + final class SaltoKsSubscriptionLimitExceeded extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that Seam's integration user does not have sufficient permissions on the provider's system to which this device belongs, so Seam cannot manage access codes or unlock the device. See the error message for specifics, then either reauthorize the connected account in Seam or grant the integration user the required permissions in the provider's system. + */ + final class InsufficientPermissions extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json( + mixed $json, + ): InsufficientPermissions|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that one or more dormakaba sites associated with the connected account could not be connected. Contact dormakaba support. + */ + final class DormakabaSitesDisconnected extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json( + mixed $json, + ): DormakabaSitesDisconnected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a [connected account](https://docs.seam.co/api/connected_accounts) error. + */ + public true|null $is_connected_account_error, + /** + * Indicates that the error is not a device error. + */ + public false|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the device is offline. + */ + final class DeviceOffline extends \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json(mixed $json): DeviceOffline|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the device has been removed. + */ + final class DeviceRemoved extends \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json(mixed $json): DeviceRemoved|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the hub is disconnected. + */ + final class HubDisconnected extends \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json(mixed $json): HubDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the device is disconnected. + */ + final class DeviceDisconnected extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json(mixed $json): DeviceDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) is empty. + */ + final class EmptyBackupAccessCodePool extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json( + mixed $json, + ): EmptyBackupAccessCodePool|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the user is not authorized to use the August lock. + */ + final class AugustLockNotAuthorized extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json( + mixed $json, + ): AugustLockNotAuthorized|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that device credentials are missing. + */ + final class MissingDeviceCredentials extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json( + mixed $json, + ): MissingDeviceCredentials|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the auxiliary heat is running. + */ + final class AuxiliaryHeatRunning extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json(mixed $json): AuxiliaryHeatRunning|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that a subscription is required to connect. + */ + final class SubscriptionRequired extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json(mixed $json): SubscriptionRequired|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + is_device_error: $json->is_device_error ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Indicates that the error is a device error. + */ + public true|null $is_device_error, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + /** + * Indicates that the Seam API cannot communicate with [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge), for example, if the Seam Bridge executable has stopped or if the computer running the Seam Bridge executable is offline. See also [Troubleshooting Your Access Control System](https://docs.seam.co/low-level-apis/access-systems/troubleshooting-your-access-control-system#acs_system-errors-seam_bridge_disconnected). + */ + final class BridgeDisconnected extends + \Seam\Resources\UnmanagedDevice\Errors + { + public static function from_json(mixed $json): BridgeDisconnected|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + is_bridge_error: $json->is_bridge_error ?? null, + is_connected_account_error: $json->is_connected_account_error ?? + null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Indicates whether the error is related to [Seam Bridge](https://docs.seam.co/capability-guides/seam-bridge). + */ + public bool|null $is_bridge_error = null, + /** + * Indicates whether the error is related specifically to the connected account. + */ + public bool|null $is_connected_account_error = null, + ) { + parent::__construct( + created_at: $created_at, + error_code: $error_code, + message: $message, + ); + } + } + + enum ErrorCode: string + { + case ACCOUNT_DISCONNECTED = "account_disconnected"; + case SALTO_KS_SUBSCRIPTION_LIMIT_EXCEEDED = "salto_ks_subscription_limit_exceeded"; + case INSUFFICIENT_PERMISSIONS = "insufficient_permissions"; + case DORMAKABA_SITES_DISCONNECTED = "dormakaba_sites_disconnected"; + case DEVICE_OFFLINE = "device_offline"; + case DEVICE_REMOVED = "device_removed"; + case HUB_DISCONNECTED = "hub_disconnected"; + case DEVICE_DISCONNECTED = "device_disconnected"; + case EMPTY_BACKUP_ACCESS_CODE_POOL = "empty_backup_access_code_pool"; + case AUGUST_LOCK_NOT_AUTHORIZED = "august_lock_not_authorized"; + case MISSING_DEVICE_CREDENTIALS = "missing_device_credentials"; + case AUXILIARY_HEAT_RUNNING = "auxiliary_heat_running"; + case SUBSCRIPTION_REQUIRED = "subscription_required"; + case BRIDGE_DISCONNECTED = "bridge_disconnected"; + } } -/** - * Location information for the device. - */ -class UnmanagedDeviceLocation -{ - public static function from_json(mixed $json): UnmanagedDeviceLocation|null - { - if (!$json) { - return null; - } - return new self( - location_name: $json->location_name ?? null, - time_zone: $json->time_zone ?? null, - timezone: $json->timezone ?? null, - ); - } - - public function __construct( - /** - * Name of the device location. - */ - public string|null $location_name, - /** - * Time zone of the device location. - */ - public string|null $time_zone, - /** - * Time zone of the device location. - * - * @deprecated Use `time_zone` instead. - */ - public string|null $timezone, - ) {} +namespace Seam\Resources\UnmanagedDevice\Properties { + /** + * Accessory keypad properties and state. + */ + class AccessoryKeypad + { + public static function from_json(mixed $json): AccessoryKeypad|null + { + if (!$json) { + return null; + } + return new self( + is_connected: $json->is_connected ?? null, + battery: isset($json->battery) + ? \Seam\Resources\UnmanagedDevice\Properties\AccessoryKeypad\Battery::from_json( + $json->battery, + ) + : null, + ); + } + + public function __construct( + /** + * Indicates if an accessory keypad is connected to the device. + */ + public bool|null $is_connected, + /** + * Keypad battery properties. + */ + public \Seam\Resources\UnmanagedDevice\Properties\AccessoryKeypad\Battery|null $battery = null, + ) {} + } + + /** + * Represents the current status of the battery charge level. + */ + class Battery + { + public static function from_json(mixed $json): Battery|null + { + if (!$json) { + return null; + } + return new self( + level: $json->level ?? null, + status: $json->status ?? null, + ); + } + + public function __construct( + /** + * Battery charge level as a value between 0 and 1, inclusive. + */ + public float|null $level, + /** + * Represents the current status of the battery charge level. Values are `critical`, which indicates an extremely low level, suggesting imminent shutdown or an urgent need for charging; `low`, which signifies that the battery is under the preferred threshold and should be charged soon; `good`, which denotes a satisfactory charge level, adequate for normal use without the immediate need for recharging; and `full`, which represents a battery that is fully charged, providing the maximum duration of usage. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Properties\Battery\Status>|string|null + */ + public string|null $status, + ) {} + } + + /** + * Device model-related properties. + */ + class Model + { + public static function from_json(mixed $json): Model|null + { + if (!$json) { + return null; + } + return new self( + display_name: $json->display_name ?? null, + manufacturer_display_name: $json->manufacturer_display_name ?? + null, + accessory_keypad_supported: $json->accessory_keypad_supported ?? + null, + can_connect_accessory_keypad: $json->can_connect_accessory_keypad ?? + null, + has_built_in_keypad: $json->has_built_in_keypad ?? null, + offline_access_codes_supported: $json->offline_access_codes_supported ?? + null, + online_access_codes_supported: $json->online_access_codes_supported ?? + null, + ); + } + + public function __construct( + /** + * Display name of the device model. + */ + public string|null $display_name, + /** + * Display name that corresponds to the manufacturer-specific terminology for the device. + */ + public string|null $manufacturer_display_name, + /** + * @deprecated use device.properties.model.can_connect_accessory_keypad + */ + public bool|null $accessory_keypad_supported = null, + /** + * Indicates whether the device can connect a accessory keypad. + */ + public bool|null $can_connect_accessory_keypad = null, + /** + * Indicates whether the device has a built in accessory keypad. + */ + public bool|null $has_built_in_keypad = null, + /** + * @deprecated use device.can_program_offline_access_codes. + */ + public bool|null $offline_access_codes_supported = null, + /** + * @deprecated use device.can_program_online_access_codes. + */ + public bool|null $online_access_codes_supported = null, + ) {} + } } -/** - * Device model-related properties. - */ -class UnmanagedDeviceModel -{ - public static function from_json(mixed $json): UnmanagedDeviceModel|null - { - if (!$json) { - return null; - } - return new self( - accessory_keypad_supported: $json->accessory_keypad_supported ?? - null, - can_connect_accessory_keypad: $json->can_connect_accessory_keypad ?? - null, - display_name: $json->display_name ?? null, - has_built_in_keypad: $json->has_built_in_keypad ?? null, - manufacturer_display_name: $json->manufacturer_display_name ?? null, - offline_access_codes_supported: $json->offline_access_codes_supported ?? - null, - online_access_codes_supported: $json->online_access_codes_supported ?? - null, - ); - } - - public function __construct( - /** - * @deprecated use device.properties.model.can_connect_accessory_keypad - */ - public bool|null $accessory_keypad_supported, - /** - * Indicates whether the device can connect a accessory keypad. - */ - public bool|null $can_connect_accessory_keypad, - /** - * Display name of the device model. - */ - public string|null $display_name, - /** - * Indicates whether the device has a built in accessory keypad. - */ - public bool|null $has_built_in_keypad, - /** - * Display name that corresponds to the manufacturer-specific terminology for the device. - */ - public string|null $manufacturer_display_name, - /** - * @deprecated use device.can_program_offline_access_codes. - */ - public bool|null $offline_access_codes_supported, - /** - * @deprecated use device.can_program_online_access_codes. - */ - public bool|null $online_access_codes_supported, - ) {} +namespace Seam\Resources\UnmanagedDevice\Properties\AccessoryKeypad { + /** + * Keypad battery properties. + */ + class Battery + { + public static function from_json(mixed $json): Battery|null + { + if (!$json) { + return null; + } + return new self(level: $json->level ?? null); + } + + public function __construct(public float|null $level) {} + } } -/** - * properties of the device. - */ -class UnmanagedDeviceProperties -{ - public static function from_json( - mixed $json, - ): UnmanagedDeviceProperties|null { - if (!$json) { - return null; - } - return new self( - accessory_keypad: isset($json->accessory_keypad) - ? UnmanagedDeviceAccessoryKeypad::from_json( - $json->accessory_keypad, - ) - : null, - battery: isset($json->battery) - ? UnmanagedDeviceBattery::from_json($json->battery) - : null, - battery_level: $json->battery_level ?? null, - image_alt_text: $json->image_alt_text ?? null, - image_url: $json->image_url ?? null, - manufacturer: $json->manufacturer ?? null, - model: isset($json->model) - ? UnmanagedDeviceModel::from_json($json->model) - : null, - name: $json->name ?? null, - offline_access_codes_enabled: $json->offline_access_codes_enabled ?? - null, - online: $json->online ?? null, - online_access_codes_enabled: $json->online_access_codes_enabled ?? - null, - ); - } - - public function __construct( - /** - * Accessory keypad properties and state. - */ - public UnmanagedDeviceAccessoryKeypad|null $accessory_keypad, - /** - * Represents the current status of the battery charge level. - */ - public UnmanagedDeviceBattery|null $battery, - /** - * Indicates the battery level of the device as a decimal value between 0 and 1, inclusive. - */ - public float|null $battery_level, - /** - * Alt text for the device image. - */ - public string|null $image_alt_text, - /** - * Image URL for the device. - */ - public string|null $image_url, - /** - * Manufacturer of the device. When a device, such as a smart lock, is connected through a smart hub, the manufacturer of the device might be different from that of the smart hub. - */ - public string|null $manufacturer, - /** - * Device model-related properties. - */ - public UnmanagedDeviceModel|null $model, - /** - * Name of the device. - * - * @deprecated use device.display_name instead - */ - public string|null $name, - /** - * Indicates whether it is currently possible to use offline access codes for the device. - * - * @deprecated use device.can_program_offline_access_codes - */ - public bool|null $offline_access_codes_enabled, - /** - * Indicates whether the device is online. - */ - public bool|null $online, - /** - * Indicates whether it is currently possible to use online access codes for the device. - * - * @deprecated use device.can_program_online_access_codes - */ - public bool|null $online_access_codes_enabled, - ) {} +namespace Seam\Resources\UnmanagedDevice\Properties\Battery { + enum Status: string + { + case CRITICAL = "critical"; + case LOW = "low"; + case GOOD = "good"; + case FULL = "full"; + } } -/** - * Array of warnings associated with the device. Each warning object within the array contains two fields: `warning_code` and `message`. `warning_code` is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. `message` provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - */ -class UnmanagedDeviceWarnings -{ - public static function from_json(mixed $json): UnmanagedDeviceWarnings|null - { - if (!$json) { - return null; - } - return new self( - active_access_code_count: $json->active_access_code_count ?? null, - created_at: $json->created_at ?? null, - max_active_access_code_count: $json->max_active_access_code_count ?? - null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); - } - - public function __construct( - /** - * Number of active access codes on the device when the warning was set. - */ - public int|null $active_access_code_count, - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Maximum number of active access codes supported by the device. - */ - public int|null $max_active_access_code_count, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} +namespace Seam\Resources\UnmanagedDevice\Warnings { + /** + * Indicates that the backup access code is unhealthy. + */ + final class PartialBackupAccessCodePool extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): PartialBackupAccessCodePool|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that there are too many backup codes. + */ + final class ManyActiveBackupCodes extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): ManyActiveBackupCodes|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a third-party integration has been detected. + */ + final class ThirdPartyIntegrationDetected extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): ThirdPartyIntegrationDetected|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the Remote Unlock feature is not enabled in the settings." + */ + final class TtlockLockGatewayUnlockingNotEnabled extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): TtlockLockGatewayUnlockingNotEnabled|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the gateway signal is weak. + */ + final class TtlockWeakGatewaySignal extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): TtlockWeakGatewaySignal|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the device is in power saving mode and may have limited functionality. + */ + final class PowerSavingMode extends \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json(mixed $json): PowerSavingMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the temperature threshold has been exceeded. + */ + final class TemperatureThresholdExceeded extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): TemperatureThresholdExceeded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the device appears to be unresponsive. + */ + final class DeviceCommunicationDegraded extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): DeviceCommunicationDegraded|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a scheduled maintenance window has been detected. + */ + final class ScheduledMaintenanceWindow extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): ScheduledMaintenanceWindow|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the device has a flaky connection. + */ + final class DeviceHasFlakyConnection extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): DeviceHasFlakyConnection|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the Salto KS lock is in Office Mode. Access Codes will not unlock doors. + */ + final class SaltoKsOfficeMode extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json(mixed $json): SaltoKsOfficeMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the Salto KS lock is in Privacy Mode. Access Codes will not unlock doors. + */ + final class SaltoKsPrivacyMode extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json(mixed $json): SaltoKsPrivacyMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the lock is in Privacy Mode. Access codes and remote unlock are blocked until Privacy Mode is disabled. + */ + final class PrivacyMode extends \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json(mixed $json): PrivacyMode|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the Salto KS site has exceeded 80% of the maximum number of allowed users. Increase your subscription limit or delete some users from your site. + */ + final class SaltoKsSubscriptionLimitAlmostReached extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): SaltoKsSubscriptionLimitAlmostReached|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a change in the reported device model has been detected for this Salto KS lock, which may occur after an IQ hub reset. Access code support may be affected. See https://help.getseam.com/articles/5098842588-salto-ks-lock-loses-access-code-support for troubleshooting steps. + */ + final class SaltoKsLockAccessCodeSupportRemoved extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): SaltoKsLockAccessCodeSupportRemoved|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that an unknown issue occurred while syncing the state of the phone with the provider. This issue may affect the proper functioning of the phone. + */ + final class UnknownIssueWithPhone extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): UnknownIssueWithPhone|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that Seam detected that the Lockly device does not have a time zone configured. Time-bound codes may not work as expected. + */ + final class LocklyTimeZoneNotConfigured extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): LocklyTimeZoneNotConfigured|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that Seam does not know the time zone of the Ultraloq device. Set a time zone to enable time-bound access codes. + */ + final class UltraloqTimeZoneUnknown extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): UltraloqTimeZoneUnknown|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that Seam does not know the device's time zone. Set a time zone to enable time-bound access codes. + */ + final class TimeZoneUnknown extends \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json(mixed $json): TimeZoneUnknown|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the device's configured time zone does not match its hardware UTC offset. Time-bound access codes may activate at the wrong local time. + */ + final class TimeZoneMismatch extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json(mixed $json): TimeZoneMismatch|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the 2N device does not have a time zone configured. Configure a time zone on the device to enable access codes. + */ + final class TwoNDeviceMissingTimezone extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): TwoNDeviceMissingTimezone|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that a hub or relay must be connected to unlock additional capabilities such as remote unlock. + */ + final class HubRequiredForAdditionalCapabilities extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): HubRequiredForAdditionalCapabilities|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates a provider-specific issue that may affect device functionality. + */ + final class ProviderIssue extends \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json(mixed $json): ProviderIssue|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the key is in a locker that does not support the access codes API. + */ + final class KeynestUnsupportedLocker extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): KeynestUnsupportedLocker|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the accessory keypad exists, but is not linked to the Igloohome Bridge. Online access code programming will fail until the keypad is linked to the Igloohome Bridge in the Igloohome app. + */ + final class AccessoryKeypadSetupRequired extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): AccessoryKeypadSetupRequired|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the device may optimistically be reported as online because the provider does not reliably report its online status. + */ + final class UnreliableOnlineStatus extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): UnreliableOnlineStatus|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the device has reached its maximum number of active access codes. Delete existing codes before creating new ones. + */ + final class MaxAccessCodesReached extends + \Seam\Resources\UnmanagedDevice\Warnings + { + public static function from_json( + mixed $json, + ): MaxAccessCodesReached|null { + if (!$json) { + return null; + } + return new self( + active_access_code_count: $json->active_access_code_count ?? + null, + created_at: $json->created_at ?? null, + max_active_access_code_count: $json->max_active_access_code_count ?? + null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Number of active access codes on the device when the warning was set. + */ + public int|null $active_access_code_count, + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Maximum number of active access codes supported by the device. + */ + public int|null $max_active_access_code_count, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedDevice\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case PARTIAL_BACKUP_ACCESS_CODE_POOL = "partial_backup_access_code_pool"; + case MANY_ACTIVE_BACKUP_CODES = "many_active_backup_codes"; + case THIRD_PARTY_INTEGRATION_DETECTED = "third_party_integration_detected"; + case TTLOCK_LOCK_GATEWAY_UNLOCKING_NOT_ENABLED = "ttlock_lock_gateway_unlocking_not_enabled"; + case TTLOCK_WEAK_GATEWAY_SIGNAL = "ttlock_weak_gateway_signal"; + case POWER_SAVING_MODE = "power_saving_mode"; + case TEMPERATURE_THRESHOLD_EXCEEDED = "temperature_threshold_exceeded"; + case DEVICE_COMMUNICATION_DEGRADED = "device_communication_degraded"; + case SCHEDULED_MAINTENANCE_WINDOW = "scheduled_maintenance_window"; + case DEVICE_HAS_FLAKY_CONNECTION = "device_has_flaky_connection"; + case SALTO_KS_OFFICE_MODE = "salto_ks_office_mode"; + case SALTO_KS_PRIVACY_MODE = "salto_ks_privacy_mode"; + case PRIVACY_MODE = "privacy_mode"; + case SALTO_KS_SUBSCRIPTION_LIMIT_ALMOST_REACHED = "salto_ks_subscription_limit_almost_reached"; + case SALTO_KS_LOCK_ACCESS_CODE_SUPPORT_REMOVED = "salto_ks_lock_access_code_support_removed"; + case UNKNOWN_ISSUE_WITH_PHONE = "unknown_issue_with_phone"; + case LOCKLY_TIME_ZONE_NOT_CONFIGURED = "lockly_time_zone_not_configured"; + case ULTRALOQ_TIME_ZONE_UNKNOWN = "ultraloq_time_zone_unknown"; + case TIME_ZONE_UNKNOWN = "time_zone_unknown"; + case TIME_ZONE_MISMATCH = "time_zone_mismatch"; + case TWO_N_DEVICE_MISSING_TIMEZONE = "two_n_device_missing_timezone"; + case HUB_REQUIRED_FOR_ADDITIONAL_CAPABILITIES = "hub_required_for_additional_capabilities"; + case PROVIDER_ISSUE = "provider_issue"; + case KEYNEST_UNSUPPORTED_LOCKER = "keynest_unsupported_locker"; + case ACCESSORY_KEYPAD_SETUP_REQUIRED = "accessory_keypad_setup_required"; + case UNRELIABLE_ONLINE_STATUS = "unreliable_online_status"; + case MAX_ACCESS_CODES_REACHED = "max_access_codes_reached"; + } } diff --git a/src/Resources/UnmanagedUserIdentity.php b/src/Resources/UnmanagedUserIdentity.php index 6f950ce7..c7b8cfeb 100644 --- a/src/Resources/UnmanagedUserIdentity.php +++ b/src/Resources/UnmanagedUserIdentity.php @@ -1,155 +1,356 @@ acs_user_ids ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + email_address: $json->email_address ?? null, + errors: array_map( + fn( + $e, + ) => \Seam\Resources\UnmanagedUserIdentity\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + full_name: $json->full_name ?? null, + phone_number: $json->phone_number ?? null, + user_identity_id: $json->user_identity_id ?? null, + warnings: array_map( + fn( + $w, + ) => \Seam\Resources\UnmanagedUserIdentity\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + ); } - return new self( - acs_user_ids: $json->acs_user_ids ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - email_address: $json->email_address ?? null, - errors: array_map( - fn($e) => UnmanagedUserIdentityErrors::from_json($e), - $json->errors ?? [], - ), - full_name: $json->full_name ?? null, - phone_number: $json->phone_number ?? null, - user_identity_id: $json->user_identity_id ?? null, - warnings: array_map( - fn($w) => UnmanagedUserIdentityWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); + + public function __construct( + /** + * Array of access system user IDs associated with the user identity. + * + * @var list|null + */ + public array|null $acs_user_ids, + /** + * Date and time at which the user identity was created. + */ + public string|null $created_at, + /** + * Display name for the user identity. + */ + public string|null $display_name, + /** + * Unique email address for the user identity. + */ + public string|null $email_address, + /** + * Array of errors associated with the user identity. Each error object within the array contains fields like "error_code" and "message." "error_code" is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. + * + * @var list<\Seam\Resources\UnmanagedUserIdentity\Errors> + */ + public array $errors, + /** + * Full name of the user associated with the user identity. + */ + public string|null $full_name, + /** + * Unique phone number for the user identity in [E.164 format](https://www.itu.int/rec/T-REC-E.164/en) (for example, +15555550100). + */ + public string|null $phone_number, + /** + * ID of the user identity. + */ + public string|null $user_identity_id, + /** + * Array of warnings associated with the user identity. Each warning object within the array contains two fields: "warning_code" and "message." "warning_code" is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. + * + * @var list<\Seam\Resources\UnmanagedUserIdentity\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the user identity. + */ + public string|null $workspace_id, + ) {} + } +} + +namespace Seam\Resources\UnmanagedUserIdentity { + /** + * Array of errors associated with the user identity. Each error object within the array contains fields like "error_code" and "message." "error_code" is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\UnmanagedUserIdentity\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedUserIdentity\Errors\ErrorCode::ISSUE_WITH_ACS_USER + => \Seam\Resources\UnmanagedUserIdentity\Errors\IssueWithAcsUser::from_json( + $json, + ), + default => new self( + acs_system_id: $json->acs_system_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * ID of the access system that the user identity is associated with. + */ + public string|null $acs_system_id, + /** + * ID of the access system user that has an issue. + */ + public string|null $acs_user_id, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedUserIdentity\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} } - public function __construct( - /** - * Array of access system user IDs associated with the user identity. - */ - public array|null $acs_user_ids, - /** - * Date and time at which the user identity was created. - */ - public string|null $created_at, - /** - * Display name for the user identity. - */ - public string|null $display_name, - /** - * Unique email address for the user identity. - */ - public string|null $email_address, - /** - * Array of errors associated with the user identity. Each error object within the array contains fields like "error_code" and "message." "error_code" is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. - */ - public array $errors, - /** - * Full name of the user associated with the user identity. - */ - public string|null $full_name, - /** - * Unique phone number for the user identity in [E.164 format](https://www.itu.int/rec/T-REC-E.164/en) (for example, +15555550100). - */ - public string|null $phone_number, - /** - * ID of the user identity. - */ - public string|null $user_identity_id, - /** - * Array of warnings associated with the user identity. Each warning object within the array contains two fields: "warning_code" and "message." "warning_code" is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - */ - public array $warnings, - /** - * ID of the workspace that contains the user identity. - */ - public string|null $workspace_id, - ) {} + /** + * Array of warnings associated with the user identity. Each warning object within the array contains two fields: "warning_code" and "message." "warning_code" is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\UnmanagedUserIdentity\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UnmanagedUserIdentity\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\UnmanagedUserIdentity\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\UnmanagedUserIdentity\Warnings\WarningCode::ACS_USER_PROFILE_DOES_NOT_MATCH_USER_IDENTITY + => \Seam\Resources\UnmanagedUserIdentity\Warnings\AcsUserProfileDoesNotMatchUserIdentity::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedUserIdentity\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } } -/** - * Array of errors associated with the user identity. Each error object within the array contains fields like "error_code" and "message." "error_code" is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. - */ -class UnmanagedUserIdentityErrors -{ - public static function from_json( - mixed $json, - ): UnmanagedUserIdentityErrors|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedUserIdentity\Errors { + /** + * Indicates that there is an issue with an access system user associated with this user identity. + */ + final class IssueWithAcsUser extends + \Seam\Resources\UnmanagedUserIdentity\Errors + { + public static function from_json(mixed $json): IssueWithAcsUser|null + { + if (!$json) { + return null; + } + return new self( + acs_system_id: $json->acs_system_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * ID of the access system that the user identity is associated with. + */ + string|null $acs_system_id, + /** + * ID of the access system user that has an issue. + */ + string|null $acs_user_id, + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedUserIdentity\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + acs_system_id: $acs_system_id, + acs_user_id: $acs_user_id, + created_at: $created_at, + error_code: $error_code, + message: $message, + ); } - return new self( - acs_system_id: $json->acs_system_id ?? null, - acs_user_id: $json->acs_user_id ?? null, - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); } - public function __construct( - /** - * ID of the access system that the user identity is associated with. - */ - public string|null $acs_system_id, - /** - * ID of the access system user that has an issue. - */ - public string|null $acs_user_id, - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} + enum ErrorCode: string + { + case ISSUE_WITH_ACS_USER = "issue_with_acs_user"; + } } -/** - * Array of warnings associated with the user identity. Each warning object within the array contains two fields: "warning_code" and "message." "warning_code" is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - */ -class UnmanagedUserIdentityWarnings -{ - public static function from_json( - mixed $json, - ): UnmanagedUserIdentityWarnings|null { - if (!$json) { - return null; +namespace Seam\Resources\UnmanagedUserIdentity\Warnings { + /** + * Indicates that the user identity is currently being deleted. + */ + final class BeingDeleted extends + \Seam\Resources\UnmanagedUserIdentity\Warnings + { + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedUserIdentity\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + /** + * Indicates that the ACS user's profile does not match the user identity's profile + */ + final class AcsUserProfileDoesNotMatchUserIdentity extends + \Seam\Resources\UnmanagedUserIdentity\Warnings + { + public static function from_json( + mixed $json, + ): AcsUserProfileDoesNotMatchUserIdentity|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UnmanagedUserIdentity\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + enum WarningCode: string + { + case BEING_DELETED = "being_deleted"; + case ACS_USER_PROFILE_DOES_NOT_MATCH_USER_IDENTITY = "acs_user_profile_does_not_match_user_identity"; + } } diff --git a/src/Resources/UserIdentity.php b/src/Resources/UserIdentity.php index d96105cd..3153515c 100644 --- a/src/Resources/UserIdentity.php +++ b/src/Resources/UserIdentity.php @@ -1,158 +1,354 @@ acs_user_ids ?? null, + created_at: $json->created_at ?? null, + display_name: $json->display_name ?? null, + email_address: $json->email_address ?? null, + errors: array_map( + fn($e) => \Seam\Resources\UserIdentity\Errors::from_json( + $e, + ), + $json->errors ?? [], + ), + full_name: $json->full_name ?? null, + phone_number: $json->phone_number ?? null, + user_identity_id: $json->user_identity_id ?? null, + user_identity_key: $json->user_identity_key ?? null, + warnings: array_map( + fn($w) => \Seam\Resources\UserIdentity\Warnings::from_json( + $w, + ), + $json->warnings ?? [], + ), + workspace_id: $json->workspace_id ?? null, + ); } - return new self( - acs_user_ids: $json->acs_user_ids ?? null, - created_at: $json->created_at ?? null, - display_name: $json->display_name ?? null, - email_address: $json->email_address ?? null, - errors: array_map( - fn($e) => UserIdentityErrors::from_json($e), - $json->errors ?? [], - ), - full_name: $json->full_name ?? null, - phone_number: $json->phone_number ?? null, - user_identity_id: $json->user_identity_id ?? null, - user_identity_key: $json->user_identity_key ?? null, - warnings: array_map( - fn($w) => UserIdentityWarnings::from_json($w), - $json->warnings ?? [], - ), - workspace_id: $json->workspace_id ?? null, - ); + + public function __construct( + /** + * Array of access system user IDs associated with the user identity. + * + * @var list|null + */ + public array|null $acs_user_ids, + /** + * Date and time at which the user identity was created. + */ + public string|null $created_at, + /** + * Display name for the user identity. + */ + public string|null $display_name, + /** + * Unique email address for the user identity. + */ + public string|null $email_address, + /** + * Array of errors associated with the user identity. Each error object within the array contains fields like "error_code" and "message." "error_code" is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. + * + * @var list<\Seam\Resources\UserIdentity\Errors> + */ + public array $errors, + /** + * Full name of the user associated with the user identity. + */ + public string|null $full_name, + /** + * Unique phone number for the user identity in [E.164 format](https://www.itu.int/rec/T-REC-E.164/en) (for example, +15555550100). + */ + public string|null $phone_number, + /** + * ID of the user identity. + */ + public string|null $user_identity_id, + /** + * Unique key for the user identity. + */ + public string|null $user_identity_key, + /** + * Array of warnings associated with the user identity. Each warning object within the array contains two fields: "warning_code" and "message." "warning_code" is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. + * + * @var list<\Seam\Resources\UserIdentity\Warnings> + */ + public array $warnings, + /** + * ID of the workspace that contains the user identity. + */ + public string|null $workspace_id, + ) {} + } +} + +namespace Seam\Resources\UserIdentity { + /** + * Array of errors associated with the user identity. Each error object within the array contains fields like "error_code" and "message." "error_code" is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. Known error_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Errors + { + public static function from_json(mixed $json): Errors|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->error_code ?? null) + ? \Seam\Resources\UserIdentity\Errors\ErrorCode::tryFrom( + $json->error_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UserIdentity\Errors\ErrorCode::ISSUE_WITH_ACS_USER + => \Seam\Resources\UserIdentity\Errors\IssueWithAcsUser::from_json( + $json, + ), + default => new self( + acs_system_id: $json->acs_system_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ), + }; + } + + public function __construct( + /** + * ID of the access system that the user identity is associated with. + */ + public string|null $acs_system_id, + /** + * ID of the access system user that has an issue. + */ + public string|null $acs_user_id, + /** + * Date and time at which Seam created the error. + */ + public string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UserIdentity\Errors\ErrorCode>|string|null + */ + public string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + ) {} } - public function __construct( - /** - * Array of access system user IDs associated with the user identity. - */ - public array|null $acs_user_ids, - /** - * Date and time at which the user identity was created. - */ - public string|null $created_at, - /** - * Display name for the user identity. - */ - public string|null $display_name, - /** - * Unique email address for the user identity. - */ - public string|null $email_address, - /** - * Array of errors associated with the user identity. Each error object within the array contains fields like "error_code" and "message." "error_code" is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. - */ - public array $errors, - /** - * Full name of the user associated with the user identity. - */ - public string|null $full_name, - /** - * Unique phone number for the user identity in [E.164 format](https://www.itu.int/rec/T-REC-E.164/en) (for example, +15555550100). - */ - public string|null $phone_number, - /** - * ID of the user identity. - */ - public string|null $user_identity_id, - /** - * Unique key for the user identity. - */ - public string|null $user_identity_key, - /** - * Array of warnings associated with the user identity. Each warning object within the array contains two fields: "warning_code" and "message." "warning_code" is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - */ - public array $warnings, - /** - * ID of the workspace that contains the user identity. - */ - public string|null $workspace_id, - ) {} + /** + * Array of warnings associated with the user identity. Each warning object within the array contains two fields: "warning_code" and "message." "warning_code" is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. Known warning_code values use subclasses; unknown values use this base class and retain their raw discriminator. + */ + class Warnings + { + public static function from_json(mixed $json): Warnings|null + { + if (!$json) { + return null; + } + $discriminant = is_string($json->warning_code ?? null) + ? \Seam\Resources\UserIdentity\Warnings\WarningCode::tryFrom( + $json->warning_code, + ) + : null; + + return match ($discriminant) { + \Seam\Resources\UserIdentity\Warnings\WarningCode::BEING_DELETED + => \Seam\Resources\UserIdentity\Warnings\BeingDeleted::from_json( + $json, + ), + \Seam\Resources\UserIdentity\Warnings\WarningCode::ACS_USER_PROFILE_DOES_NOT_MATCH_USER_IDENTITY + => \Seam\Resources\UserIdentity\Warnings\AcsUserProfileDoesNotMatchUserIdentity::from_json( + $json, + ), + default => new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ), + }; + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + public string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + public string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UserIdentity\Warnings\WarningCode>|string|null + */ + public string|null $warning_code, + ) {} + } } -/** - * Array of errors associated with the user identity. Each error object within the array contains fields like "error_code" and "message." "error_code" is a string that uniquely identifies the type of error, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the error, offering insights into the issue and potentially how to rectify it. - */ -class UserIdentityErrors -{ - public static function from_json(mixed $json): UserIdentityErrors|null +namespace Seam\Resources\UserIdentity\Errors { + /** + * Indicates that there is an issue with an access system user associated with this user identity. + */ + final class IssueWithAcsUser extends \Seam\Resources\UserIdentity\Errors { - if (!$json) { - return null; + public static function from_json(mixed $json): IssueWithAcsUser|null + { + if (!$json) { + return null; + } + return new self( + acs_system_id: $json->acs_system_id ?? null, + acs_user_id: $json->acs_user_id ?? null, + created_at: $json->created_at ?? null, + error_code: $json->error_code ?? null, + message: $json->message ?? null, + ); + } + + public function __construct( + /** + * ID of the access system that the user identity is associated with. + */ + string|null $acs_system_id, + /** + * ID of the access system user that has an issue. + */ + string|null $acs_user_id, + /** + * Date and time at which Seam created the error. + */ + string|null $created_at, + /** + * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UserIdentity\Errors\ErrorCode>|string|null + */ + string|null $error_code, + /** + * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + ) { + parent::__construct( + acs_system_id: $acs_system_id, + acs_user_id: $acs_user_id, + created_at: $created_at, + error_code: $error_code, + message: $message, + ); } - return new self( - acs_system_id: $json->acs_system_id ?? null, - acs_user_id: $json->acs_user_id ?? null, - created_at: $json->created_at ?? null, - error_code: $json->error_code ?? null, - message: $json->message ?? null, - ); } - public function __construct( - /** - * ID of the access system that the user identity is associated with. - */ - public string|null $acs_system_id, - /** - * ID of the access system user that has an issue. - */ - public string|null $acs_user_id, - /** - * Date and time at which Seam created the error. - */ - public string|null $created_at, - /** - * Unique identifier of the type of error. Enables quick recognition and categorization of the issue. - */ - public string|null $error_code, - /** - * Detailed description of the error. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - ) {} + enum ErrorCode: string + { + case ISSUE_WITH_ACS_USER = "issue_with_acs_user"; + } } -/** - * Array of warnings associated with the user identity. Each warning object within the array contains two fields: "warning_code" and "message." "warning_code" is a string that uniquely identifies the type of warning, enabling quick recognition and categorization of the issue. "message" provides a more detailed description of the warning, offering insights into the issue and potentially how to rectify it. - */ -class UserIdentityWarnings -{ - public static function from_json(mixed $json): UserIdentityWarnings|null +namespace Seam\Resources\UserIdentity\Warnings { + /** + * Indicates that the user identity is currently being deleted. + */ + final class BeingDeleted extends \Seam\Resources\UserIdentity\Warnings { - if (!$json) { - return null; + public static function from_json(mixed $json): BeingDeleted|null + { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UserIdentity\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); } - return new self( - created_at: $json->created_at ?? null, - message: $json->message ?? null, - warning_code: $json->warning_code ?? null, - ); } - public function __construct( - /** - * Date and time at which Seam created the warning. - */ - public string|null $created_at, - /** - * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. - */ - public string|null $message, - /** - * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. - */ - public string|null $warning_code, - ) {} + /** + * Indicates that the ACS user's profile does not match the user identity's profile + */ + final class AcsUserProfileDoesNotMatchUserIdentity extends + \Seam\Resources\UserIdentity\Warnings + { + public static function from_json( + mixed $json, + ): AcsUserProfileDoesNotMatchUserIdentity|null { + if (!$json) { + return null; + } + return new self( + created_at: $json->created_at ?? null, + message: $json->message ?? null, + warning_code: $json->warning_code ?? null, + ); + } + + public function __construct( + /** + * Date and time at which Seam created the warning. + */ + string|null $created_at, + /** + * Detailed description of the warning. Provides insights into the issue and potentially how to rectify it. + */ + string|null $message, + /** + * Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. + * + * @var value-of<\Seam\Resources\UserIdentity\Warnings\WarningCode>|string|null + */ + string|null $warning_code, + ) { + parent::__construct( + created_at: $created_at, + message: $message, + warning_code: $warning_code, + ); + } + } + + enum WarningCode: string + { + case BEING_DELETED = "being_deleted"; + case ACS_USER_PROFILE_DOES_NOT_MATCH_USER_IDENTITY = "acs_user_profile_does_not_match_user_identity"; + } } diff --git a/src/Resources/Webhook.php b/src/Resources/Webhook.php index 6a39a342..e1f4f04a 100644 --- a/src/Resources/Webhook.php +++ b/src/Resources/Webhook.php @@ -1,41 +1,43 @@ url ?? null, + webhook_id: $json->webhook_id ?? null, + event_types: $json->event_types ?? null, + secret: $json->secret ?? null, + ); } - return new self( - event_types: $json->event_types ?? null, - secret: $json->secret ?? null, - url: $json->url ?? null, - webhook_id: $json->webhook_id ?? null, - ); - } - public function __construct( - /** - * Types of events that the [webhook](https://docs.seam.co/developer-tools/webhooks) should receive. - */ - public array|null $event_types, - /** - * Secret associated with the [webhook](https://docs.seam.co/developer-tools/webhooks). - */ - public string|null $secret, - /** - * URL for the [webhook](https://docs.seam.co/developer-tools/webhooks). - */ - public string|null $url, - /** - * ID of the webhook. - */ - public string|null $webhook_id, - ) {} + public function __construct( + /** + * URL for the [webhook](https://docs.seam.co/developer-tools/webhooks). + */ + public string|null $url, + /** + * ID of the webhook. + */ + public string|null $webhook_id, + /** + * Types of events that the [webhook](https://docs.seam.co/developer-tools/webhooks) should receive. + * + * @var list|null + */ + public array|null $event_types = null, + /** + * Secret associated with the [webhook](https://docs.seam.co/developer-tools/webhooks). + */ + public string|null $secret = null, + ) {} + } } diff --git a/src/Resources/Workspace.php b/src/Resources/Workspace.php index 86e4acb1..e68dbcd9 100644 --- a/src/Resources/Workspace.php +++ b/src/Resources/Workspace.php @@ -1,116 +1,129 @@ company_name ?? null, - connect_partner_name: $json->connect_partner_name ?? null, - connect_webview_customization: isset( - $json->connect_webview_customization, - ) - ? WorkspaceConnectWebviewCustomization::from_json( + public static function from_json(mixed $json): Workspace|null + { + if (!$json) { + return null; + } + return new self( + company_name: $json->company_name ?? null, + connect_partner_name: $json->connect_partner_name ?? null, + connect_webview_customization: isset( $json->connect_webview_customization, ) - : null, - is_publishable_key_auth_enabled: $json->is_publishable_key_auth_enabled ?? - null, - is_sandbox: $json->is_sandbox ?? null, - is_suspended: $json->is_suspended ?? null, - name: $json->name ?? null, - organization_id: $json->organization_id ?? null, - publishable_key: $json->publishable_key ?? null, - workspace_id: $json->workspace_id ?? null, - ); - } + ? \Seam\Resources\Workspace\ConnectWebviewCustomization::from_json( + $json->connect_webview_customization, + ) + : null, + is_publishable_key_auth_enabled: $json->is_publishable_key_auth_enabled ?? + null, + is_sandbox: $json->is_sandbox ?? null, + is_suspended: $json->is_suspended ?? null, + name: $json->name ?? null, + organization_id: $json->organization_id ?? null, + workspace_id: $json->workspace_id ?? null, + publishable_key: $json->publishable_key ?? null, + ); + } - public function __construct( - /** - * Company name associated with the [workspace](https://docs.seam.co/core-concepts/workspaces). - */ - public string|null $company_name, - /** - * @deprecated Use `company_name` instead. - */ - public string|null $connect_partner_name, - public WorkspaceConnectWebviewCustomization|null $connect_webview_customization, - /** - * Indicates whether publishable key authentication is enabled for this workspace. - */ - public bool|null $is_publishable_key_auth_enabled, - /** - * Indicates whether the workspace is a [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces). - */ - public bool|null $is_sandbox, - /** - * Indicates whether the [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces) is suspended. Seam suspends sandbox workspaces that have not been accessed in 14 days. - */ - public bool|null $is_suspended, - /** - * Name of the [workspace](https://docs.seam.co/core-concepts/workspaces). - */ - public string|null $name, - /** - * ID of the organization to which the workspace belongs, or `null` if the workspace is not assigned to an organization. - */ - public string|null $organization_id, - /** - * Publishable key for the [workspace](https://docs.seam.co/core-concepts/workspaces). This key is used to identify the workspace in client-side applications. - */ - public string|null $publishable_key, - /** - * ID of the workspace. - */ - public string|null $workspace_id, - ) {} + public function __construct( + /** + * Company name associated with the [workspace](https://docs.seam.co/core-concepts/workspaces). + */ + public string|null $company_name, + /** + * @deprecated Use `company_name` instead. + */ + public string|null $connect_partner_name, + public \Seam\Resources\Workspace\ConnectWebviewCustomization|null $connect_webview_customization, + /** + * Indicates whether publishable key authentication is enabled for this workspace. + */ + public bool|null $is_publishable_key_auth_enabled, + /** + * Indicates whether the workspace is a [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces). + */ + public bool|null $is_sandbox, + /** + * Indicates whether the [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces) is suspended. Seam suspends sandbox workspaces that have not been accessed in 14 days. + */ + public bool|null $is_suspended, + /** + * Name of the [workspace](https://docs.seam.co/core-concepts/workspaces). + */ + public string|null $name, + /** + * ID of the organization to which the workspace belongs, or `null` if the workspace is not assigned to an organization. + */ + public string|null $organization_id, + /** + * ID of the workspace. + */ + public string|null $workspace_id, + /** + * Publishable key for the [workspace](https://docs.seam.co/core-concepts/workspaces). This key is used to identify the workspace in client-side applications. + */ + public string|null $publishable_key = null, + ) {} + } } -class WorkspaceConnectWebviewCustomization -{ - public static function from_json( - mixed $json, - ): WorkspaceConnectWebviewCustomization|null { - if (!$json) { - return null; +namespace Seam\Resources\Workspace { + class ConnectWebviewCustomization + { + public static function from_json( + mixed $json, + ): ConnectWebviewCustomization|null { + if (!$json) { + return null; + } + return new self( + inviter_logo_url: $json->inviter_logo_url ?? null, + logo_shape: $json->logo_shape ?? null, + primary_button_color: $json->primary_button_color ?? null, + primary_button_text_color: $json->primary_button_text_color ?? + null, + success_message: $json->success_message ?? null, + ); } - return new self( - inviter_logo_url: $json->inviter_logo_url ?? null, - logo_shape: $json->logo_shape ?? null, - primary_button_color: $json->primary_button_color ?? null, - primary_button_text_color: $json->primary_button_text_color ?? null, - success_message: $json->success_message ?? null, - ); + + public function __construct( + /** + * URL of the inviter logo for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). + */ + public string|null $inviter_logo_url = null, + /** + * Logo shape for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). + * + * @var value-of<\Seam\Resources\Workspace\ConnectWebviewCustomization\LogoShape>|string|null + */ + public string|null $logo_shape = null, + /** + * Primary button color for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). + */ + public string|null $primary_button_color = null, + /** + * Primary button text color for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). + */ + public string|null $primary_button_text_color = null, + /** + * Success message for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). + */ + public string|null $success_message = null, + ) {} } +} - public function __construct( - /** - * URL of the inviter logo for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). - */ - public string|null $inviter_logo_url, - /** - * Logo shape for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). - */ - public string|null $logo_shape, - /** - * Primary button color for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). - */ - public string|null $primary_button_color, - /** - * Primary button text color for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). - */ - public string|null $primary_button_text_color, - /** - * Success message for [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) in the workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). - */ - public string|null $success_message, - ) {} +namespace Seam\Resources\Workspace\ConnectWebviewCustomization { + enum LogoShape: string + { + case CIRCLE = "circle"; + case SQUARE = "square"; + } } diff --git a/src/Routes/AccessCodesClient.php b/src/Routes/AccessCodesClient.php index 1c53b718..5a1bb240 100644 --- a/src/Routes/AccessCodesClient.php +++ b/src/Routes/AccessCodesClient.php @@ -2,19 +2,30 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\AccessCode; -use Seam\SeamClient; class AccessCodesClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public AccessCodesSimulateClient $simulate; public AccessCodesUnmanagedClient $unmanaged; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->simulate = new AccessCodesSimulateClient($seam); - $this->unmanaged = new AccessCodesUnmanagedClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->simulate = new AccessCodesSimulateClient($client, $defaults); + $this->unmanaged = new AccessCodesUnmanagedClient($client, $defaults); } /** @@ -64,9 +75,7 @@ public function create( ): AccessCode { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($allow_external_modification !== null) { $request_payload[ "allow_external_modification" @@ -127,13 +136,15 @@ public function create( ] = $use_offline_access_code; } - $res = $this->seam->request( - "POST", - "/access_codes/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/access_codes/create", [ + "json" => (object) $request_payload, + ]), ); - return AccessCode::from_json($res->access_code); + return AccessCode::from_json( + Body::read($res, "access_code", "/access_codes/create"), + ); } /** @@ -149,7 +160,7 @@ public function create( * * For granting a person access to a space, [Access Grants](https://docs.seam.co/use-cases/granting-access) are the default and recommended approach and work across both standalone smart locks and access systems. Use the lower-level Access Codes API directly only when you specifically need to manage individual PIN codes. * - * @param array $device_ids IDs of the devices for which you want to create the new access codes. + * @param list $device_ids IDs of the devices for which you want to create the new access codes. * @param bool $allow_external_modification Indicates whether [external modification](https://docs.seam.co/low-level-apis/smart-locks/access-codes#external-modification) of the code is allowed. Default: `false`. * @param bool $attempt_for_offline_device * @param string $behavior_when_code_cannot_be_shared Desired behavior if any device cannot share a code. If `throw` (default), no access codes will be created if any device cannot share a code. If `create_random_code`, a random code will be created on devices that cannot share a code. @@ -185,9 +196,7 @@ public function create_multiple( ): array { $request_payload = []; - if ($device_ids !== null) { - $request_payload["device_ids"] = $device_ids; - } + $request_payload["device_ids"] = $device_ids; if ($allow_external_modification !== null) { $request_payload[ "allow_external_modification" @@ -234,15 +243,19 @@ public function create_multiple( ] = $use_backup_access_code_pool; } - $res = $this->seam->request( - "POST", - "/access_codes/create_multiple", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("PUT", "/access_codes/create_multiple", [ + "json" => (object) $request_payload, + ]), ); return array_map( fn($r) => AccessCode::from_json($r), - $res->access_codes, + Body::read_list( + $res, + "access_codes", + "/access_codes/create_multiple", + ), ); } @@ -259,18 +272,14 @@ public function delete( ): void { $request_payload = []; - if ($access_code_id !== null) { - $request_payload["access_code_id"] = $access_code_id; - } + $request_payload["access_code_id"] = $access_code_id; if ($device_id !== null) { $request_payload["device_id"] = $device_id; } - $this->seam->request( - "POST", - "/access_codes/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/access_codes/delete", [ + "query" => $request_payload, + ]); } /** @@ -283,17 +292,17 @@ public function generate_code(string $device_id): AccessCode { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/access_codes/generate_code", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_codes/generate_code", [ + "query" => $request_payload, + ]), ); - return AccessCode::from_json($res->generated_code); + return AccessCode::from_json( + Body::read($res, "generated_code", "/access_codes/generate_code"), + ); } /** @@ -311,6 +320,11 @@ public function get( ?string $code = null, ?string $device_id = null, ): AccessCode { + if ($access_code_id === null && $code === null && $device_id === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /access_codes/get", + ); + } $request_payload = []; if ($access_code_id !== null) { @@ -323,13 +337,15 @@ public function get( $request_payload["device_id"] = $device_id; } - $res = $this->seam->request( - "POST", - "/access_codes/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_codes/get", [ + "query" => $request_payload, + ]), ); - return AccessCode::from_json($res->access_code); + return AccessCode::from_json( + Body::read($res, "access_code", "/access_codes/get"), + ); } /** @@ -337,16 +353,17 @@ public function get( * * Specify `device_id`, `access_code_ids`, `access_method_id`, `access_grant_id`, or `access_grant_key`. * - * @param array $access_code_ids IDs of the access codes that you want to retrieve. Specify `device_id`, `access_code_ids`, `access_method_id`, `access_grant_id`, or `access_grant_key`. + * @param list $access_code_ids IDs of the access codes that you want to retrieve. Specify `device_id`, `access_code_ids`, `access_method_id`, `access_grant_id`, or `access_grant_key`. * @param string $access_grant_id ID of the access grant for which you want to list access codes. Specify `device_id`, `access_code_ids`, `access_method_id`, `access_grant_id`, or `access_grant_key`. * @param string $access_grant_key Key of the access grant for which you want to list access codes. Specify `device_id`, `access_code_ids`, `access_method_id`, `access_grant_id`, or `access_grant_key`. * @param string $access_method_id ID of the access method for which you want to list access codes. Specify `device_id`, `access_code_ids`, `access_method_id`, `access_grant_id`, or `access_grant_key`. * @param string $customer_key Customer key for which you want to list access codes. * @param string $device_id ID of the device for which you want to list access codes. Specify `device_id`, `access_code_ids`, `access_method_id`, `access_grant_id`, or `access_grant_key`. * @param float $limit Numerical limit on the number of access codes to return. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned access codes to include all records that satisfy a partial match using `name`, `code` or `access_code_id`. * @param string $user_identifier_key Your user ID for the user by which to filter access codes. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( @@ -357,11 +374,25 @@ public function list( ?string $customer_key = null, ?string $device_id = null, ?float $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?string $user_identifier_key = null, ?callable $on_response = null, ): array { + if ( + $access_code_ids === null && + $access_grant_id === null && + $access_grant_key === null && + $access_method_id === null && + $customer_key === null && + $device_id === null && + $search === null && + $user_identifier_key === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /access_codes/list", + ); + } $request_payload = []; if ($access_code_ids !== null) { @@ -395,10 +426,10 @@ public function list( $request_payload["user_identifier_key"] = $user_identifier_key; } - $res = $this->seam->request( - "POST", - "/access_codes/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_codes/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -407,7 +438,7 @@ public function list( return array_map( fn($r) => AccessCode::from_json($r), - $res->access_codes, + Body::read_list($res, "access_codes", "/access_codes/list"), ); } @@ -429,17 +460,23 @@ public function pull_backup_access_code(string $access_code_id): AccessCode { $request_payload = []; - if ($access_code_id !== null) { - $request_payload["access_code_id"] = $access_code_id; - } + $request_payload["access_code_id"] = $access_code_id; - $res = $this->seam->request( - "POST", - "/access_codes/pull_backup_access_code", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/access_codes/pull_backup_access_code", + ["json" => (object) $request_payload], + ), ); - return AccessCode::from_json($res->access_code); + return AccessCode::from_json( + Body::read( + $res, + "access_code", + "/access_codes/pull_backup_access_code", + ), + ); } /** @@ -450,7 +487,7 @@ public function pull_backup_access_code(string $access_code_id): AccessCode * @param string $device_id ID of the device for which you want to report constraints. * @param int $max_code_length Maximum supported code length as an integer between 4 and 20, inclusive. You can specify either `min_code_length`/`max_code_length` or `supported_code_lengths`. * @param int $min_code_length Minimum supported code length as an integer between 4 and 20, inclusive. You can specify either `min_code_length`/`max_code_length` or `supported_code_lengths`. - * @param array $supported_code_lengths Array of supported code lengths as integers between 4 and 20, inclusive. You can specify either `supported_code_lengths` or `min_code_length`/`max_code_length`. + * @param list $supported_code_lengths Array of supported code lengths as integers between 4 and 20, inclusive. You can specify either `supported_code_lengths` or `min_code_length`/`max_code_length`. * @return void OK */ public function report_device_constraints( @@ -461,9 +498,7 @@ public function report_device_constraints( ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($max_code_length !== null) { $request_payload["max_code_length"] = $max_code_length; } @@ -476,10 +511,10 @@ public function report_device_constraints( ] = $supported_code_lengths; } - $this->seam->request( + $this->client->request( "POST", "/access_codes/report_device_constraints", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -496,9 +531,6 @@ public function report_device_constraints( * @param string $ends_at Date and time at which the validity of the new access code ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. * @param bool $is_external_modification_allowed Indicates whether [external modification](https://docs.seam.co/low-level-apis/smart-locks/access-codes#external-modification) of the code is allowed. Default: `false`. * @param bool $is_managed Indicates whether the access code is managed through Seam. Note that to convert an unmanaged access code into a managed access code, use `/access_codes/unmanaged/convert_to_managed`. - * @param bool $is_offline_access_code Indicates whether the access code is an [offline access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/offline-access-codes). - * @param bool $is_one_time_use Indicates whether the [offline access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/offline-access-codes) is a single-use access code. - * @param string $max_time_rounding Maximum rounding adjustment. To create a daily-bound [offline access code](https://docs.seam.co/low-level-apis/smart-locks/access-codes/offline-access-codes) for devices that support this feature, set this parameter to `1d`. * @param string $name Name of the new access code. Enables administrators and users to identify the access code easily, especially when there are numerous access codes. Note that the name provided on Seam is used to identify the code on Seam and is not necessarily the name that will appear in the lock provider's app or on the device. This is because lock providers may have constraints on names, such as length, uniqueness, or characters that can be used. In addition, some lock providers may break down names into components such as `first_name` and `last_name`. @@ -506,12 +538,8 @@ public function report_device_constraints( To provide a consistent experience, Seam identifies the code on Seam by its name but may modify the name that appears on the lock provider's app or on the device. For example, Seam may add additional characters or truncate the name to meet provider constraints. To help your users identify codes set by Seam, Seam provides the name exactly as it appears on the lock provider's app or on the device as a separate property called `appearance`. This is an object with a `name` property and, optionally, `first_name` and `last_name` properties (for providers that break down a name into components). - * @param bool $prefer_native_scheduling Indicates whether [native scheduling](https://docs.seam.co/low-level-apis/smart-locks/access-codes#native-scheduling) should be used for time-bound codes when supported by the provider. Default: `true`. - * @param float $preferred_code_length Preferred code length. Only applicable if you do not specify a `code`. If the affected device does not support the preferred code length, Seam reverts to using the shortest supported code length. * @param string $starts_at Date and time at which the validity of the new access code starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. * @param string $type Type to which you want to convert the access code. To convert a time-bound access code to an ongoing access code, set `type` to `ongoing`. See also [Changing a time-bound access code to permanent access](https://docs.seam.co/low-level-apis/smart-locks/access-codes/modifying-access-codes#special-case-2-changing-a-time-bound-access-code-to-permanent-access). - * @param bool $use_backup_access_code_pool Indicates whether to use a [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) provided by Seam. If `true`, you can use [`/access_codes/pull_backup_access_code`](https://docs.seam.co/api/access_codes/pull_backup_access_code). - * @param bool $use_offline_access_code * @return void OK */ public function update( @@ -523,22 +551,13 @@ public function update( ?string $ends_at = null, ?bool $is_external_modification_allowed = null, ?bool $is_managed = null, - ?bool $is_offline_access_code = null, - ?bool $is_one_time_use = null, - ?string $max_time_rounding = null, ?string $name = null, - ?bool $prefer_native_scheduling = null, - ?float $preferred_code_length = null, ?string $starts_at = null, ?string $type = null, - ?bool $use_backup_access_code_pool = null, - ?bool $use_offline_access_code = null, ): void { $request_payload = []; - if ($access_code_id !== null) { - $request_payload["access_code_id"] = $access_code_id; - } + $request_payload["access_code_id"] = $access_code_id; if ($allow_external_modification !== null) { $request_payload[ "allow_external_modification" @@ -566,50 +585,19 @@ public function update( if ($is_managed !== null) { $request_payload["is_managed"] = $is_managed; } - if ($is_offline_access_code !== null) { - $request_payload[ - "is_offline_access_code" - ] = $is_offline_access_code; - } - if ($is_one_time_use !== null) { - $request_payload["is_one_time_use"] = $is_one_time_use; - } - if ($max_time_rounding !== null) { - $request_payload["max_time_rounding"] = $max_time_rounding; - } if ($name !== null) { $request_payload["name"] = $name; } - if ($prefer_native_scheduling !== null) { - $request_payload[ - "prefer_native_scheduling" - ] = $prefer_native_scheduling; - } - if ($preferred_code_length !== null) { - $request_payload["preferred_code_length"] = $preferred_code_length; - } if ($starts_at !== null) { $request_payload["starts_at"] = $starts_at; } if ($type !== null) { $request_payload["type"] = $type; } - if ($use_backup_access_code_pool !== null) { - $request_payload[ - "use_backup_access_code_pool" - ] = $use_backup_access_code_pool; - } - if ($use_offline_access_code !== null) { - $request_payload[ - "use_offline_access_code" - ] = $use_offline_access_code; - } - $this->seam->request( - "POST", - "/access_codes/update", - json: (object) $request_payload, - ); + $this->client->request("PUT", "/access_codes/update", [ + "json" => (object) $request_payload, + ]); } /** @@ -639,9 +627,7 @@ public function update_multiple( ): void { $request_payload = []; - if ($common_code_key !== null) { - $request_payload["common_code_key"] = $common_code_key; - } + $request_payload["common_code_key"] = $common_code_key; if ($ends_at !== null) { $request_payload["ends_at"] = $ends_at; } @@ -652,10 +638,8 @@ public function update_multiple( $request_payload["starts_at"] = $starts_at; } - $this->seam->request( - "POST", - "/access_codes/update_multiple", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/access_codes/update_multiple", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/AccessCodesSimulateClient.php b/src/Routes/AccessCodesSimulateClient.php index 7f1ded04..a94074c3 100644 --- a/src/Routes/AccessCodesSimulateClient.php +++ b/src/Routes/AccessCodesSimulateClient.php @@ -2,16 +2,26 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\UnmanagedAccessCode; -use Seam\SeamClient; class AccessCodesSimulateClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -29,22 +39,24 @@ public function create_unmanaged_access_code( ): UnmanagedAccessCode { $request_payload = []; - if ($code !== null) { - $request_payload["code"] = $code; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($name !== null) { - $request_payload["name"] = $name; - } - - $res = $this->seam->request( - "POST", - "/access_codes/simulate/create_unmanaged_access_code", - json: (object) $request_payload, + $request_payload["code"] = $code; + $request_payload["device_id"] = $device_id; + $request_payload["name"] = $name; + + $res = Body::decode( + $this->client->request( + "POST", + "/access_codes/simulate/create_unmanaged_access_code", + ["json" => (object) $request_payload], + ), ); - return UnmanagedAccessCode::from_json($res->access_code); + return UnmanagedAccessCode::from_json( + Body::read( + $res, + "access_code", + "/access_codes/simulate/create_unmanaged_access_code", + ), + ); } } diff --git a/src/Routes/AccessCodesUnmanagedClient.php b/src/Routes/AccessCodesUnmanagedClient.php index 93ec0001..4c0739fc 100644 --- a/src/Routes/AccessCodesUnmanagedClient.php +++ b/src/Routes/AccessCodesUnmanagedClient.php @@ -2,16 +2,27 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\UnmanagedAccessCode; -use Seam\SeamClient; class AccessCodesUnmanagedClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -35,9 +46,7 @@ public function convert_to_managed( ): void { $request_payload = []; - if ($access_code_id !== null) { - $request_payload["access_code_id"] = $access_code_id; - } + $request_payload["access_code_id"] = $access_code_id; if ($allow_external_modification !== null) { $request_payload[ "allow_external_modification" @@ -52,10 +61,10 @@ public function convert_to_managed( ] = $is_external_modification_allowed; } - $this->seam->request( - "POST", + $this->client->request( + "PATCH", "/access_codes/unmanaged/convert_to_managed", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -69,15 +78,11 @@ public function delete(string $access_code_id): void { $request_payload = []; - if ($access_code_id !== null) { - $request_payload["access_code_id"] = $access_code_id; - } + $request_payload["access_code_id"] = $access_code_id; - $this->seam->request( - "POST", - "/access_codes/unmanaged/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/access_codes/unmanaged/delete", [ + "query" => $request_payload, + ]); } /** @@ -95,6 +100,11 @@ public function get( ?string $code = null, ?string $device_id = null, ): UnmanagedAccessCode { + if ($access_code_id === null && $code === null && $device_id === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /access_codes/unmanaged/get", + ); + } $request_payload = []; if ($access_code_id !== null) { @@ -107,13 +117,15 @@ public function get( $request_payload["device_id"] = $device_id; } - $res = $this->seam->request( - "POST", - "/access_codes/unmanaged/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_codes/unmanaged/get", [ + "query" => $request_payload, + ]), ); - return UnmanagedAccessCode::from_json($res->access_code); + return UnmanagedAccessCode::from_json( + Body::read($res, "access_code", "/access_codes/unmanaged/get"), + ); } /** @@ -121,24 +133,23 @@ public function get( * * @param string $device_id ID of the device for which you want to list unmanaged access codes. * @param float $limit Numerical limit on the number of unmanaged access codes to return. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned access codes to include all records that satisfy a partial match using `name`, `code` or `access_code_id`. * @param string $user_identifier_key Your user ID for the user by which to filter unmanaged access codes. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( string $device_id, ?float $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?string $user_identifier_key = null, ?callable $on_response = null, ): array { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($limit !== null) { $request_payload["limit"] = $limit; } @@ -152,10 +163,10 @@ public function list( $request_payload["user_identifier_key"] = $user_identifier_key; } - $res = $this->seam->request( - "POST", - "/access_codes/unmanaged/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_codes/unmanaged/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -164,7 +175,11 @@ public function list( return array_map( fn($r) => UnmanagedAccessCode::from_json($r), - $res->access_codes, + Body::read_list( + $res, + "access_codes", + "/access_codes/unmanaged/list", + ), ); } @@ -187,12 +202,8 @@ public function update( ): void { $request_payload = []; - if ($access_code_id !== null) { - $request_payload["access_code_id"] = $access_code_id; - } - if ($is_managed !== null) { - $request_payload["is_managed"] = $is_managed; - } + $request_payload["access_code_id"] = $access_code_id; + $request_payload["is_managed"] = $is_managed; if ($allow_external_modification !== null) { $request_payload[ "allow_external_modification" @@ -207,10 +218,8 @@ public function update( ] = $is_external_modification_allowed; } - $this->seam->request( - "POST", - "/access_codes/unmanaged/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/access_codes/unmanaged/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/AccessGrantsClient.php b/src/Routes/AccessGrantsClient.php index 42c99bd3..8e391a66 100644 --- a/src/Routes/AccessGrantsClient.php +++ b/src/Routes/AccessGrantsClient.php @@ -2,37 +2,48 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\AccessGrant; use Seam\Resources\Batch; -use Seam\SeamClient; class AccessGrantsClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public AccessGrantsUnmanagedClient $unmanaged; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->unmanaged = new AccessGrantsUnmanagedClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->unmanaged = new AccessGrantsUnmanagedClient($client, $defaults); } /** * Creates a new [Access Grant](https://docs.seam.co/use-cases/granting-access/access-grants). Access Grants are the default and recommended way to grant a user access to any physical space, irrespective of the locking hardware. They work with both standalone smart locks (using `device_ids`) and access control systems (using `acs_entrance_ids` or `space_ids`), and can issue PIN codes, key cards, and mobile keys through a single request. * - * @param array $requested_access_methods + * @param list|\stdClass> $requested_access_methods * @param string $user_identity_id ID of user identity for whom access is being granted. * @param mixed $user_identity When used, creates a new user identity with the given details, and grants them access. * @param string $access_grant_key Unique key for the access grant within the workspace. - * @param array $acs_entrance_ids Set of IDs of the [entrances](https://docs.seam.co/api/acs/systems/list) to which access is being granted. + * @param list $acs_entrance_ids Set of IDs of the [entrances](https://docs.seam.co/api/acs/systems/list) to which access is being granted. * @param string $customization_profile_id ID of the customization profile to apply to the Access Grant and its access methods. - * @param array $device_ids Set of IDs of the [devices](https://docs.seam.co/api/devices/list) to which access is being granted. - * @param string $ends_at Date and time at which the validity of the new grant ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. + * @param list $device_ids Set of IDs of the [devices](https://docs.seam.co/api/devices/list) to which access is being granted. + * @param string|NullValue $ends_at Date and time at which the validity of the new grant ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. * @param mixed $location - * @param array $location_ids - * @param string $name Name for the access grant. + * @param list $location_ids + * @param string|NullValue $name Name for the access grant. * @param string $reservation_key Reservation key for the access grant. - * @param array $space_ids Set of IDs of existing spaces to which access is being granted. - * @param array $space_keys Set of keys of existing spaces to which access is being granted. + * @param list $space_ids Set of IDs of existing spaces to which access is being granted. + * @param list $space_keys Set of keys of existing spaces to which access is being granted. * @param string $starts_at Date and time at which the validity of the new grant starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. * @return AccessGrant OK */ @@ -44,10 +55,10 @@ public function create( ?array $acs_entrance_ids = null, ?string $customization_profile_id = null, ?array $device_ids = null, - ?string $ends_at = null, + string|NullValue|null $ends_at = null, mixed $location = null, ?array $location_ids = null, - ?string $name = null, + string|NullValue|null $name = null, ?string $reservation_key = null, ?array $space_ids = null, ?array $space_keys = null, @@ -55,11 +66,9 @@ public function create( ): AccessGrant { $request_payload = []; - if ($requested_access_methods !== null) { - $request_payload[ - "requested_access_methods" - ] = $requested_access_methods; - } + $request_payload[ + "requested_access_methods" + ] = $requested_access_methods; if ($user_identity_id !== null) { $request_payload["user_identity_id"] = $user_identity_id; } @@ -105,13 +114,15 @@ public function create( $request_payload["starts_at"] = $starts_at; } - $res = $this->seam->request( - "POST", - "/access_grants/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/access_grants/create", [ + "json" => (object) $request_payload, + ]), ); - return AccessGrant::from_json($res->access_grant); + return AccessGrant::from_json( + Body::read($res, "access_grant", "/access_grants/create"), + ); } /** @@ -124,15 +135,11 @@ public function delete(string $access_grant_id): void { $request_payload = []; - if ($access_grant_id !== null) { - $request_payload["access_grant_id"] = $access_grant_id; - } + $request_payload["access_grant_id"] = $access_grant_id; - $this->seam->request( - "POST", - "/access_grants/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/access_grants/delete", [ + "query" => $request_payload, + ]); } /** @@ -146,6 +153,11 @@ public function get( ?string $access_grant_id = null, ?string $access_grant_key = null, ): AccessGrant { + if ($access_grant_id === null && $access_grant_key === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /access_grants/get", + ); + } $request_payload = []; if ($access_grant_id !== null) { @@ -155,22 +167,24 @@ public function get( $request_payload["access_grant_key"] = $access_grant_key; } - $res = $this->seam->request( - "POST", - "/access_grants/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_grants/get", [ + "query" => $request_payload, + ]), ); - return AccessGrant::from_json($res->access_grant); + return AccessGrant::from_json( + Body::read($res, "access_grant", "/access_grants/get"), + ); } /** * Gets all related resources for one or more Access Grants. * - * @param array $access_grant_ids IDs of the access grants that you want to get along with their related resources. - * @param array $access_grant_keys Keys of the access grants that you want to get along with their related resources. - * @param array $exclude - * @param array $include + * @param list $access_grant_ids IDs of the access grants that you want to get along with their related resources. + * @param list $access_grant_keys Keys of the access grants that you want to get along with their related resources. + * @param list $exclude + * @param list $include * @return Batch OK */ public function get_related( @@ -179,6 +193,16 @@ public function get_related( ?array $exclude = null, ?array $include = null, ): Batch { + if ( + $access_grant_ids === null && + $access_grant_keys === null && + $exclude === null && + $include === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /access_grants/get_related", + ); + } $request_payload = []; if ($access_grant_ids !== null) { @@ -194,44 +218,47 @@ public function get_related( $request_payload["include"] = $include; } - $res = $this->seam->request( - "POST", - "/access_grants/get_related", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_grants/get_related", [ + "query" => $request_payload, + ]), ); - return Batch::from_json($res->batch); + return Batch::from_json( + Body::read($res, "batch", "/access_grants/get_related"), + ); } /** * Gets an Access Grant. * * @param string $access_code_id ID of the access code by which you want to filter the list of Access Grants. - * @param array $access_grant_ids IDs of the access grants to retrieve. - * @param string $access_grant_key Filter Access Grants by access_grant_key. Use null to filter for Access Grants without an access_grant_key. + * @param list $access_grant_ids IDs of the access grants to retrieve. + * @param string|NullValue $access_grant_key Filter Access Grants by access_grant_key. Use null to filter for Access Grants without an access_grant_key. * @param string $acs_entrance_id ID of the entrance by which you want to filter the list of Access Grants. * @param string $acs_system_id ID of the access system by which you want to filter the list of Access Grants. * @param string $customer_key Customer key for which you want to list access grants. * @param string $device_id ID of the device by which you want to filter the list of Access Grants. * @param float $limit Numerical limit on the number of access grants to return. * @param string $location_id - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $reservation_key Filter Access Grants by reservation_key. * @param string $space_id ID of the space by which you want to filter the list of Access Grants. * @param string $user_identity_id ID of user identity by which you want to filter the list of Access Grants. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( ?string $access_code_id = null, ?array $access_grant_ids = null, - ?string $access_grant_key = null, + string|NullValue|null $access_grant_key = null, ?string $acs_entrance_id = null, ?string $acs_system_id = null, ?string $customer_key = null, ?string $device_id = null, ?float $limit = null, ?string $location_id = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $reservation_key = null, ?string $space_id = null, ?string $user_identity_id = null, @@ -279,10 +306,10 @@ public function list( $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/access_grants/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_grants/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -291,7 +318,7 @@ public function list( return array_map( fn($r) => AccessGrant::from_json($r), - $res->access_grants, + Body::read_list($res, "access_grants", "/access_grants/list"), ); } @@ -299,7 +326,7 @@ public function list( * Adds additional requested access methods to an existing Access Grant. * * @param string $access_grant_id ID of the Access Grant to add access methods to. - * @param array $requested_access_methods Array of requested access methods to add to the access grant. + * @param list|\stdClass> $requested_access_methods Array of requested access methods to add to the access grant. * @return AccessGrant OK */ public function request_access_methods( @@ -308,22 +335,26 @@ public function request_access_methods( ): AccessGrant { $request_payload = []; - if ($access_grant_id !== null) { - $request_payload["access_grant_id"] = $access_grant_id; - } - if ($requested_access_methods !== null) { - $request_payload[ - "requested_access_methods" - ] = $requested_access_methods; - } + $request_payload["access_grant_id"] = $access_grant_id; + $request_payload[ + "requested_access_methods" + ] = $requested_access_methods; - $res = $this->seam->request( - "POST", - "/access_grants/request_access_methods", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/access_grants/request_access_methods", + ["json" => (object) $request_payload], + ), ); - return AccessGrant::from_json($res->access_grant); + return AccessGrant::from_json( + Body::read( + $res, + "access_grant", + "/access_grants/request_access_methods", + ), + ); } /** @@ -331,18 +362,29 @@ public function request_access_methods( * * @param string $access_grant_id ID of the Access Grant to update. Provide either `access_grant_id` or `access_grant_key`. * @param string $access_grant_key Key of the Access Grant to update. Provide either `access_grant_id` or `access_grant_key`. - * @param string $ends_at Date and time at which the validity of the grant ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. - * @param string $name Display name for the access grant. + * @param string|NullValue $ends_at Date and time at which the validity of the grant ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Must be a time in the future and after `starts_at`. + * @param string|NullValue $name Display name for the access grant. * @param string $starts_at Date and time at which the validity of the grant starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. * @return void OK */ public function update( ?string $access_grant_id = null, ?string $access_grant_key = null, - ?string $ends_at = null, - ?string $name = null, + string|NullValue|null $ends_at = null, + string|NullValue|null $name = null, ?string $starts_at = null, ): void { + if ( + $access_grant_id === null && + $access_grant_key === null && + $ends_at === null && + $name === null && + $starts_at === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /access_grants/update", + ); + } $request_payload = []; if ($access_grant_id !== null) { @@ -361,10 +403,8 @@ public function update( $request_payload["starts_at"] = $starts_at; } - $this->seam->request( - "POST", - "/access_grants/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/access_grants/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/AccessGrantsUnmanagedClient.php b/src/Routes/AccessGrantsUnmanagedClient.php index 84c4c449..b22800b8 100644 --- a/src/Routes/AccessGrantsUnmanagedClient.php +++ b/src/Routes/AccessGrantsUnmanagedClient.php @@ -2,16 +2,27 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\UnmanagedAccessGrant; -use Seam\SeamClient; class AccessGrantsUnmanagedClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -24,17 +35,17 @@ public function get(string $access_grant_id): UnmanagedAccessGrant { $request_payload = []; - if ($access_grant_id !== null) { - $request_payload["access_grant_id"] = $access_grant_id; - } + $request_payload["access_grant_id"] = $access_grant_id; - $res = $this->seam->request( - "POST", - "/access_grants/unmanaged/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_grants/unmanaged/get", [ + "query" => $request_payload, + ]), ); - return UnmanagedAccessGrant::from_json($res->access_grant); + return UnmanagedAccessGrant::from_json( + Body::read($res, "access_grant", "/access_grants/unmanaged/get"), + ); } /** @@ -43,16 +54,17 @@ public function get(string $access_grant_id): UnmanagedAccessGrant * @param string $acs_entrance_id ID of the entrance by which you want to filter the list of unmanaged Access Grants. * @param string $acs_system_id ID of the access system by which you want to filter the list of unmanaged Access Grants. * @param float $limit Numerical limit on the number of unmanaged access grants to return. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $reservation_key Filter unmanaged Access Grants by reservation_key. * @param string $user_identity_id ID of user identity by which you want to filter the list of unmanaged Access Grants. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( ?string $acs_entrance_id = null, ?string $acs_system_id = null, ?float $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $reservation_key = null, ?string $user_identity_id = null, ?callable $on_response = null, @@ -78,10 +90,10 @@ public function list( $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/access_grants/unmanaged/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_grants/unmanaged/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -90,7 +102,11 @@ public function list( return array_map( fn($r) => UnmanagedAccessGrant::from_json($r), - $res->access_grants, + Body::read_list( + $res, + "access_grants", + "/access_grants/unmanaged/list", + ), ); } @@ -102,31 +118,25 @@ public function list( * When converting an unmanaged access grant to managed, all associated access methods will also be converted to managed. * * @param string $access_grant_id ID of the unmanaged Access Grant to update. - * @param bool $is_managed Must be set to true to convert the unmanaged access grant to managed. + * @param true $is_managed Must be set to true to convert the unmanaged access grant to managed. * @param string $access_grant_key Unique key for the access grant. If not provided, the existing key will be preserved. * @return void OK */ public function update( string $access_grant_id, - bool $is_managed, + true $is_managed, ?string $access_grant_key = null, ): void { $request_payload = []; - if ($access_grant_id !== null) { - $request_payload["access_grant_id"] = $access_grant_id; - } - if ($is_managed !== null) { - $request_payload["is_managed"] = $is_managed; - } + $request_payload["access_grant_id"] = $access_grant_id; + $request_payload["is_managed"] = $is_managed; if ($access_grant_key !== null) { $request_payload["access_grant_key"] = $access_grant_key; } - $this->seam->request( - "POST", - "/access_grants/unmanaged/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/access_grants/unmanaged/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/AccessMethodsClient.php b/src/Routes/AccessMethodsClient.php index 6e9d975d..0b296a17 100644 --- a/src/Routes/AccessMethodsClient.php +++ b/src/Routes/AccessMethodsClient.php @@ -2,19 +2,31 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; +use Seam\NullValue; use Seam\Resources\AccessMethod; use Seam\Resources\ActionAttempt; use Seam\Resources\Batch; -use Seam\SeamClient; class AccessMethodsClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public AccessMethodsUnmanagedClient $unmanaged; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->unmanaged = new AccessMethodsUnmanagedClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->unmanaged = new AccessMethodsUnmanagedClient($client, $defaults); } /** @@ -22,37 +34,37 @@ public function __construct(SeamClient $seam) * * @param string $access_method_id ID of the `access_method` to assign the credential to. * @param string $card_number Card number of the credential to assign. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function assign_card( string $access_method_id, string $card_number, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($access_method_id !== null) { - $request_payload["access_method_id"] = $access_method_id; - } - if ($card_number !== null) { - $request_payload["card_number"] = $card_number; - } + $request_payload["access_method_id"] = $access_method_id; + $request_payload["card_number"] = $card_number; - $res = $this->seam->request( - "POST", - "/access_methods/assign_card", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/access_methods/assign_card", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/access_methods/assign_card", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -68,6 +80,15 @@ public function delete( ?string $access_grant_id = null, ?string $reservation_key = null, ): void { + if ( + $access_method_id === null && + $access_grant_id === null && + $reservation_key === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /access_methods/delete", + ); + } $request_payload = []; if ($access_method_id !== null) { @@ -80,11 +101,9 @@ public function delete( $request_payload["reservation_key"] = $reservation_key; } - $this->seam->request( - "POST", - "/access_methods/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/access_methods/delete", [ + "query" => $request_payload, + ]); } /** @@ -92,37 +111,33 @@ public function delete( * * @param string $access_method_id ID of the `access_method` to encode onto a card. * @param string $acs_encoder_id ID of the `acs_encoder` to use to encode the `access_method`. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function encode( string $access_method_id, string $acs_encoder_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($access_method_id !== null) { - $request_payload["access_method_id"] = $access_method_id; - } - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["access_method_id"] = $access_method_id; + $request_payload["acs_encoder_id"] = $acs_encoder_id; - $res = $this->seam->request( - "POST", - "/access_methods/encode", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/access_methods/encode", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/access_methods/encode"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -135,25 +150,25 @@ public function get(string $access_method_id): AccessMethod { $request_payload = []; - if ($access_method_id !== null) { - $request_payload["access_method_id"] = $access_method_id; - } + $request_payload["access_method_id"] = $access_method_id; - $res = $this->seam->request( - "POST", - "/access_methods/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_methods/get", [ + "query" => $request_payload, + ]), ); - return AccessMethod::from_json($res->access_method); + return AccessMethod::from_json( + Body::read($res, "access_method", "/access_methods/get"), + ); } /** * Gets all related resources for one or more Access Methods. * - * @param array $access_method_ids IDs of the access methods that you want to get along with their related resources. - * @param array $exclude - * @param array $include + * @param list $access_method_ids IDs of the access methods that you want to get along with their related resources. + * @param list $exclude + * @param list $include * @return Batch OK */ public function get_related( @@ -163,9 +178,7 @@ public function get_related( ): Batch { $request_payload = []; - if ($access_method_ids !== null) { - $request_payload["access_method_ids"] = $access_method_ids; - } + $request_payload["access_method_ids"] = $access_method_ids; if ($exclude !== null) { $request_payload["exclude"] = $exclude; } @@ -173,13 +186,15 @@ public function get_related( $request_payload["include"] = $include; } - $res = $this->seam->request( - "POST", - "/access_methods/get_related", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_methods/get_related", [ + "query" => $request_payload, + ]), ); - return Batch::from_json($res->batch); + return Batch::from_json( + Body::read($res, "batch", "/access_methods/get_related"), + ); } /** @@ -191,8 +206,9 @@ public function get_related( * @param string $acs_entrance_id ID of the entrance for which you want to retrieve all access methods that grant access to it. * @param string $device_id ID of the device by which to filter the returned access methods. Must be combined with `access_grant_id`, `access_grant_key`, or `acs_entrance_id`. * @param int $limit Maximum number of records to return per page. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $space_id ID of the space by which to filter the returned access methods. Must be combined with `access_grant_id`, `access_grant_key`, or `acs_entrance_id`. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( @@ -202,10 +218,22 @@ public function list( ?string $acs_entrance_id = null, ?string $device_id = null, ?int $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $space_id = null, ?callable $on_response = null, ): array { + if ( + $access_code_id === null && + $access_grant_id === null && + $access_grant_key === null && + $acs_entrance_id === null && + $device_id === null && + $space_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /access_methods/list", + ); + } $request_payload = []; if ($access_code_id !== null) { @@ -233,10 +261,10 @@ public function list( $request_payload["space_id"] = $space_id; } - $res = $this->seam->request( - "POST", - "/access_methods/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_methods/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -245,7 +273,7 @@ public function list( return array_map( fn($r) => AccessMethod::from_json($r), - $res->access_methods, + Body::read_list($res, "access_methods", "/access_methods/list"), ); } @@ -254,36 +282,36 @@ public function list( * * @param string $access_method_id ID of the cloud_key `access_method` to use for the unlock operation. * @param string $acs_entrance_id ID of the entrance to unlock. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function unlock_door( string $access_method_id, string $acs_entrance_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($access_method_id !== null) { - $request_payload["access_method_id"] = $access_method_id; - } - if ($acs_entrance_id !== null) { - $request_payload["acs_entrance_id"] = $acs_entrance_id; - } + $request_payload["access_method_id"] = $access_method_id; + $request_payload["acs_entrance_id"] = $acs_entrance_id; - $res = $this->seam->request( - "POST", - "/access_methods/unlock_door", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/access_methods/unlock_door", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/access_methods/unlock_door", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } } diff --git a/src/Routes/AccessMethodsUnmanagedClient.php b/src/Routes/AccessMethodsUnmanagedClient.php index 94f9eb15..34c85538 100644 --- a/src/Routes/AccessMethodsUnmanagedClient.php +++ b/src/Routes/AccessMethodsUnmanagedClient.php @@ -2,16 +2,26 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\UnmanagedAccessMethod; -use Seam\SeamClient; class AccessMethodsUnmanagedClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -24,17 +34,17 @@ public function get(string $access_method_id): UnmanagedAccessMethod { $request_payload = []; - if ($access_method_id !== null) { - $request_payload["access_method_id"] = $access_method_id; - } + $request_payload["access_method_id"] = $access_method_id; - $res = $this->seam->request( - "POST", - "/access_methods/unmanaged/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_methods/unmanaged/get", [ + "query" => $request_payload, + ]), ); - return UnmanagedAccessMethod::from_json($res->access_method); + return UnmanagedAccessMethod::from_json( + Body::read($res, "access_method", "/access_methods/unmanaged/get"), + ); } /** @@ -54,9 +64,7 @@ public function list( ): array { $request_payload = []; - if ($access_grant_id !== null) { - $request_payload["access_grant_id"] = $access_grant_id; - } + $request_payload["access_grant_id"] = $access_grant_id; if ($acs_entrance_id !== null) { $request_payload["acs_entrance_id"] = $acs_entrance_id; } @@ -67,15 +75,19 @@ public function list( $request_payload["space_id"] = $space_id; } - $res = $this->seam->request( - "POST", - "/access_methods/unmanaged/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/access_methods/unmanaged/list", [ + "query" => $request_payload, + ]), ); return array_map( fn($r) => UnmanagedAccessMethod::from_json($r), - $res->access_methods, + Body::read_list( + $res, + "access_methods", + "/access_methods/unmanaged/list", + ), ); } } diff --git a/src/Routes/AcsAccessGroupsClient.php b/src/Routes/AcsAccessGroupsClient.php index f4878d23..d52d009a 100644 --- a/src/Routes/AcsAccessGroupsClient.php +++ b/src/Routes/AcsAccessGroupsClient.php @@ -2,18 +2,28 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\AcsAccessGroup; use Seam\Resources\AcsEntrance; use Seam\Resources\AcsUser; -use Seam\SeamClient; class AcsAccessGroupsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -31,9 +41,7 @@ public function add_user( ): void { $request_payload = []; - if ($acs_access_group_id !== null) { - $request_payload["acs_access_group_id"] = $acs_access_group_id; - } + $request_payload["acs_access_group_id"] = $acs_access_group_id; if ($acs_user_id !== null) { $request_payload["acs_user_id"] = $acs_user_id; } @@ -41,11 +49,9 @@ public function add_user( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/access_groups/add_user", - json: (object) $request_payload, - ); + $this->client->request("PUT", "/acs/access_groups/add_user", [ + "json" => (object) $request_payload, + ]); } /** @@ -58,15 +64,11 @@ public function delete(string $acs_access_group_id): void { $request_payload = []; - if ($acs_access_group_id !== null) { - $request_payload["acs_access_group_id"] = $acs_access_group_id; - } + $request_payload["acs_access_group_id"] = $acs_access_group_id; - $this->seam->request( - "POST", - "/acs/access_groups/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/acs/access_groups/delete", [ + "query" => $request_payload, + ]); } /** @@ -79,17 +81,17 @@ public function get(string $acs_access_group_id): AcsAccessGroup { $request_payload = []; - if ($acs_access_group_id !== null) { - $request_payload["acs_access_group_id"] = $acs_access_group_id; - } + $request_payload["acs_access_group_id"] = $acs_access_group_id; - $res = $this->seam->request( - "POST", - "/acs/access_groups/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/access_groups/get", [ + "query" => $request_payload, + ]), ); - return AcsAccessGroup::from_json($res->acs_access_group); + return AcsAccessGroup::from_json( + Body::read($res, "acs_access_group", "/acs/access_groups/get"), + ); } /** @@ -122,15 +124,19 @@ public function list( $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/acs/access_groups/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/access_groups/list", [ + "query" => $request_payload, + ]), ); return array_map( fn($r) => AcsAccessGroup::from_json($r), - $res->acs_access_groups, + Body::read_list( + $res, + "acs_access_groups", + "/acs/access_groups/list", + ), ); } @@ -145,19 +151,23 @@ public function list_accessible_entrances( ): array { $request_payload = []; - if ($acs_access_group_id !== null) { - $request_payload["acs_access_group_id"] = $acs_access_group_id; - } + $request_payload["acs_access_group_id"] = $acs_access_group_id; - $res = $this->seam->request( - "POST", - "/acs/access_groups/list_accessible_entrances", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/acs/access_groups/list_accessible_entrances", + ["query" => $request_payload], + ), ); return array_map( fn($r) => AcsEntrance::from_json($r), - $res->acs_entrances, + Body::read_list( + $res, + "acs_entrances", + "/acs/access_groups/list_accessible_entrances", + ), ); } @@ -171,17 +181,18 @@ public function list_users(string $acs_access_group_id): array { $request_payload = []; - if ($acs_access_group_id !== null) { - $request_payload["acs_access_group_id"] = $acs_access_group_id; - } + $request_payload["acs_access_group_id"] = $acs_access_group_id; - $res = $this->seam->request( - "POST", - "/acs/access_groups/list_users", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/access_groups/list_users", [ + "query" => $request_payload, + ]), ); - return array_map(fn($r) => AcsUser::from_json($r), $res->acs_users); + return array_map( + fn($r) => AcsUser::from_json($r), + Body::read_list($res, "acs_users", "/acs/access_groups/list_users"), + ); } /** @@ -199,9 +210,7 @@ public function remove_user( ): void { $request_payload = []; - if ($acs_access_group_id !== null) { - $request_payload["acs_access_group_id"] = $acs_access_group_id; - } + $request_payload["acs_access_group_id"] = $acs_access_group_id; if ($acs_user_id !== null) { $request_payload["acs_user_id"] = $acs_user_id; } @@ -209,10 +218,8 @@ public function remove_user( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/access_groups/remove_user", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/acs/access_groups/remove_user", [ + "query" => $request_payload, + ]); } } diff --git a/src/Routes/AcsClient.php b/src/Routes/AcsClient.php index e6454811..bee3e615 100644 --- a/src/Routes/AcsClient.php +++ b/src/Routes/AcsClient.php @@ -2,25 +2,34 @@ namespace Seam\Routes; -use Seam\SeamClient; +use GuzzleHttp\ClientInterface; class AcsClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public AcsAccessGroupsClient $access_groups; public AcsCredentialsClient $credentials; public AcsEncodersClient $encoders; public AcsEntrancesClient $entrances; public AcsSystemsClient $systems; public AcsUsersClient $users; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->access_groups = new AcsAccessGroupsClient($seam); - $this->credentials = new AcsCredentialsClient($seam); - $this->encoders = new AcsEncodersClient($seam); - $this->entrances = new AcsEntrancesClient($seam); - $this->systems = new AcsSystemsClient($seam); - $this->users = new AcsUsersClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->access_groups = new AcsAccessGroupsClient($client, $defaults); + $this->credentials = new AcsCredentialsClient($client, $defaults); + $this->encoders = new AcsEncodersClient($client, $defaults); + $this->entrances = new AcsEntrancesClient($client, $defaults); + $this->systems = new AcsSystemsClient($client, $defaults); + $this->users = new AcsUsersClient($client, $defaults); } } diff --git a/src/Routes/AcsCredentialsClient.php b/src/Routes/AcsCredentialsClient.php index b4151455..ab4826e3 100644 --- a/src/Routes/AcsCredentialsClient.php +++ b/src/Routes/AcsCredentialsClient.php @@ -2,17 +2,28 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\AcsCredential; use Seam\Resources\AcsEntrance; -use Seam\SeamClient; class AcsCredentialsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -30,9 +41,7 @@ public function assign( ): void { $request_payload = []; - if ($acs_credential_id !== null) { - $request_payload["acs_credential_id"] = $acs_credential_id; - } + $request_payload["acs_credential_id"] = $acs_credential_id; if ($acs_user_id !== null) { $request_payload["acs_user_id"] = $acs_user_id; } @@ -40,11 +49,9 @@ public function assign( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/credentials/assign", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/acs/credentials/assign", [ + "json" => (object) $request_payload, + ]); } /** @@ -53,7 +60,7 @@ public function assign( * @param string $access_method Access method for the new credential. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. * @param string $acs_system_id ID of the access system to which the new credential belongs. You must provide either `acs_user_id` or the combination of `user_identity_id` and `acs_system_id`. * @param string $acs_user_id ID of the access system user to whom the new credential belongs. You must provide either `acs_user_id` or the combination of `user_identity_id` and `acs_system_id`. - * @param array $allowed_acs_entrance_ids Set of IDs of the [entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) for which the new credential grants access. + * @param list $allowed_acs_entrance_ids Set of IDs of the [entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) for which the new credential grants access. * @param mixed $assa_abloy_vostio_metadata Vostio-specific metadata for the new credential. * @param string $code Access (PIN) code for the new credential. There may be manufacturer-specific code restrictions. For details, see the applicable [device or system integration guide](https://docs.seam.co/device-and-system-integration-guides). * @param string $credential_manager_acs_system_id ACS system ID of the credential manager for the new credential. @@ -82,9 +89,7 @@ public function create( ): AcsCredential { $request_payload = []; - if ($access_method !== null) { - $request_payload["access_method"] = $access_method; - } + $request_payload["access_method"] = $access_method; if ($acs_system_id !== null) { $request_payload["acs_system_id"] = $acs_system_id; } @@ -130,13 +135,15 @@ public function create( $request_payload["visionline_metadata"] = $visionline_metadata; } - $res = $this->seam->request( - "POST", - "/acs/credentials/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/acs/credentials/create", [ + "json" => (object) $request_payload, + ]), ); - return AcsCredential::from_json($res->acs_credential); + return AcsCredential::from_json( + Body::read($res, "acs_credential", "/acs/credentials/create"), + ); } /** @@ -149,15 +156,11 @@ public function delete(string $acs_credential_id): void { $request_payload = []; - if ($acs_credential_id !== null) { - $request_payload["acs_credential_id"] = $acs_credential_id; - } + $request_payload["acs_credential_id"] = $acs_credential_id; - $this->seam->request( - "POST", - "/acs/credentials/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/acs/credentials/delete", [ + "query" => $request_payload, + ]); } /** @@ -170,17 +173,17 @@ public function get(string $acs_credential_id): AcsCredential { $request_payload = []; - if ($acs_credential_id !== null) { - $request_payload["acs_credential_id"] = $acs_credential_id; - } + $request_payload["acs_credential_id"] = $acs_credential_id; - $res = $this->seam->request( - "POST", - "/acs/credentials/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/credentials/get", [ + "query" => $request_payload, + ]), ); - return AcsCredential::from_json($res->acs_credential); + return AcsCredential::from_json( + Body::read($res, "acs_credential", "/acs/credentials/get"), + ); } /** @@ -192,8 +195,9 @@ public function get(string $acs_credential_id): AcsCredential * @param string $created_before Date and time, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format, before which events to return were created. * @param bool $is_multi_phone_sync_credential Indicates whether you want to retrieve only multi-phone sync credentials or non-multi-phone sync credentials. * @param float $limit Number of credentials to return. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned credentials to include all records that satisfy a partial match using `display_name`, `code`, `card_number`, `acs_user_id` or `acs_credential_id`. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( @@ -203,7 +207,7 @@ public function list( ?string $created_before = null, ?bool $is_multi_phone_sync_credential = null, ?float $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?callable $on_response = null, ): array { @@ -236,10 +240,10 @@ public function list( $request_payload["search"] = $search; } - $res = $this->seam->request( - "POST", - "/acs/credentials/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/credentials/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -248,7 +252,7 @@ public function list( return array_map( fn($r) => AcsCredential::from_json($r), - $res->acs_credentials, + Body::read_list($res, "acs_credentials", "/acs/credentials/list"), ); } @@ -262,19 +266,23 @@ public function list_accessible_entrances(string $acs_credential_id): array { $request_payload = []; - if ($acs_credential_id !== null) { - $request_payload["acs_credential_id"] = $acs_credential_id; - } + $request_payload["acs_credential_id"] = $acs_credential_id; - $res = $this->seam->request( - "POST", - "/acs/credentials/list_accessible_entrances", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/acs/credentials/list_accessible_entrances", + ["query" => $request_payload], + ), ); return array_map( fn($r) => AcsEntrance::from_json($r), - $res->acs_entrances, + Body::read_list( + $res, + "acs_entrances", + "/acs/credentials/list_accessible_entrances", + ), ); } @@ -293,9 +301,7 @@ public function unassign( ): void { $request_payload = []; - if ($acs_credential_id !== null) { - $request_payload["acs_credential_id"] = $acs_credential_id; - } + $request_payload["acs_credential_id"] = $acs_credential_id; if ($acs_user_id !== null) { $request_payload["acs_user_id"] = $acs_user_id; } @@ -303,11 +309,9 @@ public function unassign( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/credentials/unassign", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/acs/credentials/unassign", [ + "json" => (object) $request_payload, + ]); } /** @@ -325,9 +329,7 @@ public function update( ): void { $request_payload = []; - if ($acs_credential_id !== null) { - $request_payload["acs_credential_id"] = $acs_credential_id; - } + $request_payload["acs_credential_id"] = $acs_credential_id; if ($code !== null) { $request_payload["code"] = $code; } @@ -335,10 +337,8 @@ public function update( $request_payload["ends_at"] = $ends_at; } - $this->seam->request( - "POST", - "/acs/credentials/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/acs/credentials/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/AcsEncodersClient.php b/src/Routes/AcsEncodersClient.php index d277fdb9..e5fec99b 100644 --- a/src/Routes/AcsEncodersClient.php +++ b/src/Routes/AcsEncodersClient.php @@ -2,18 +2,30 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; +use Seam\NullValue; use Seam\Resources\AcsEncoder; use Seam\Resources\ActionAttempt; -use Seam\SeamClient; class AcsEncodersClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public AcsEncodersSimulateClient $simulate; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->simulate = new AcsEncodersSimulateClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->simulate = new AcsEncodersSimulateClient($client, $defaults); } /** @@ -22,19 +34,18 @@ public function __construct(SeamClient $seam) * @param string $acs_encoder_id ID of the `acs_encoder` to use to encode the `acs_credential`. * @param string $access_method_id ID of the `access_method` to encode onto a card. * @param string $acs_credential_id ID of the `acs_credential` to encode onto a card. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function encode_credential( string $acs_encoder_id, ?string $access_method_id = null, ?string $acs_credential_id = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["acs_encoder_id"] = $acs_encoder_id; if ($access_method_id !== null) { $request_payload["access_method_id"] = $access_method_id; } @@ -42,21 +53,24 @@ public function encode_credential( $request_payload["acs_credential_id"] = $acs_credential_id; } - $res = $this->seam->request( - "POST", - "/acs/encoders/encode_credential", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/acs/encoders/encode_credential", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/acs/encoders/encode_credential", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -69,27 +83,28 @@ public function get(string $acs_encoder_id): AcsEncoder { $request_payload = []; - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["acs_encoder_id"] = $acs_encoder_id; - $res = $this->seam->request( - "POST", - "/acs/encoders/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/encoders/get", [ + "query" => $request_payload, + ]), ); - return AcsEncoder::from_json($res->acs_encoder); + return AcsEncoder::from_json( + Body::read($res, "acs_encoder", "/acs/encoders/get"), + ); } /** * Returns a list of all [encoders](https://docs.seam.co/low-level-apis/access-systems/working-with-card-encoders-and-scanners). * * @param string $acs_system_id ID of the access system for which you want to retrieve all encoders. - * @param array $acs_system_ids IDs of the access systems for which you want to retrieve all encoders. - * @param array $acs_encoder_ids IDs of the encoders that you want to retrieve. + * @param list $acs_system_ids IDs of the access systems for which you want to retrieve all encoders. + * @param list $acs_encoder_ids IDs of the encoders that you want to retrieve. * @param float $limit Number of encoders to return. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( @@ -97,7 +112,7 @@ public function list( ?array $acs_system_ids = null, ?array $acs_encoder_ids = null, ?float $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?callable $on_response = null, ): array { $request_payload = []; @@ -118,10 +133,10 @@ public function list( $request_payload["page_cursor"] = $page_cursor; } - $res = $this->seam->request( - "POST", - "/acs/encoders/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/encoders/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -130,7 +145,7 @@ public function list( return array_map( fn($r) => AcsEncoder::from_json($r), - $res->acs_encoders, + Body::read_list($res, "acs_encoders", "/acs/encoders/list"), ); } @@ -139,37 +154,39 @@ public function list( * * @param string $acs_encoder_id ID of the encoder to use for the scan. * @param mixed $salto_ks_metadata Salto KS-specific metadata for the scan action. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function scan_credential( string $acs_encoder_id, mixed $salto_ks_metadata = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["acs_encoder_id"] = $acs_encoder_id; if ($salto_ks_metadata !== null) { $request_payload["salto_ks_metadata"] = $salto_ks_metadata; } - $res = $this->seam->request( - "POST", - "/acs/encoders/scan_credential", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/acs/encoders/scan_credential", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/acs/encoders/scan_credential", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -179,6 +196,7 @@ public function scan_credential( * @param string $acs_user_id ID of the `acs_user` to assign the scanned credential to. * @param mixed $salto_ks_metadata Salto KS-specific metadata for the scan action. * @param string $user_identity_id ID of the `user_identity` to assign the scanned credential to. If the ACS system contains an ACS user linked to this user identity, it is used. Otherwise, one is created. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function scan_to_assign_credential( @@ -186,13 +204,11 @@ public function scan_to_assign_credential( ?string $acs_user_id = null, mixed $salto_ks_metadata = null, ?string $user_identity_id = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["acs_encoder_id"] = $acs_encoder_id; if ($acs_user_id !== null) { $request_payload["acs_user_id"] = $acs_user_id; } @@ -203,20 +219,25 @@ public function scan_to_assign_credential( $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/acs/encoders/scan_to_assign_credential", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/acs/encoders/scan_to_assign_credential", + ["json" => (object) $request_payload], + ), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/acs/encoders/scan_to_assign_credential", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } } diff --git a/src/Routes/AcsEncodersSimulateClient.php b/src/Routes/AcsEncodersSimulateClient.php index 7fb9c2b4..f1616fea 100644 --- a/src/Routes/AcsEncodersSimulateClient.php +++ b/src/Routes/AcsEncodersSimulateClient.php @@ -2,15 +2,24 @@ namespace Seam\Routes; -use Seam\SeamClient; +use GuzzleHttp\ClientInterface; class AcsEncodersSimulateClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -28,9 +37,7 @@ public function next_credential_encode_will_fail( ): void { $request_payload = []; - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["acs_encoder_id"] = $acs_encoder_id; if ($error_code !== null) { $request_payload["error_code"] = $error_code; } @@ -38,10 +45,10 @@ public function next_credential_encode_will_fail( $request_payload["acs_credential_id"] = $acs_credential_id; } - $this->seam->request( + $this->client->request( "POST", "/acs/encoders/simulate/next_credential_encode_will_fail", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -58,17 +65,15 @@ public function next_credential_encode_will_succeed( ): void { $request_payload = []; - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["acs_encoder_id"] = $acs_encoder_id; if ($scenario !== null) { $request_payload["scenario"] = $scenario; } - $this->seam->request( + $this->client->request( "POST", "/acs/encoders/simulate/next_credential_encode_will_succeed", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -87,9 +92,7 @@ public function next_credential_scan_will_fail( ): void { $request_payload = []; - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["acs_encoder_id"] = $acs_encoder_id; if ($error_code !== null) { $request_payload["error_code"] = $error_code; } @@ -99,10 +102,10 @@ public function next_credential_scan_will_fail( ] = $acs_credential_id_on_seam; } - $this->seam->request( + $this->client->request( "POST", "/acs/encoders/simulate/next_credential_scan_will_fail", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -121,9 +124,7 @@ public function next_credential_scan_will_succeed( ): void { $request_payload = []; - if ($acs_encoder_id !== null) { - $request_payload["acs_encoder_id"] = $acs_encoder_id; - } + $request_payload["acs_encoder_id"] = $acs_encoder_id; if ($acs_credential_id_on_seam !== null) { $request_payload[ "acs_credential_id_on_seam" @@ -133,10 +134,10 @@ public function next_credential_scan_will_succeed( $request_payload["scenario"] = $scenario; } - $this->seam->request( + $this->client->request( "POST", "/acs/encoders/simulate/next_credential_scan_will_succeed", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } } diff --git a/src/Routes/AcsEntrancesClient.php b/src/Routes/AcsEntrancesClient.php index 583d6e5e..85cc23fe 100644 --- a/src/Routes/AcsEntrancesClient.php +++ b/src/Routes/AcsEntrancesClient.php @@ -2,18 +2,30 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; +use Seam\NullValue; use Seam\Resources\AcsCredential; use Seam\Resources\AcsEntrance; use Seam\Resources\ActionAttempt; -use Seam\SeamClient; class AcsEntrancesClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -26,17 +38,17 @@ public function get(string $acs_entrance_id): AcsEntrance { $request_payload = []; - if ($acs_entrance_id !== null) { - $request_payload["acs_entrance_id"] = $acs_entrance_id; - } + $request_payload["acs_entrance_id"] = $acs_entrance_id; - $res = $this->seam->request( - "POST", - "/acs/entrances/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/entrances/get", [ + "query" => $request_payload, + ]), ); - return AcsEntrance::from_json($res->acs_entrance); + return AcsEntrance::from_json( + Body::read($res, "acs_entrance", "/acs/entrances/get"), + ); } /** @@ -54,9 +66,7 @@ public function grant_access( ): void { $request_payload = []; - if ($acs_entrance_id !== null) { - $request_payload["acs_entrance_id"] = $acs_entrance_id; - } + $request_payload["acs_entrance_id"] = $acs_entrance_id; if ($acs_user_id !== null) { $request_payload["acs_user_id"] = $acs_user_id; } @@ -64,11 +74,9 @@ public function grant_access( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/entrances/grant_access", - json: (object) $request_payload, - ); + $this->client->request("POST", "/acs/entrances/grant_access", [ + "json" => (object) $request_payload, + ]); } /** @@ -76,15 +84,16 @@ public function grant_access( * * @param string $access_method_id ID of the access method for which you want to retrieve all entrances to which it grants access. * @param string $acs_credential_id ID of the credential for which you want to retrieve all entrances. - * @param array $acs_entrance_ids IDs of the entrances for which you want to retrieve all entrances. + * @param list $acs_entrance_ids IDs of the entrances for which you want to retrieve all entrances. * @param string $acs_system_id ID of the access system for which you want to retrieve all entrances. * @param string $connected_account_id ID of the connected account for which you want to retrieve all entrances. * @param string $customer_key Customer key for which you want to list entrances. * @param int $limit Maximum number of records to return per page. - * @param string $location_id - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $location_id + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned entrances to include all records that satisfy a partial match using `display_name`. * @param string $space_id ID of the space for which you want to list entrances. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( @@ -95,8 +104,8 @@ public function list( ?string $connected_account_id = null, ?string $customer_key = null, ?int $limit = null, - ?string $location_id = null, - ?string $page_cursor = null, + string|NullValue|null $location_id = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?string $space_id = null, ?callable $on_response = null, @@ -137,10 +146,10 @@ public function list( $request_payload["space_id"] = $space_id; } - $res = $this->seam->request( - "POST", - "/acs/entrances/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/entrances/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -149,7 +158,7 @@ public function list( return array_map( fn($r) => AcsEntrance::from_json($r), - $res->acs_entrances, + Body::read_list($res, "acs_entrances", "/acs/entrances/list"), ); } @@ -157,7 +166,7 @@ public function list( * Returns a list of all [credentials](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) with access to a specified [entrance](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details). * * @param string $acs_entrance_id ID of the entrance for which you want to list all credentials that grant access. - * @param array $include_if Conditions that credentials must meet to be included in the returned list. + * @param list $include_if Conditions that credentials must meet to be included in the returned list. * @return array OK */ public function list_credentials_with_access( @@ -166,22 +175,26 @@ public function list_credentials_with_access( ): array { $request_payload = []; - if ($acs_entrance_id !== null) { - $request_payload["acs_entrance_id"] = $acs_entrance_id; - } + $request_payload["acs_entrance_id"] = $acs_entrance_id; if ($include_if !== null) { $request_payload["include_if"] = $include_if; } - $res = $this->seam->request( - "POST", - "/acs/entrances/list_credentials_with_access", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/acs/entrances/list_credentials_with_access", + ["query" => $request_payload], + ), ); return array_map( fn($r) => AcsCredential::from_json($r), - $res->acs_credentials, + Body::read_list( + $res, + "acs_credentials", + "/acs/entrances/list_credentials_with_access", + ), ); } @@ -190,36 +203,32 @@ public function list_credentials_with_access( * * @param string $acs_credential_id ID of the cloud_key credential to use for the unlock operation. * @param string $acs_entrance_id ID of the entrance to unlock. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function unlock( string $acs_credential_id, string $acs_entrance_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($acs_credential_id !== null) { - $request_payload["acs_credential_id"] = $acs_credential_id; - } - if ($acs_entrance_id !== null) { - $request_payload["acs_entrance_id"] = $acs_entrance_id; - } + $request_payload["acs_credential_id"] = $acs_credential_id; + $request_payload["acs_entrance_id"] = $acs_entrance_id; - $res = $this->seam->request( - "POST", - "/acs/entrances/unlock", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/acs/entrances/unlock", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/acs/entrances/unlock"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } } diff --git a/src/Routes/AcsSystemsClient.php b/src/Routes/AcsSystemsClient.php index 9278fd9f..c6093452 100644 --- a/src/Routes/AcsSystemsClient.php +++ b/src/Routes/AcsSystemsClient.php @@ -2,16 +2,26 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\AcsSystem; -use Seam\SeamClient; class AcsSystemsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -24,17 +34,17 @@ public function get(string $acs_system_id): AcsSystem { $request_payload = []; - if ($acs_system_id !== null) { - $request_payload["acs_system_id"] = $acs_system_id; - } + $request_payload["acs_system_id"] = $acs_system_id; - $res = $this->seam->request( - "POST", - "/acs/systems/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/systems/get", [ + "query" => $request_payload, + ]), ); - return AcsSystem::from_json($res->acs_system); + return AcsSystem::from_json( + Body::read($res, "acs_system", "/acs/systems/get"), + ); } /** @@ -64,13 +74,16 @@ public function list( $request_payload["search"] = $search; } - $res = $this->seam->request( - "POST", - "/acs/systems/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/systems/list", [ + "query" => $request_payload, + ]), ); - return array_map(fn($r) => AcsSystem::from_json($r), $res->acs_systems); + return array_map( + fn($r) => AcsSystem::from_json($r), + Body::read_list($res, "acs_systems", "/acs/systems/list"), + ); } /** @@ -86,25 +99,32 @@ public function list_compatible_credential_manager_acs_systems( ): array { $request_payload = []; - if ($acs_system_id !== null) { - $request_payload["acs_system_id"] = $acs_system_id; - } + $request_payload["acs_system_id"] = $acs_system_id; - $res = $this->seam->request( - "POST", - "/acs/systems/list_compatible_credential_manager_acs_systems", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/acs/systems/list_compatible_credential_manager_acs_systems", + ["query" => $request_payload], + ), ); - return array_map(fn($r) => AcsSystem::from_json($r), $res->acs_systems); + return array_map( + fn($r) => AcsSystem::from_json($r), + Body::read_list( + $res, + "acs_systems", + "/acs/systems/list_compatible_credential_manager_acs_systems", + ), + ); } /** * Reports ACS system device status including encoders and entrances. * * @param string $acs_system_id ID of the ACS system to report resources for - * @param array $acs_encoders Array of ACS encoders to report - * @param array $acs_entrances Array of ACS entrances to report + * @param list|\stdClass> $acs_encoders Array of ACS encoders to report + * @param list|\stdClass> $acs_entrances Array of ACS entrances to report * @return void OK */ public function report_devices( @@ -114,9 +134,7 @@ public function report_devices( ): void { $request_payload = []; - if ($acs_system_id !== null) { - $request_payload["acs_system_id"] = $acs_system_id; - } + $request_payload["acs_system_id"] = $acs_system_id; if ($acs_encoders !== null) { $request_payload["acs_encoders"] = $acs_encoders; } @@ -124,10 +142,8 @@ public function report_devices( $request_payload["acs_entrances"] = $acs_entrances; } - $this->seam->request( - "POST", - "/acs/systems/report_devices", - json: (object) $request_payload, - ); + $this->client->request("POST", "/acs/systems/report_devices", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/AcsUsersClient.php b/src/Routes/AcsUsersClient.php index 729709e1..33551428 100644 --- a/src/Routes/AcsUsersClient.php +++ b/src/Routes/AcsUsersClient.php @@ -2,17 +2,28 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\AcsEntrance; use Seam\Resources\AcsUser; -use Seam\SeamClient; class AcsUsersClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -28,18 +39,12 @@ public function add_to_access_group( ): void { $request_payload = []; - if ($acs_access_group_id !== null) { - $request_payload["acs_access_group_id"] = $acs_access_group_id; - } - if ($acs_user_id !== null) { - $request_payload["acs_user_id"] = $acs_user_id; - } + $request_payload["acs_access_group_id"] = $acs_access_group_id; + $request_payload["acs_user_id"] = $acs_user_id; - $this->seam->request( - "POST", - "/acs/users/add_to_access_group", - json: (object) $request_payload, - ); + $this->client->request("PUT", "/acs/users/add_to_access_group", [ + "json" => (object) $request_payload, + ]); } /** @@ -48,7 +53,7 @@ public function add_to_access_group( * @param string $acs_system_id ID of the access system to which you want to add the new access system user. * @param string $full_name Full name of the new access system user. * @param mixed $access_schedule `starts_at` and `ends_at` timestamps for the new access system user's access. If you specify an `access_schedule`, you may include both `starts_at` and `ends_at`. If you omit `starts_at`, it defaults to the current time. `ends_at` is optional and must be a time in the future and after `starts_at`. - * @param array $acs_access_group_ids Array of access group IDs to indicate the access groups to which you want to add the new access system user. + * @param list $acs_access_group_ids Array of access group IDs to indicate the access groups to which you want to add the new access system user. * @param string $email * @param string $email_address Email address of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management). * @param string $phone_number Phone number of the [access system user](https://docs.seam.co/low-level-apis/access-systems/user-management) in E.164 format (for example, `+15555550100`). @@ -67,12 +72,8 @@ public function create( ): AcsUser { $request_payload = []; - if ($acs_system_id !== null) { - $request_payload["acs_system_id"] = $acs_system_id; - } - if ($full_name !== null) { - $request_payload["full_name"] = $full_name; - } + $request_payload["acs_system_id"] = $acs_system_id; + $request_payload["full_name"] = $full_name; if ($access_schedule !== null) { $request_payload["access_schedule"] = $access_schedule; } @@ -92,13 +93,15 @@ public function create( $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/acs/users/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/acs/users/create", [ + "json" => (object) $request_payload, + ]), ); - return AcsUser::from_json($res->acs_user); + return AcsUser::from_json( + Body::read($res, "acs_user", "/acs/users/create"), + ); } /** @@ -114,6 +117,15 @@ public function delete( ?string $acs_user_id = null, ?string $user_identity_id = null, ): void { + if ( + $acs_system_id === null && + $acs_user_id === null && + $user_identity_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /acs/users/delete", + ); + } $request_payload = []; if ($acs_system_id !== null) { @@ -126,11 +138,9 @@ public function delete( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/users/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/acs/users/delete", [ + "query" => $request_payload, + ]); } /** @@ -146,6 +156,15 @@ public function get( ?string $acs_system_id = null, ?string $user_identity_id = null, ): AcsUser { + if ( + $acs_user_id === null && + $acs_system_id === null && + $user_identity_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /acs/users/get", + ); + } $request_payload = []; if ($acs_user_id !== null) { @@ -158,13 +177,15 @@ public function get( $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/acs/users/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/users/get", [ + "query" => $request_payload, + ]), ); - return AcsUser::from_json($res->acs_user); + return AcsUser::from_json( + Body::read($res, "acs_user", "/acs/users/get"), + ); } /** @@ -173,18 +194,19 @@ public function get( * @param string $acs_system_id ID of the `acs_system` for which you want to retrieve all access system users. * @param string $created_before Timestamp by which to limit returned access system users. Returns users created before this timestamp. * @param int $limit Maximum number of records to return per page. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned access system users to include all records that satisfy a partial match using `full_name`, `phone_number`, `email_address`, `acs_user_id`, `user_identity_id`, `user_identity_full_name` or `user_identity_phone_number`. * @param string $user_identity_email_address Email address of the user identity for which you want to retrieve all access system users. * @param string $user_identity_id ID of the user identity for which you want to retrieve all access system users. * @param string $user_identity_phone_number Phone number of the user identity for which you want to retrieve all access system users, in [E.164 format](https://www.itu.int/rec/T-REC-E.164/en) (for example, `+15555550100`). + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( ?string $acs_system_id = null, ?string $created_before = null, ?int $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?string $user_identity_email_address = null, ?string $user_identity_id = null, @@ -222,17 +244,20 @@ public function list( ] = $user_identity_phone_number; } - $res = $this->seam->request( - "POST", - "/acs/users/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/acs/users/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { $on_response($res); } - return array_map(fn($r) => AcsUser::from_json($r), $res->acs_users); + return array_map( + fn($r) => AcsUser::from_json($r), + Body::read_list($res, "acs_users", "/acs/users/list"), + ); } /** @@ -248,6 +273,15 @@ public function list_accessible_entrances( ?string $acs_user_id = null, ?string $user_identity_id = null, ): array { + if ( + $acs_system_id === null && + $acs_user_id === null && + $user_identity_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /acs/users/list_accessible_entrances", + ); + } $request_payload = []; if ($acs_system_id !== null) { @@ -260,15 +294,21 @@ public function list_accessible_entrances( $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/acs/users/list_accessible_entrances", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/acs/users/list_accessible_entrances", + ["query" => $request_payload], + ), ); return array_map( fn($r) => AcsEntrance::from_json($r), - $res->acs_entrances, + Body::read_list( + $res, + "acs_entrances", + "/acs/users/list_accessible_entrances", + ), ); } @@ -287,9 +327,7 @@ public function remove_from_access_group( ): void { $request_payload = []; - if ($acs_access_group_id !== null) { - $request_payload["acs_access_group_id"] = $acs_access_group_id; - } + $request_payload["acs_access_group_id"] = $acs_access_group_id; if ($acs_user_id !== null) { $request_payload["acs_user_id"] = $acs_user_id; } @@ -297,10 +335,10 @@ public function remove_from_access_group( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", + $this->client->request( + "DELETE", "/acs/users/remove_from_access_group", - json: (object) $request_payload, + ["query" => $request_payload], ); } @@ -317,6 +355,15 @@ public function revoke_access_to_all_entrances( ?string $acs_user_id = null, ?string $user_identity_id = null, ): void { + if ( + $acs_system_id === null && + $acs_user_id === null && + $user_identity_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /acs/users/revoke_access_to_all_entrances", + ); + } $request_payload = []; if ($acs_system_id !== null) { @@ -329,10 +376,10 @@ public function revoke_access_to_all_entrances( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( + $this->client->request( "POST", "/acs/users/revoke_access_to_all_entrances", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -349,6 +396,15 @@ public function suspend( ?string $acs_user_id = null, ?string $user_identity_id = null, ): void { + if ( + $acs_system_id === null && + $acs_user_id === null && + $user_identity_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /acs/users/suspend", + ); + } $request_payload = []; if ($acs_system_id !== null) { @@ -361,11 +417,9 @@ public function suspend( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/users/suspend", - json: (object) $request_payload, - ); + $this->client->request("POST", "/acs/users/suspend", [ + "json" => (object) $request_payload, + ]); } /** @@ -381,6 +435,15 @@ public function unsuspend( ?string $acs_user_id = null, ?string $user_identity_id = null, ): void { + if ( + $acs_system_id === null && + $acs_user_id === null && + $user_identity_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /acs/users/unsuspend", + ); + } $request_payload = []; if ($acs_system_id !== null) { @@ -393,11 +456,9 @@ public function unsuspend( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/users/unsuspend", - json: (object) $request_payload, - ); + $this->client->request("POST", "/acs/users/unsuspend", [ + "json" => (object) $request_payload, + ]); } /** @@ -425,6 +486,21 @@ public function update( ?string $phone_number = null, ?string $user_identity_id = null, ): void { + if ( + $access_schedule === null && + $acs_system_id === null && + $acs_user_id === null && + $email === null && + $email_address === null && + $full_name === null && + $hid_acs_system_id === null && + $phone_number === null && + $user_identity_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /acs/users/update", + ); + } $request_payload = []; if ($access_schedule !== null) { @@ -455,10 +531,8 @@ public function update( $request_payload["user_identity_id"] = $user_identity_id; } - $this->seam->request( - "POST", - "/acs/users/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/acs/users/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/ActionAttemptsClient.php b/src/Routes/ActionAttemptsClient.php index becd80db..fe4fa397 100644 --- a/src/Routes/ActionAttemptsClient.php +++ b/src/Routes/ActionAttemptsClient.php @@ -2,57 +2,76 @@ namespace Seam\Routes; -use Seam\ActionAttemptFailedError; -use Seam\ActionAttemptTimeoutError; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; +use Seam\NullValue; use Seam\Resources\ActionAttempt; -use Seam\SeamClient; class ActionAttemptsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** * Returns a specified [action attempt](https://docs.seam.co/core-concepts/action-attempts). * * @param string $action_attempt_id ID of the action attempt that you want to get. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ - public function get(string $action_attempt_id): ActionAttempt - { + public function get( + string $action_attempt_id, + bool|array|null $wait_for_action_attempt = null, + ): ActionAttempt { $request_payload = []; - if ($action_attempt_id !== null) { - $request_payload["action_attempt_id"] = $action_attempt_id; - } + $request_payload["action_attempt_id"] = $action_attempt_id; - $res = $this->seam->request( - "POST", - "/action_attempts/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/action_attempts/get", [ + "query" => $request_payload, + ]), ); - return ActionAttempt::from_json($res->action_attempt); + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/action_attempts/get"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], + ); } /** * Returns a list of the [action attempts](https://docs.seam.co/core-concepts/action-attempts) that you specify as an array of `action_attempt_id`s. * - * @param array $action_attempt_ids IDs of the action attempts that you want to retrieve. + * @param list $action_attempt_ids IDs of the action attempts that you want to retrieve. * @param string $device_id ID of the device to filter action attempts by. * @param int $limit Maximum number of records to return per page. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( ?array $action_attempt_ids = null, ?string $device_id = null, ?int $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?callable $on_response = null, ): array { $request_payload = []; @@ -70,10 +89,10 @@ public function list( $request_payload["page_cursor"] = $page_cursor; } - $res = $this->seam->request( - "POST", - "/action_attempts/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/action_attempts/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -82,33 +101,7 @@ public function list( return array_map( fn($r) => ActionAttempt::from_json($r), - $res->action_attempts, + Body::read_list($res, "action_attempts", "/action_attempts/list"), ); } - public function poll_until_ready( - string $action_attempt_id, - float $timeout = 20.0, - ): ActionAttempt { - $seam = $this->seam; - $time_waiting = 0.0; - $polling_interval = 0.4; - $action_attempt = $seam->action_attempts->get($action_attempt_id); - - while ($action_attempt->status == "pending") { - $action_attempt = $seam->action_attempts->get( - $action_attempt->action_attempt_id, - ); - if ($time_waiting > $timeout) { - throw new ActionAttemptTimeoutError($action_attempt, $timeout); - } - $time_waiting += $polling_interval; - usleep($polling_interval * 1000000); - } - - if ($action_attempt->status == "error") { - throw new ActionAttemptFailedError($action_attempt); - } - - return $action_attempt; - } } diff --git a/src/Routes/ClientSessionsClient.php b/src/Routes/ClientSessionsClient.php index ebb651de..cefeec04 100644 --- a/src/Routes/ClientSessionsClient.php +++ b/src/Routes/ClientSessionsClient.php @@ -2,29 +2,39 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\ClientSession; -use Seam\SeamClient; class ClientSessionsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** * Creates a new [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens). * - * @param array $connect_webview_ids IDs of the [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) for which you want to create a client session. - * @param array $connected_account_ids IDs of the [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) for which you want to create a client session. + * @param list $connect_webview_ids IDs of the [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) for which you want to create a client session. + * @param list $connected_account_ids IDs of the [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) for which you want to create a client session. * @param string $customer_id Customer ID that you want to associate with the new client session. * @param string $customer_key Customer key that you want to associate with the new client session. * @param string $expires_at Date and time at which the client session should expire, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. * @param string $user_identifier_key Your user ID for the user for whom you want to create a client session. * @param string $user_identity_id ID of the [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) for which you want to create a client session. - * @param array $user_identity_ids IDs of the [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) that you want to associate with the client session. + * @param list $user_identity_ids IDs of the [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) that you want to associate with the client session. * @return ClientSession OK */ public function create( @@ -64,13 +74,15 @@ public function create( $request_payload["user_identity_ids"] = $user_identity_ids; } - $res = $this->seam->request( - "POST", - "/client_sessions/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("PUT", "/client_sessions/create", [ + "json" => (object) $request_payload, + ]), ); - return ClientSession::from_json($res->client_session); + return ClientSession::from_json( + Body::read($res, "client_session", "/client_sessions/create"), + ); } /** @@ -83,15 +95,11 @@ public function delete(string $client_session_id): void { $request_payload = []; - if ($client_session_id !== null) { - $request_payload["client_session_id"] = $client_session_id; - } + $request_payload["client_session_id"] = $client_session_id; - $this->seam->request( - "POST", - "/client_sessions/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/client_sessions/delete", [ + "query" => $request_payload, + ]); } /** @@ -114,24 +122,26 @@ public function get( $request_payload["user_identifier_key"] = $user_identifier_key; } - $res = $this->seam->request( - "POST", - "/client_sessions/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/client_sessions/get", [ + "query" => $request_payload, + ]), ); - return ClientSession::from_json($res->client_session); + return ClientSession::from_json( + Body::read($res, "client_session", "/client_sessions/get"), + ); } /** * Returns a [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens) with specific characteristics or creates a new client session with these characteristics if it does not yet exist. * - * @param array $connect_webview_ids IDs of the [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) that you want to associate with the client session (or that are already associated with the existing client session). - * @param array $connected_account_ids IDs of the [connected accounts](https://docs.seam.co/api/connected_accounts) that you want to associate with the client session (or that are already associated with the existing client session). + * @param list $connect_webview_ids IDs of the [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) that you want to associate with the client session (or that are already associated with the existing client session). + * @param list $connected_account_ids IDs of the [connected accounts](https://docs.seam.co/api/connected_accounts) that you want to associate with the client session (or that are already associated with the existing client session). * @param string $expires_at Date and time at which the client session should expire in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. If the client session already exists, this will update the expiration before returning it. * @param string $user_identifier_key Your user ID for the user that you want to associate with the client session (or that is already associated with the existing client session). * @param string $user_identity_id ID of the [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) that you want to associate with the client session (or that are already associated with the existing client session). - * @param array $user_identity_ids IDs of the [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) that you want to associate with the client session. + * @param list $user_identity_ids IDs of the [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) that you want to associate with the client session. * @return ClientSession OK */ public function get_or_create( @@ -163,24 +173,30 @@ public function get_or_create( $request_payload["user_identity_ids"] = $user_identity_ids; } - $res = $this->seam->request( - "POST", - "/client_sessions/get_or_create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/client_sessions/get_or_create", [ + "json" => (object) $request_payload, + ]), ); - return ClientSession::from_json($res->client_session); + return ClientSession::from_json( + Body::read( + $res, + "client_session", + "/client_sessions/get_or_create", + ), + ); } /** * Grants a [client session](https://docs.seam.co/core-concepts/authentication/client-session-tokens) access to one or more resources, such as [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews), [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity), and so on. * * @param string $client_session_id ID of the client session to which you want to grant access to resources. - * @param array $connect_webview_ids IDs of the [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) that you want to associate with the client session. - * @param array $connected_account_ids IDs of the [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) that you want to associate with the client session. + * @param list $connect_webview_ids IDs of the [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews) that you want to associate with the client session. + * @param list $connected_account_ids IDs of the [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) that you want to associate with the client session. * @param string $user_identifier_key Your user ID for the user that you want to associate with the client session. * @param string $user_identity_id ID of the [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) that you want to associate with the client session. - * @param array $user_identity_ids IDs of the [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) that you want to associate with the client session. + * @param list $user_identity_ids IDs of the [user identities](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity) that you want to associate with the client session. * @return void OK */ public function grant_access( @@ -191,6 +207,18 @@ public function grant_access( ?string $user_identity_id = null, ?array $user_identity_ids = null, ): void { + if ( + $client_session_id === null && + $connect_webview_ids === null && + $connected_account_ids === null && + $user_identifier_key === null && + $user_identity_id === null && + $user_identity_ids === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /client_sessions/grant_access", + ); + } $request_payload = []; if ($client_session_id !== null) { @@ -212,11 +240,9 @@ public function grant_access( $request_payload["user_identity_ids"] = $user_identity_ids; } - $this->seam->request( - "POST", - "/client_sessions/grant_access", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/client_sessions/grant_access", [ + "json" => (object) $request_payload, + ]); } /** @@ -256,15 +282,15 @@ public function list( ] = $without_user_identifier_key; } - $res = $this->seam->request( - "POST", - "/client_sessions/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/client_sessions/list", [ + "query" => $request_payload, + ]), ); return array_map( fn($r) => ClientSession::from_json($r), - $res->client_sessions, + Body::read_list($res, "client_sessions", "/client_sessions/list"), ); } @@ -280,14 +306,10 @@ public function revoke(string $client_session_id): void { $request_payload = []; - if ($client_session_id !== null) { - $request_payload["client_session_id"] = $client_session_id; - } + $request_payload["client_session_id"] = $client_session_id; - $this->seam->request( - "POST", - "/client_sessions/revoke", - json: (object) $request_payload, - ); + $this->client->request("POST", "/client_sessions/revoke", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/ConnectWebviewsClient.php b/src/Routes/ConnectWebviewsClient.php index 4bd32a3e..adacfc8f 100644 --- a/src/Routes/ConnectWebviewsClient.php +++ b/src/Routes/ConnectWebviewsClient.php @@ -2,16 +2,27 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\ConnectWebview; -use Seam\SeamClient; class ConnectWebviewsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -23,14 +34,14 @@ public function __construct(SeamClient $seam) * * See also: [Connect Webview Process](https://docs.seam.co/core-concepts/connect-webviews/connect-webview-process). * - * @param array $accepted_capabilities List of accepted device capabilities that restrict the types of devices that can be connected through the Connect Webview. If not provided, defaults will be determined based on the accepted providers. - * @param array $accepted_providers Accepted device provider keys as an alternative to `provider_category`. Use this parameter to specify accepted providers explicitly. See [Customize the Brands to Display in Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-brands-to-display-in-your-connect-webviews). To list all provider keys, use [`/devices/list_device_providers`](https://docs.seam.co/api/devices/list_device_providers) with no filters. + * @param list $accepted_capabilities List of accepted device capabilities that restrict the types of devices that can be connected through the Connect Webview. If not provided, defaults will be determined based on the accepted providers. + * @param list $accepted_providers Accepted device provider keys as an alternative to `provider_category`. Use this parameter to specify accepted providers explicitly. See [Customize the Brands to Display in Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-brands-to-display-in-your-connect-webviews). To list all provider keys, use [`/devices/list_device_providers`](https://docs.seam.co/api/devices/list_device_providers) with no filters. * @param bool $automatically_manage_new_devices Indicates whether newly-added devices should appear as [managed devices](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices). See also: [Customize the Behavior Settings of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-behavior-settings-of-your-connect-webviews). - * @param mixed $custom_metadata Custom metadata that you want to associate with the Connect Webview. Supports up to 50 JSON key:value pairs. [Adding custom metadata to a Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview) enables you to store custom information, like customer details or internal IDs from your application. The custom metadata is then transferred to any [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) that were connected using the Connect Webview, making it easy to find and filter these resources in your [workspace](https://docs.seam.co/core-concepts/workspaces). You can also [filter Connect Webviews by custom metadata](https://docs.seam.co/core-concepts/connect-webviews/filtering-connect-webviews-by-custom-metadata). + * @param array|\stdClass $custom_metadata Custom metadata that you want to associate with the Connect Webview. Supports up to 50 JSON key:value pairs. [Adding custom metadata to a Connect Webview](https://docs.seam.co/core-concepts/connect-webviews/attaching-custom-data-to-the-connect-webview) enables you to store custom information, like customer details or internal IDs from your application. The custom metadata is then transferred to any [connected accounts](https://docs.seam.co/core-concepts/connected-accounts) that were connected using the Connect Webview, making it easy to find and filter these resources in your [workspace](https://docs.seam.co/core-concepts/workspaces). You can also [filter Connect Webviews by custom metadata](https://docs.seam.co/core-concepts/connect-webviews/filtering-connect-webviews-by-custom-metadata). * @param string $custom_redirect_failure_url Alternative URL that you want to redirect the user to on an error. If you do not set this parameter, the Connect Webview falls back to the `custom_redirect_url`. * @param string $custom_redirect_url URL that you want to redirect the user to after the provider login is complete. * @param string $customer_key Associate the Connect Webview, the connected account, and all resources under the connected account with a customer. If the connected account already exists, it will be associated with the customer. If the connected account already exists, but is already associated with a customer, the Connect Webview will show an error. - * @param array $excluded_providers List of provider keys to exclude from the Connect Webview. These providers will not be shown when the user tries to connect an account. + * @param list $excluded_providers List of provider keys to exclude from the Connect Webview. These providers will not be shown when the user tries to connect an account. * @param string $provider_category Specifies the category of providers that you want to include. To list all providers within a category, use [`/devices/list_device_providers`](https://docs.seam.co/api/devices/list_device_providers) with the desired `provider_category` filter. * @param bool $wait_for_device_creation Indicates whether Seam should finish syncing all devices in a newly-connected account before completing the associated Connect Webview. See also: [Customize the Behavior Settings of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-behavior-settings-of-your-connect-webviews). * @return ConnectWebview OK @@ -39,7 +50,7 @@ public function create( ?array $accepted_capabilities = null, ?array $accepted_providers = null, ?bool $automatically_manage_new_devices = null, - mixed $custom_metadata = null, + array|\stdClass|null $custom_metadata = null, ?string $custom_redirect_failure_url = null, ?string $custom_redirect_url = null, ?string $customer_key = null, @@ -86,13 +97,15 @@ public function create( ] = $wait_for_device_creation; } - $res = $this->seam->request( - "POST", - "/connect_webviews/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/connect_webviews/create", [ + "json" => (object) $request_payload, + ]), ); - return ConnectWebview::from_json($res->connect_webview); + return ConnectWebview::from_json( + Body::read($res, "connect_webview", "/connect_webviews/create"), + ); } /** @@ -107,15 +120,11 @@ public function delete(string $connect_webview_id): void { $request_payload = []; - if ($connect_webview_id !== null) { - $request_payload["connect_webview_id"] = $connect_webview_id; - } + $request_payload["connect_webview_id"] = $connect_webview_id; - $this->seam->request( - "POST", - "/connect_webviews/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/connect_webviews/delete", [ + "query" => $request_payload, + ]); } /** @@ -130,35 +139,36 @@ public function get(string $connect_webview_id): ConnectWebview { $request_payload = []; - if ($connect_webview_id !== null) { - $request_payload["connect_webview_id"] = $connect_webview_id; - } + $request_payload["connect_webview_id"] = $connect_webview_id; - $res = $this->seam->request( - "POST", - "/connect_webviews/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/connect_webviews/get", [ + "query" => $request_payload, + ]), ); - return ConnectWebview::from_json($res->connect_webview); + return ConnectWebview::from_json( + Body::read($res, "connect_webview", "/connect_webviews/get"), + ); } /** * Returns a list of all [Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews). * - * @param mixed $custom_metadata_has Custom metadata pairs by which you want to [filter Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/filtering-connect-webviews-by-custom-metadata). Returns Connect Webviews with `custom_metadata` that contains all of the provided key:value pairs. + * @param array|\stdClass $custom_metadata_has Custom metadata pairs by which you want to [filter Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/filtering-connect-webviews-by-custom-metadata). Returns Connect Webviews with `custom_metadata` that contains all of the provided key:value pairs. * @param string $customer_key Customer key for which you want to list connect webviews. * @param float $limit Maximum number of records to return per page. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned Connect Webviews to include all records that satisfy a partial match using `connect_webview_id`, `accepted_providers`, `custom_metadata`, or `customer_key`. * @param string $user_identifier_key Your user ID for the user by which you want to filter Connect Webviews. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( - mixed $custom_metadata_has = null, + array|\stdClass|null $custom_metadata_has = null, ?string $customer_key = null, ?float $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?string $user_identifier_key = null, ?callable $on_response = null, @@ -184,10 +194,10 @@ public function list( $request_payload["user_identifier_key"] = $user_identifier_key; } - $res = $this->seam->request( - "POST", - "/connect_webviews/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/connect_webviews/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -196,7 +206,7 @@ public function list( return array_map( fn($r) => ConnectWebview::from_json($r), - $res->connect_webviews, + Body::read_list($res, "connect_webviews", "/connect_webviews/list"), ); } } diff --git a/src/Routes/ConnectedAccountsClient.php b/src/Routes/ConnectedAccountsClient.php index d3873aa0..1b311387 100644 --- a/src/Routes/ConnectedAccountsClient.php +++ b/src/Routes/ConnectedAccountsClient.php @@ -2,17 +2,31 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\ConnectedAccount; -use Seam\SeamClient; class ConnectedAccountsClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public ConnectedAccountsSimulateClient $simulate; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->simulate = new ConnectedAccountsSimulateClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->simulate = new ConnectedAccountsSimulateClient( + $client, + $defaults, + ); } /** @@ -29,15 +43,11 @@ public function delete(string $connected_account_id): void { $request_payload = []; - if ($connected_account_id !== null) { - $request_payload["connected_account_id"] = $connected_account_id; - } + $request_payload["connected_account_id"] = $connected_account_id; - $this->seam->request( - "POST", - "/connected_accounts/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/connected_accounts/delete", [ + "query" => $request_payload, + ]); } /** @@ -51,6 +61,11 @@ public function get( ?string $connected_account_id = null, ?string $email = null, ): ConnectedAccount { + if ($connected_account_id === null && $email === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /connected_accounts/get", + ); + } $request_payload = []; if ($connected_account_id !== null) { @@ -60,32 +75,35 @@ public function get( $request_payload["email"] = $email; } - $res = $this->seam->request( - "POST", - "/connected_accounts/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/connected_accounts/get", [ + "query" => $request_payload, + ]), ); - return ConnectedAccount::from_json($res->connected_account); + return ConnectedAccount::from_json( + Body::read($res, "connected_account", "/connected_accounts/get"), + ); } /** * Returns a list of all [connected accounts](https://docs.seam.co/core-concepts/connected-accounts). * - * @param mixed $custom_metadata_has Custom metadata pairs by which you want to filter connected accounts. Returns connected accounts with `custom_metadata` that contains all of the provided key:value pairs. + * @param array|\stdClass $custom_metadata_has Custom metadata pairs by which you want to filter connected accounts. Returns connected accounts with `custom_metadata` that contains all of the provided key:value pairs. * @param string $customer_key Customer key by which you want to filter connected accounts. * @param int $limit Maximum number of records to return per page. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned connected accounts to include all records that satisfy a partial match using `connected_account_id`, `account_type`, `customer_key`, `custom_metadata`, `user_identifier.username`, `user_identifier.email` or `user_identifier.phone`. * @param string $space_id ID of the space by which you want to filter connected accounts. * @param string $user_identifier_key Your user ID for the user by which you want to filter connected accounts. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( - mixed $custom_metadata_has = null, + array|\stdClass|null $custom_metadata_has = null, ?string $customer_key = null, ?int $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?string $space_id = null, ?string $user_identifier_key = null, @@ -115,10 +133,10 @@ public function list( $request_payload["user_identifier_key"] = $user_identifier_key; } - $res = $this->seam->request( - "POST", - "/connected_accounts/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/connected_accounts/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -127,7 +145,11 @@ public function list( return array_map( fn($r) => ConnectedAccount::from_json($r), - $res->connected_accounts, + Body::read_list( + $res, + "connected_accounts", + "/connected_accounts/list", + ), ); } @@ -141,24 +163,20 @@ public function sync(string $connected_account_id): void { $request_payload = []; - if ($connected_account_id !== null) { - $request_payload["connected_account_id"] = $connected_account_id; - } + $request_payload["connected_account_id"] = $connected_account_id; - $this->seam->request( - "POST", - "/connected_accounts/sync", - json: (object) $request_payload, - ); + $this->client->request("POST", "/connected_accounts/sync", [ + "json" => (object) $request_payload, + ]); } /** * Updates a [connected account](https://docs.seam.co/core-concepts/connected-accounts). * * @param string $connected_account_id ID of the connected account that you want to update. - * @param array $accepted_capabilities List of accepted device capabilities that restrict the types of devices that can be connected through this connected account. Valid values are `lock`, `thermostat`, `noise_sensor`, and `access_control`. + * @param list $accepted_capabilities List of accepted device capabilities that restrict the types of devices that can be connected through this connected account. Valid values are `lock`, `thermostat`, `noise_sensor`, and `access_control`. * @param bool $automatically_manage_new_devices Indicates whether newly-added devices should appear as [managed devices](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices). - * @param mixed $custom_metadata Custom metadata that you want to associate with the connected account. Entirely replaces the existing custom metadata object. If a new Connect Webview contains custom metadata and is used to reconnect a connected account, the custom metadata from the Connect Webview will entirely replace the entire custom metadata object on the connected account. Supports up to 50 JSON key:value pairs. [Adding custom metadata to a connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account) enables you to store custom information, like customer details or internal IDs from your application. Then, you can [filter connected accounts by the desired metadata](https://docs.seam.co/core-concepts/connected-accounts/filtering-connected-accounts-by-custom-metadata). + * @param array|\stdClass $custom_metadata Custom metadata that you want to associate with the connected account. Entirely replaces the existing custom metadata object. If a new Connect Webview contains custom metadata and is used to reconnect a connected account, the custom metadata from the Connect Webview will entirely replace the entire custom metadata object on the connected account. Supports up to 50 JSON key:value pairs. [Adding custom metadata to a connected account](https://docs.seam.co/core-concepts/connected-accounts/adding-custom-metadata-to-a-connected-account) enables you to store custom information, like customer details or internal IDs from your application. Then, you can [filter connected accounts by the desired metadata](https://docs.seam.co/core-concepts/connected-accounts/filtering-connected-accounts-by-custom-metadata). * @param string $customer_key The customer key to associate with this connected account. If provided, the connected account and all resources under the connected account will be moved to this customer. May only be provided if the connected account is not already associated with a customer. * @param string $display_name Human-readable name for the connected account, shown in the dashboard. For example, `Booking from Airbnb House 1`. * @return void OK @@ -167,15 +185,13 @@ public function update( string $connected_account_id, ?array $accepted_capabilities = null, ?bool $automatically_manage_new_devices = null, - mixed $custom_metadata = null, + array|\stdClass|null $custom_metadata = null, ?string $customer_key = null, ?string $display_name = null, ): void { $request_payload = []; - if ($connected_account_id !== null) { - $request_payload["connected_account_id"] = $connected_account_id; - } + $request_payload["connected_account_id"] = $connected_account_id; if ($accepted_capabilities !== null) { $request_payload["accepted_capabilities"] = $accepted_capabilities; } @@ -194,10 +210,8 @@ public function update( $request_payload["display_name"] = $display_name; } - $this->seam->request( - "POST", - "/connected_accounts/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/connected_accounts/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/ConnectedAccountsSimulateClient.php b/src/Routes/ConnectedAccountsSimulateClient.php index d3a913df..85e0612e 100644 --- a/src/Routes/ConnectedAccountsSimulateClient.php +++ b/src/Routes/ConnectedAccountsSimulateClient.php @@ -2,15 +2,24 @@ namespace Seam\Routes; -use Seam\SeamClient; +use GuzzleHttp\ClientInterface; class ConnectedAccountsSimulateClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -23,14 +32,12 @@ public function disconnect(string $connected_account_id): void { $request_payload = []; - if ($connected_account_id !== null) { - $request_payload["connected_account_id"] = $connected_account_id; - } + $request_payload["connected_account_id"] = $connected_account_id; - $this->seam->request( + $this->client->request( "POST", "/connected_accounts/simulate/disconnect", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } } diff --git a/src/Routes/CustomersClient.php b/src/Routes/CustomersClient.php index f8c97a1f..1ee55265 100644 --- a/src/Routes/CustomersClient.php +++ b/src/Routes/CustomersClient.php @@ -2,22 +2,32 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\CustomerPortal; -use Seam\SeamClient; class CustomersClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** * Creates a new customer portal magic link with configurable features. * - * @param array $customer_resources_filters Filter configuration for resources based on their custom_metadata. Each filter specifies a field, operation, and value to match against resource custom_metadata. + * @param list|\stdClass> $customer_resources_filters Filter configuration for resources based on their custom_metadata. Each filter specifies a field, operation, and value to match against resource custom_metadata. * @param string $customization_profile_id The ID of the customization profile to use for the portal. * @param mixed $deep_link Deep link target resource for initial redirect. When set, the portal will navigate directly to the specified resource. * @param bool $exclude_locale_picker Whether to exclude the option to select a locale within the portal UI. @@ -83,38 +93,40 @@ public function create_portal( $request_payload["customer_data"] = $customer_data; } - $res = $this->seam->request( - "POST", - "/customers/create_portal", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/customers/create_portal", [ + "json" => (object) $request_payload, + ]), ); - return CustomerPortal::from_json($res->customer_portal); + return CustomerPortal::from_json( + Body::read($res, "customer_portal", "/customers/create_portal"), + ); } /** * Deletes customer data including resources like spaces, properties, rooms, users, etc. * This will delete the partner resources and any related Seam resources (user identities, access grants, spaces). * - * @param array $access_grant_keys List of access grant keys to delete. - * @param array $booking_keys List of booking keys to delete. - * @param array $building_keys List of building keys to delete. - * @param array $common_area_keys List of common area keys to delete. - * @param array $customer_keys List of customer keys to delete all data for. - * @param array $facility_keys List of facility keys to delete. - * @param array $guest_keys List of guest keys to delete. - * @param array $listing_keys List of listing keys to delete. - * @param array $property_keys List of property keys to delete. - * @param array $property_listing_keys List of property listing keys to delete. - * @param array $reservation_keys List of reservation keys to delete. - * @param array $resident_keys List of resident keys to delete. - * @param array $room_keys List of room keys to delete. - * @param array $space_keys List of space keys to delete. - * @param array $staff_member_keys List of staff member keys to delete. - * @param array $tenant_keys List of tenant keys to delete. - * @param array $unit_keys List of unit keys to delete. - * @param array $user_identity_keys List of user identity keys to delete. - * @param array $user_keys List of user keys to delete. + * @param list $access_grant_keys List of access grant keys to delete. + * @param list $booking_keys List of booking keys to delete. + * @param list $building_keys List of building keys to delete. + * @param list $common_area_keys List of common area keys to delete. + * @param list $customer_keys List of customer keys to delete all data for. + * @param list $facility_keys List of facility keys to delete. + * @param list $guest_keys List of guest keys to delete. + * @param list $listing_keys List of listing keys to delete. + * @param list $property_keys List of property keys to delete. + * @param list $property_listing_keys List of property listing keys to delete. + * @param list $reservation_keys List of reservation keys to delete. + * @param list $resident_keys List of resident keys to delete. + * @param list $room_keys List of room keys to delete. + * @param list $space_keys List of space keys to delete. + * @param list $staff_member_keys List of staff member keys to delete. + * @param list $tenant_keys List of tenant keys to delete. + * @param list $unit_keys List of unit keys to delete. + * @param list $user_identity_keys List of user identity keys to delete. + * @param list $user_keys List of user keys to delete. * @return void OK */ public function delete_data( @@ -198,36 +210,34 @@ public function delete_data( $request_payload["user_keys"] = $user_keys; } - $this->seam->request( - "POST", - "/customers/delete_data", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/customers/delete_data", [ + "query" => $request_payload, + ]); } /** * Pushes customer data including resources like spaces, properties, rooms, users, etc. * * @param string $customer_key Your unique identifier for the customer. - * @param array $access_grants List of access grants. - * @param array $bookings List of bookings. - * @param array $buildings List of buildings. - * @param array $common_areas List of shared common areas. - * @param array $facilities List of gym or fitness facilities. - * @param array $guests List of guests. - * @param array $listings List of property listings. - * @param array $properties List of short-term rental properties. - * @param array $property_listings List of property listings. - * @param array $reservations List of reservations. - * @param array $residents List of residents. - * @param array $rooms List of hotel or hospitality rooms. - * @param array $sites List of general sites or areas. - * @param array $spaces List of general spaces or areas. - * @param array $staff_members List of staff members. - * @param array $tenants List of tenants. - * @param array $units List of multi-family residential units. - * @param array $user_identities List of user identities. - * @param array $users List of users. + * @param list|\stdClass> $access_grants List of access grants. + * @param list|\stdClass> $bookings List of bookings. + * @param list|\stdClass> $buildings List of buildings. + * @param list|\stdClass> $common_areas List of shared common areas. + * @param list|\stdClass> $facilities List of gym or fitness facilities. + * @param list|\stdClass> $guests List of guests. + * @param list|\stdClass> $listings List of property listings. + * @param list|\stdClass> $properties List of short-term rental properties. + * @param list|\stdClass> $property_listings List of property listings. + * @param list|\stdClass> $reservations List of reservations. + * @param list|\stdClass> $residents List of residents. + * @param list|\stdClass> $rooms List of hotel or hospitality rooms. + * @param list|\stdClass> $sites List of general sites or areas. + * @param list|\stdClass> $spaces List of general spaces or areas. + * @param list|\stdClass> $staff_members List of staff members. + * @param list|\stdClass> $tenants List of tenants. + * @param list|\stdClass> $units List of multi-family residential units. + * @param list|\stdClass> $user_identities List of user identities. + * @param list|\stdClass> $users List of users. * @return void OK */ public function push_data( @@ -254,9 +264,7 @@ public function push_data( ): void { $request_payload = []; - if ($customer_key !== null) { - $request_payload["customer_key"] = $customer_key; - } + $request_payload["customer_key"] = $customer_key; if ($access_grants !== null) { $request_payload["access_grants"] = $access_grants; } @@ -315,10 +323,8 @@ public function push_data( $request_payload["users"] = $users; } - $this->seam->request( - "POST", - "/customers/push_data", - json: (object) $request_payload, - ); + $this->client->request("POST", "/customers/push_data", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/DevicesClient.php b/src/Routes/DevicesClient.php index 9eb2f20c..ab41c023 100644 --- a/src/Routes/DevicesClient.php +++ b/src/Routes/DevicesClient.php @@ -2,20 +2,31 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\Device; use Seam\Resources\DeviceProvider; -use Seam\SeamClient; class DevicesClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public DevicesSimulateClient $simulate; public DevicesUnmanagedClient $unmanaged; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->simulate = new DevicesSimulateClient($seam); - $this->unmanaged = new DevicesUnmanagedClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->simulate = new DevicesSimulateClient($client, $defaults); + $this->unmanaged = new DevicesUnmanagedClient($client, $defaults); } /** @@ -29,6 +40,11 @@ public function __construct(SeamClient $seam) */ public function get(?string $device_id = null, ?string $name = null): Device { + if ($device_id === null && $name === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /devices/get", + ); + } $request_payload = []; if ($device_id !== null) { @@ -38,13 +54,13 @@ public function get(?string $device_id = null, ?string $name = null): Device $request_payload["name"] = $name; } - $res = $this->seam->request( - "POST", - "/devices/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/devices/get", [ + "query" => $request_payload, + ]), ); - return Device::from_json($res->device); + return Device::from_json(Body::read($res, "device", "/devices/get")); } /** @@ -52,20 +68,21 @@ public function get(?string $device_id = null, ?string $name = null): Device * * @param string $connect_webview_id ID of the Connect Webview for which you want to list devices. * @param string $connected_account_id ID of the connected account for which you want to list devices. - * @param array $connected_account_ids Array of IDs of the connected accounts for which you want to list devices. + * @param list $connected_account_ids Array of IDs of the connected accounts for which you want to list devices. * @param string $created_before Timestamp by which to limit returned devices. Returns devices created before this timestamp. - * @param mixed $custom_metadata_has Set of key:value [custom metadata](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) pairs for which you want to list devices. + * @param array|\stdClass $custom_metadata_has Set of key:value [custom metadata](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) pairs for which you want to list devices. * @param string $customer_key Customer key for which you want to list devices. - * @param array $device_ids Array of device IDs for which you want to list devices. + * @param list $device_ids Array of device IDs for which you want to list devices. * @param string $device_type Device type for which you want to list devices. - * @param array $device_types Array of device types for which you want to list devices. + * @param list $device_types Array of device types for which you want to list devices. * @param float $limit Numerical limit on the number of devices to return. * @param string $manufacturer Manufacturer for which you want to list devices. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned devices to include all records that satisfy a partial match using `device_id` (full or partial UUID prefix, minimum 4 characters), `connected_account_id`, `display_name`, `custom_metadata` or `location.location_name`. * @param string $space_id ID of the space for which you want to list devices. - * @param string $unstable_location_id + * @param string|NullValue $unstable_location_id * @param string $user_identifier_key Your own internal user ID for the user for which you want to list devices. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( @@ -73,17 +90,17 @@ public function list( ?string $connected_account_id = null, ?array $connected_account_ids = null, ?string $created_before = null, - mixed $custom_metadata_has = null, + array|\stdClass|null $custom_metadata_has = null, ?string $customer_key = null, ?array $device_ids = null, ?string $device_type = null, ?array $device_types = null, ?float $limit = null, ?string $manufacturer = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?string $space_id = null, - ?string $unstable_location_id = null, + string|NullValue|null $unstable_location_id = null, ?string $user_identifier_key = null, ?callable $on_response = null, ): array { @@ -138,17 +155,20 @@ public function list( $request_payload["user_identifier_key"] = $user_identifier_key; } - $res = $this->seam->request( - "POST", - "/devices/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/devices/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { $on_response($res); } - return array_map(fn($r) => Device::from_json($r), $res->devices); + return array_map( + fn($r) => Device::from_json($r), + Body::read_list($res, "devices", "/devices/list"), + ); } /** @@ -170,37 +190,37 @@ public function list_device_providers( $request_payload["provider_category"] = $provider_category; } - $res = $this->seam->request( - "POST", - "/devices/list_device_providers", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/devices/list_device_providers", [ + "query" => $request_payload, + ]), ); return array_map( fn($r) => DeviceProvider::from_json($r), - $res->device_providers, + Body::read_list( + $res, + "device_providers", + "/devices/list_device_providers", + ), ); } /** * Updates provider-specific metadata for devices. * - * @param array $devices Array of devices with provider metadata to update + * @param list|\stdClass> $devices Array of devices with provider metadata to update * @return void OK */ public function report_provider_metadata(array $devices): void { $request_payload = []; - if ($devices !== null) { - $request_payload["devices"] = $devices; - } + $request_payload["devices"] = $devices; - $this->seam->request( - "POST", - "/devices/report_provider_metadata", - json: (object) $request_payload, - ); + $this->client->request("POST", "/devices/report_provider_metadata", [ + "json" => (object) $request_payload, + ]); } /** @@ -210,25 +230,23 @@ public function report_provider_metadata(array $devices): void * * @param string $device_id ID of the device that you want to update. * @param bool $backup_access_code_pool_enabled Indicates whether the device's [backup access code pool](https://docs.seam.co/low-level-apis/smart-locks/access-codes/backup-access-codes) is enabled. Set to `false` to disable the pool: Seam stops refilling it and removes any backup codes that have not yet been pulled into active use. - * @param mixed $custom_metadata Custom metadata that you want to associate with the device. Supports up to 50 JSON key:value pairs. [Adding custom metadata to a device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) enables you to store custom information, like customer details or internal IDs from your application. Then, you can [filter devices by the desired metadata](https://docs.seam.co/core-concepts/devices/filtering-devices-by-custom-metadata). + * @param array|\stdClass $custom_metadata Custom metadata that you want to associate with the device. Supports up to 50 JSON key:value pairs. [Adding custom metadata to a device](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) enables you to store custom information, like customer details or internal IDs from your application. Then, you can [filter devices by the desired metadata](https://docs.seam.co/core-concepts/devices/filtering-devices-by-custom-metadata). * @param bool $is_managed Indicates whether the device is managed. To unmanage a device, set `is_managed` to `false`. - * @param string $name Name for the device. + * @param string|NullValue $name Name for the device. * @param mixed $properties * @return void OK */ public function update( string $device_id, ?bool $backup_access_code_pool_enabled = null, - mixed $custom_metadata = null, + array|\stdClass|null $custom_metadata = null, ?bool $is_managed = null, - ?string $name = null, + string|NullValue|null $name = null, mixed $properties = null, ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($backup_access_code_pool_enabled !== null) { $request_payload[ "backup_access_code_pool_enabled" @@ -247,10 +265,8 @@ public function update( $request_payload["properties"] = $properties; } - $this->seam->request( - "POST", - "/devices/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/devices/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/DevicesSimulateClient.php b/src/Routes/DevicesSimulateClient.php index 9fb351b3..b33bceac 100644 --- a/src/Routes/DevicesSimulateClient.php +++ b/src/Routes/DevicesSimulateClient.php @@ -2,15 +2,24 @@ namespace Seam\Routes; -use Seam\SeamClient; +use GuzzleHttp\ClientInterface; class DevicesSimulateClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -23,15 +32,11 @@ public function connect(string $device_id): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $this->seam->request( - "POST", - "/devices/simulate/connect", - json: (object) $request_payload, - ); + $this->client->request("POST", "/devices/simulate/connect", [ + "json" => (object) $request_payload, + ]); } /** @@ -47,15 +52,11 @@ public function connect_to_hub(string $device_id): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $this->seam->request( - "POST", - "/devices/simulate/connect_to_hub", - json: (object) $request_payload, - ); + $this->client->request("POST", "/devices/simulate/connect_to_hub", [ + "json" => (object) $request_payload, + ]); } /** @@ -68,15 +69,11 @@ public function disconnect(string $device_id): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $this->seam->request( - "POST", - "/devices/simulate/disconnect", - json: (object) $request_payload, - ); + $this->client->request("POST", "/devices/simulate/disconnect", [ + "json" => (object) $request_payload, + ]); } /** @@ -93,14 +90,12 @@ public function disconnect_from_hub(string $device_id): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $this->seam->request( + $this->client->request( "POST", "/devices/simulate/disconnect_from_hub", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -117,18 +112,12 @@ public function paid_subscription(string $device_id, bool $is_expired): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($is_expired !== null) { - $request_payload["is_expired"] = $is_expired; - } + $request_payload["device_id"] = $device_id; + $request_payload["is_expired"] = $is_expired; - $this->seam->request( - "POST", - "/devices/simulate/paid_subscription", - json: (object) $request_payload, - ); + $this->client->request("POST", "/devices/simulate/paid_subscription", [ + "json" => (object) $request_payload, + ]); } /** @@ -141,14 +130,10 @@ public function remove(string $device_id): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $this->seam->request( - "POST", - "/devices/simulate/remove", - json: (object) $request_payload, - ); + $this->client->request("POST", "/devices/simulate/remove", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/DevicesUnmanagedClient.php b/src/Routes/DevicesUnmanagedClient.php index 171090df..2f70c9b2 100644 --- a/src/Routes/DevicesUnmanagedClient.php +++ b/src/Routes/DevicesUnmanagedClient.php @@ -2,16 +2,27 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\UnmanagedDevice; -use Seam\SeamClient; class DevicesUnmanagedClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -29,6 +40,11 @@ public function get( ?string $device_id = null, ?string $name = null, ): UnmanagedDevice { + if ($device_id === null && $name === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /devices/unmanaged/get", + ); + } $request_payload = []; if ($device_id !== null) { @@ -38,13 +54,15 @@ public function get( $request_payload["name"] = $name; } - $res = $this->seam->request( - "POST", - "/devices/unmanaged/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/devices/unmanaged/get", [ + "query" => $request_payload, + ]), ); - return UnmanagedDevice::from_json($res->device); + return UnmanagedDevice::from_json( + Body::read($res, "device", "/devices/unmanaged/get"), + ); } /** @@ -54,20 +72,17 @@ public function get( * * @param string $connect_webview_id ID of the Connect Webview for which you want to list devices. * @param string $connected_account_id ID of the connected account for which you want to list devices. - * @param array $connected_account_ids Array of IDs of the connected accounts for which you want to list devices. + * @param list $connected_account_ids Array of IDs of the connected accounts for which you want to list devices. * @param string $created_before Timestamp by which to limit returned devices. Returns devices created before this timestamp. - * @param mixed $custom_metadata_has Set of key:value [custom metadata](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) pairs for which you want to list devices. * @param string $customer_key Customer key for which you want to list devices. - * @param array $device_ids Array of device IDs for which you want to list devices. + * @param list $device_ids Array of device IDs for which you want to list devices. * @param string $device_type Device type for which you want to list devices. - * @param array $device_types Array of device types for which you want to list devices. + * @param list $device_types Array of device types for which you want to list devices. * @param float $limit Numerical limit on the number of devices to return. * @param string $manufacturer Manufacturer for which you want to list devices. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned devices to include all records that satisfy a partial match using `device_id` (full or partial UUID prefix, minimum 4 characters), `connected_account_id`, `display_name`, `custom_metadata` or `location.location_name`. - * @param string $space_id ID of the space for which you want to list devices. - * @param string $unstable_location_id - * @param string $user_identifier_key Your own internal user ID for the user for which you want to list devices. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( @@ -75,18 +90,14 @@ public function list( ?string $connected_account_id = null, ?array $connected_account_ids = null, ?string $created_before = null, - mixed $custom_metadata_has = null, ?string $customer_key = null, ?array $device_ids = null, ?string $device_type = null, ?array $device_types = null, ?float $limit = null, ?string $manufacturer = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, - ?string $space_id = null, - ?string $unstable_location_id = null, - ?string $user_identifier_key = null, ?callable $on_response = null, ): array { $request_payload = []; @@ -103,9 +114,6 @@ public function list( if ($created_before !== null) { $request_payload["created_before"] = $created_before; } - if ($custom_metadata_has !== null) { - $request_payload["custom_metadata_has"] = $custom_metadata_has; - } if ($customer_key !== null) { $request_payload["customer_key"] = $customer_key; } @@ -130,20 +138,11 @@ public function list( if ($search !== null) { $request_payload["search"] = $search; } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } - if ($unstable_location_id !== null) { - $request_payload["unstable_location_id"] = $unstable_location_id; - } - if ($user_identifier_key !== null) { - $request_payload["user_identifier_key"] = $user_identifier_key; - } - $res = $this->seam->request( - "POST", - "/devices/unmanaged/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/devices/unmanaged/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -152,7 +151,7 @@ public function list( return array_map( fn($r) => UnmanagedDevice::from_json($r), - $res->devices, + Body::read_list($res, "devices", "/devices/unmanaged/list"), ); } @@ -162,20 +161,18 @@ public function list( * An unmanaged device has a limited set of visible properties and a subset of supported events. You cannot control an unmanaged device. Any [access codes](https://docs.seam.co/low-level-apis/smart-locks/access-codes/migrating-existing-access-codes) on an unmanaged device are unmanaged. To control an unmanaged device with Seam, [convert it to a managed device](https://docs.seam.co/core-concepts/devices/managed-and-unmanaged-devices#convert-an-unmanaged-device-to-managed). * * @param string $device_id ID of the unmanaged device that you want to update. - * @param mixed $custom_metadata Custom metadata that you want to associate with the device. Supports up to 50 JSON key:value pairs. - * @param bool $is_managed Indicates whether the device is managed. Set this parameter to `true` to convert an unmanaged device to managed. + * @param array|\stdClass $custom_metadata Custom metadata that you want to associate with the device. Supports up to 50 JSON key:value pairs. + * @param true $is_managed Indicates whether the device is managed. Set this parameter to `true` to convert an unmanaged device to managed. * @return void OK */ public function update( string $device_id, - mixed $custom_metadata = null, - ?bool $is_managed = null, + array|\stdClass|null $custom_metadata = null, + ?true $is_managed = null, ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($custom_metadata !== null) { $request_payload["custom_metadata"] = $custom_metadata; } @@ -183,10 +180,8 @@ public function update( $request_payload["is_managed"] = $is_managed; } - $this->seam->request( - "POST", - "/devices/unmanaged/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/devices/unmanaged/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/EventsClient.php b/src/Routes/EventsClient.php index a1c37ba1..6a4509ce 100644 --- a/src/Routes/EventsClient.php +++ b/src/Routes/EventsClient.php @@ -2,16 +2,26 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\Event; -use Seam\SeamClient; class EventsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -27,6 +37,11 @@ public function get( ?string $device_id = null, ?string $event_type = null, ): Event { + if ($event_id === null && $device_id === null && $event_type === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /events/get", + ); + } $request_payload = []; if ($event_id !== null) { @@ -39,44 +54,44 @@ public function get( $request_payload["event_type"] = $event_type; } - $res = $this->seam->request( - "POST", - "/events/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/events/get", [ + "query" => $request_payload, + ]), ); - return Event::from_json($res->event); + return Event::from_json(Body::read($res, "event", "/events/get")); } /** * Returns a list of all events. This endpoint returns the same events that would be sent to a [webhook](https://docs.seam.co/developer-tools/webhooks), but it enables you to filter or see events that already took place. * * @param string $access_code_id ID of the access code for which you want to list events. - * @param array $access_code_ids IDs of the access codes for which you want to list events. + * @param list $access_code_ids IDs of the access codes for which you want to list events. * @param string $access_grant_id ID of the access grant for which you want to list events. - * @param array $access_grant_ids IDs of the access grants for which you want to list events. + * @param list $access_grant_ids IDs of the access grants for which you want to list events. * @param string $access_method_id ID of the access method for which you want to list events. - * @param array $access_method_ids IDs of the access methods for which you want to list events. + * @param list $access_method_ids IDs of the access methods for which you want to list events. * @param string $acs_access_group_id ID of the ACS access group for which you want to list events. * @param string $acs_credential_id ID of the ACS credential for which you want to list events. * @param string $acs_encoder_id ID of the ACS encoder for which you want to list events. * @param string $acs_entrance_id ID of the ACS entrance for which you want to list events. * @param string $acs_system_id ID of the access system for which you want to list events. - * @param array $acs_system_ids IDs of the access systems for which you want to list events. + * @param list $acs_system_ids IDs of the access systems for which you want to list events. * @param string $acs_user_id ID of the ACS user for which you want to list events. - * @param array $between Lower and upper timestamps to define an exclusive interval containing the events that you want to list. You must include `since` or `between`. + * @param list $between Lower and upper timestamps to define an exclusive interval containing the events that you want to list. You must include `since` or `between`. * @param string $connect_webview_id ID of the Connect Webview for which you want to list events. * @param string $connected_account_id ID of the connected account for which you want to list events. * @param string $customer_key Customer key for which you want to list events. * @param string $device_id ID of the device for which you want to list events. - * @param array $device_ids IDs of the devices for which you want to list events. - * @param array $event_ids IDs of the events that you want to list. + * @param list $device_ids IDs of the devices for which you want to list events. + * @param list $event_ids IDs of the events that you want to list. * @param string $event_type Type of the events that you want to list. - * @param array $event_types Types of the events that you want to list. + * @param list $event_types Types of the events that you want to list. * @param float $limit Numerical limit on the number of events to return. * @param string $since Timestamp to indicate the beginning generation time for the events that you want to list. You must include `since` or `between`. * @param string $space_id ID of the space for which you want to list events. - * @param array $space_ids IDs of the spaces for which you want to list events. + * @param list $space_ids IDs of the spaces for which you want to list events. * @param float $unstable_offset Offset for the events that you want to list. * @param string $user_identity_id ID of the user identity for which you want to list events. * @return array OK @@ -111,6 +126,39 @@ public function list( ?float $unstable_offset = null, ?string $user_identity_id = null, ): array { + if ( + $access_code_id === null && + $access_code_ids === null && + $access_grant_id === null && + $access_grant_ids === null && + $access_method_id === null && + $access_method_ids === null && + $acs_access_group_id === null && + $acs_credential_id === null && + $acs_encoder_id === null && + $acs_entrance_id === null && + $acs_system_id === null && + $acs_system_ids === null && + $acs_user_id === null && + $between === null && + $connect_webview_id === null && + $connected_account_id === null && + $customer_key === null && + $device_id === null && + $device_ids === null && + $event_ids === null && + $event_type === null && + $event_types === null && + $since === null && + $space_id === null && + $space_ids === null && + $unstable_offset === null && + $user_identity_id === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /events/list", + ); + } $request_payload = []; if ($access_code_id !== null) { @@ -198,12 +246,15 @@ public function list( $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/events/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/events/list", [ + "query" => $request_payload, + ]), ); - return array_map(fn($r) => Event::from_json($r), $res->events); + return array_map( + fn($r) => Event::from_json($r), + Body::read_list($res, "events", "/events/list"), + ); } } diff --git a/src/Routes/InstantKeysClient.php b/src/Routes/InstantKeysClient.php index 34815729..d732d637 100644 --- a/src/Routes/InstantKeysClient.php +++ b/src/Routes/InstantKeysClient.php @@ -2,16 +2,26 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\InstantKey; -use Seam\SeamClient; class InstantKeysClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -24,15 +34,11 @@ public function delete(string $instant_key_id): void { $request_payload = []; - if ($instant_key_id !== null) { - $request_payload["instant_key_id"] = $instant_key_id; - } + $request_payload["instant_key_id"] = $instant_key_id; - $this->seam->request( - "POST", - "/instant_keys/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/instant_keys/delete", [ + "query" => $request_payload, + ]); } /** @@ -46,6 +52,11 @@ public function get( ?string $instant_key_id = null, ?string $instant_key_url = null, ): InstantKey { + if ($instant_key_id === null && $instant_key_url === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /instant_keys/get", + ); + } $request_payload = []; if ($instant_key_id !== null) { @@ -55,13 +66,15 @@ public function get( $request_payload["instant_key_url"] = $instant_key_url; } - $res = $this->seam->request( - "POST", - "/instant_keys/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/instant_keys/get", [ + "query" => $request_payload, + ]), ); - return InstantKey::from_json($res->instant_key); + return InstantKey::from_json( + Body::read($res, "instant_key", "/instant_keys/get"), + ); } /** @@ -78,15 +91,15 @@ public function list(?string $user_identity_id = null): array $request_payload["user_identity_id"] = $user_identity_id; } - $res = $this->seam->request( - "POST", - "/instant_keys/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/instant_keys/list", [ + "query" => $request_payload, + ]), ); return array_map( fn($r) => InstantKey::from_json($r), - $res->instant_keys, + Body::read_list($res, "instant_keys", "/instant_keys/list"), ); } } diff --git a/src/Routes/LocksClient.php b/src/Routes/LocksClient.php index 00878fd9..a1d64a7c 100644 --- a/src/Routes/LocksClient.php +++ b/src/Routes/LocksClient.php @@ -2,18 +2,29 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; use Seam\Resources\ActionAttempt; use Seam\Resources\Device; -use Seam\SeamClient; class LocksClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public LocksSimulateClient $simulate; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->simulate = new LocksSimulateClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->simulate = new LocksSimulateClient($client, $defaults); } /** @@ -22,43 +33,43 @@ public function __construct(SeamClient $seam) * @param bool $auto_lock_enabled Whether to enable or disable auto-lock. * @param string $device_id ID of the lock for which you want to configure the auto-lock. * @param float $auto_lock_delay_seconds Delay in seconds before the lock automatically locks. Required when enabling auto-lock. Must be between 1 and 60. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function configure_auto_lock( bool $auto_lock_enabled, string $device_id, ?float $auto_lock_delay_seconds = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($auto_lock_enabled !== null) { - $request_payload["auto_lock_enabled"] = $auto_lock_enabled; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["auto_lock_enabled"] = $auto_lock_enabled; + $request_payload["device_id"] = $device_id; if ($auto_lock_delay_seconds !== null) { $request_payload[ "auto_lock_delay_seconds" ] = $auto_lock_delay_seconds; } - $res = $this->seam->request( - "POST", - "/locks/configure_auto_lock", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/locks/configure_auto_lock", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/locks/configure_auto_lock", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -71,6 +82,11 @@ public function configure_auto_lock( */ public function get(?string $device_id = null, ?string $name = null): Device { + if ($device_id === null && $name === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /locks/get", + ); + } $request_payload = []; if ($device_id !== null) { @@ -80,13 +96,13 @@ public function get(?string $device_id = null, ?string $name = null): Device $request_payload["name"] = $name; } - $res = $this->seam->request( - "POST", - "/locks/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/locks/get", [ + "query" => $request_payload, + ]), ); - return Device::from_json($res->device); + return Device::from_json(Body::read($res, "device", "/locks/get")); } /** @@ -94,40 +110,19 @@ public function get(?string $device_id = null, ?string $name = null): Device * * @param string $connect_webview_id ID of the Connect Webview for which you want to list devices. * @param string $connected_account_id ID of the connected account for which you want to list devices. - * @param array $connected_account_ids Array of IDs of the connected accounts for which you want to list devices. - * @param string $created_before Timestamp by which to limit returned devices. Returns devices created before this timestamp. - * @param mixed $custom_metadata_has Set of key:value [custom metadata](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) pairs for which you want to list devices. * @param string $customer_key Customer key for which you want to list devices. - * @param array $device_ids Array of device IDs for which you want to list devices. * @param string $device_type Device type of the locks that you want to list. - * @param array $device_types Device types of the locks that you want to list. - * @param float $limit Numerical limit on the number of devices to return. + * @param list $device_types Device types of the locks that you want to list. * @param string $manufacturer Manufacturer of the locks that you want to list. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. - * @param string $search String for which to search. Filters returned devices to include all records that satisfy a partial match using `device_id` (full or partial UUID prefix, minimum 4 characters), `connected_account_id`, `display_name`, `custom_metadata` or `location.location_name`. - * @param string $space_id ID of the space for which you want to list devices. - * @param string $unstable_location_id - * @param string $user_identifier_key Your own internal user ID for the user for which you want to list devices. * @return array OK */ public function list( ?string $connect_webview_id = null, ?string $connected_account_id = null, - ?array $connected_account_ids = null, - ?string $created_before = null, - mixed $custom_metadata_has = null, ?string $customer_key = null, - ?array $device_ids = null, ?string $device_type = null, ?array $device_types = null, - ?float $limit = null, ?string $manufacturer = null, - ?string $page_cursor = null, - ?string $search = null, - ?string $space_id = null, - ?string $unstable_location_id = null, - ?string $user_identifier_key = null, - ?callable $on_response = null, ): array { $request_payload = []; @@ -137,125 +132,90 @@ public function list( if ($connected_account_id !== null) { $request_payload["connected_account_id"] = $connected_account_id; } - if ($connected_account_ids !== null) { - $request_payload["connected_account_ids"] = $connected_account_ids; - } - if ($created_before !== null) { - $request_payload["created_before"] = $created_before; - } - if ($custom_metadata_has !== null) { - $request_payload["custom_metadata_has"] = $custom_metadata_has; - } if ($customer_key !== null) { $request_payload["customer_key"] = $customer_key; } - if ($device_ids !== null) { - $request_payload["device_ids"] = $device_ids; - } if ($device_type !== null) { $request_payload["device_type"] = $device_type; } if ($device_types !== null) { $request_payload["device_types"] = $device_types; } - if ($limit !== null) { - $request_payload["limit"] = $limit; - } if ($manufacturer !== null) { $request_payload["manufacturer"] = $manufacturer; } - if ($page_cursor !== null) { - $request_payload["page_cursor"] = $page_cursor; - } - if ($search !== null) { - $request_payload["search"] = $search; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } - if ($unstable_location_id !== null) { - $request_payload["unstable_location_id"] = $unstable_location_id; - } - if ($user_identifier_key !== null) { - $request_payload["user_identifier_key"] = $user_identifier_key; - } - $res = $this->seam->request( - "POST", - "/locks/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/locks/list", [ + "query" => $request_payload, + ]), ); - if ($on_response !== null) { - $on_response($res); - } - - return array_map(fn($r) => Device::from_json($r), $res->devices); + return array_map( + fn($r) => Device::from_json($r), + Body::read_list($res, "devices", "/locks/list"), + ); } /** * Locks a [lock](https://docs.seam.co/low-level-apis/smart-locks). See also [Locking and Unlocking Smart Locks](https://docs.seam.co/low-level-apis/smart-locks/lock-and-unlock). * * @param string $device_id ID of the lock that you want to lock. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function lock_door( string $device_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/locks/lock_door", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/locks/lock_door", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/locks/lock_door"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** * Unlocks a [lock](https://docs.seam.co/low-level-apis/smart-locks). See also [Locking and Unlocking Smart Locks](https://docs.seam.co/low-level-apis/smart-locks/lock-and-unlock). * * @param string $device_id ID of the lock that you want to unlock. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function unlock_door( string $device_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/locks/unlock_door", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/locks/unlock_door", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/locks/unlock_door"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } } diff --git a/src/Routes/LocksSimulateClient.php b/src/Routes/LocksSimulateClient.php index 500448d8..02daf9fa 100644 --- a/src/Routes/LocksSimulateClient.php +++ b/src/Routes/LocksSimulateClient.php @@ -2,16 +2,27 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; use Seam\Resources\ActionAttempt; -use Seam\SeamClient; class LocksSimulateClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -19,69 +30,75 @@ public function __construct(SeamClient $seam) * * @param string $code Code that you want to simulate entering on a keypad. * @param string $device_id ID of the device for which you want to simulate a keypad code entry. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function keypad_code_entry( string $code, string $device_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($code !== null) { - $request_payload["code"] = $code; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["code"] = $code; + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/locks/simulate/keypad_code_entry", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/locks/simulate/keypad_code_entry", + ["json" => (object) $request_payload], + ), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/locks/simulate/keypad_code_entry", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** * Simulates a manual lock action using a keypad. You can only perform this action for [August](https://docs.seam.co/device-and-system-integration-guides/august-locks) devices within [sandbox workspaces](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces). * * @param string $device_id ID of the device for which you want to simulate a manual lock action using a keypad. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function manual_lock_via_keypad( string $device_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/locks/simulate/manual_lock_via_keypad", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/locks/simulate/manual_lock_via_keypad", + ["json" => (object) $request_payload], + ), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/locks/simulate/manual_lock_via_keypad", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } } diff --git a/src/Routes/NoiseSensorsClient.php b/src/Routes/NoiseSensorsClient.php index d34a85b9..05079f0e 100644 --- a/src/Routes/NoiseSensorsClient.php +++ b/src/Routes/NoiseSensorsClient.php @@ -2,19 +2,32 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\Device; -use Seam\SeamClient; class NoiseSensorsClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public NoiseSensorsNoiseThresholdsClient $noise_thresholds; public NoiseSensorsSimulateClient $simulate; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->noise_thresholds = new NoiseSensorsNoiseThresholdsClient($seam); - $this->simulate = new NoiseSensorsSimulateClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->noise_thresholds = new NoiseSensorsNoiseThresholdsClient( + $client, + $defaults, + ); + $this->simulate = new NoiseSensorsSimulateClient($client, $defaults); } /** @@ -22,40 +35,19 @@ public function __construct(SeamClient $seam) * * @param string $connect_webview_id ID of the Connect Webview for which you want to list devices. * @param string $connected_account_id ID of the connected account for which you want to list devices. - * @param array $connected_account_ids Array of IDs of the connected accounts for which you want to list devices. - * @param string $created_before Timestamp by which to limit returned devices. Returns devices created before this timestamp. - * @param mixed $custom_metadata_has Set of key:value [custom metadata](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) pairs for which you want to list devices. * @param string $customer_key Customer key for which you want to list devices. - * @param array $device_ids Array of device IDs for which you want to list devices. * @param string $device_type Device type of the noise sensors that you want to list. - * @param array $device_types Device types of the noise sensors that you want to list. - * @param float $limit Numerical limit on the number of devices to return. + * @param list $device_types Device types of the noise sensors that you want to list. * @param string $manufacturer Manufacturers of the noise sensors that you want to list. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. - * @param string $search String for which to search. Filters returned devices to include all records that satisfy a partial match using `device_id` (full or partial UUID prefix, minimum 4 characters), `connected_account_id`, `display_name`, `custom_metadata` or `location.location_name`. - * @param string $space_id ID of the space for which you want to list devices. - * @param string $unstable_location_id - * @param string $user_identifier_key Your own internal user ID for the user for which you want to list devices. * @return array OK */ public function list( ?string $connect_webview_id = null, ?string $connected_account_id = null, - ?array $connected_account_ids = null, - ?string $created_before = null, - mixed $custom_metadata_has = null, ?string $customer_key = null, - ?array $device_ids = null, ?string $device_type = null, ?array $device_types = null, - ?float $limit = null, ?string $manufacturer = null, - ?string $page_cursor = null, - ?string $search = null, - ?string $space_id = null, - ?string $unstable_location_id = null, - ?string $user_identifier_key = null, - ?callable $on_response = null, ): array { $request_payload = []; @@ -65,59 +57,28 @@ public function list( if ($connected_account_id !== null) { $request_payload["connected_account_id"] = $connected_account_id; } - if ($connected_account_ids !== null) { - $request_payload["connected_account_ids"] = $connected_account_ids; - } - if ($created_before !== null) { - $request_payload["created_before"] = $created_before; - } - if ($custom_metadata_has !== null) { - $request_payload["custom_metadata_has"] = $custom_metadata_has; - } if ($customer_key !== null) { $request_payload["customer_key"] = $customer_key; } - if ($device_ids !== null) { - $request_payload["device_ids"] = $device_ids; - } if ($device_type !== null) { $request_payload["device_type"] = $device_type; } if ($device_types !== null) { $request_payload["device_types"] = $device_types; } - if ($limit !== null) { - $request_payload["limit"] = $limit; - } if ($manufacturer !== null) { $request_payload["manufacturer"] = $manufacturer; } - if ($page_cursor !== null) { - $request_payload["page_cursor"] = $page_cursor; - } - if ($search !== null) { - $request_payload["search"] = $search; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } - if ($unstable_location_id !== null) { - $request_payload["unstable_location_id"] = $unstable_location_id; - } - if ($user_identifier_key !== null) { - $request_payload["user_identifier_key"] = $user_identifier_key; - } - $res = $this->seam->request( - "POST", - "/noise_sensors/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/noise_sensors/list", [ + "query" => $request_payload, + ]), ); - if ($on_response !== null) { - $on_response($res); - } - - return array_map(fn($r) => Device::from_json($r), $res->devices); + return array_map( + fn($r) => Device::from_json($r), + Body::read_list($res, "devices", "/noise_sensors/list"), + ); } } diff --git a/src/Routes/NoiseSensorsNoiseThresholdsClient.php b/src/Routes/NoiseSensorsNoiseThresholdsClient.php index 80492126..8d65eb62 100644 --- a/src/Routes/NoiseSensorsNoiseThresholdsClient.php +++ b/src/Routes/NoiseSensorsNoiseThresholdsClient.php @@ -2,16 +2,26 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\NoiseThreshold; -use Seam\SeamClient; class NoiseSensorsNoiseThresholdsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -35,15 +45,9 @@ public function create( ): NoiseThreshold { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($ends_daily_at !== null) { - $request_payload["ends_daily_at"] = $ends_daily_at; - } - if ($starts_daily_at !== null) { - $request_payload["starts_daily_at"] = $starts_daily_at; - } + $request_payload["device_id"] = $device_id; + $request_payload["ends_daily_at"] = $ends_daily_at; + $request_payload["starts_daily_at"] = $starts_daily_at; if ($name !== null) { $request_payload["name"] = $name; } @@ -56,13 +60,21 @@ public function create( $request_payload["noise_threshold_nrs"] = $noise_threshold_nrs; } - $res = $this->seam->request( - "POST", - "/noise_sensors/noise_thresholds/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/noise_sensors/noise_thresholds/create", + ["json" => (object) $request_payload], + ), ); - return NoiseThreshold::from_json($res->noise_threshold); + return NoiseThreshold::from_json( + Body::read( + $res, + "noise_threshold", + "/noise_sensors/noise_thresholds/create", + ), + ); } /** @@ -76,17 +88,13 @@ public function delete(string $device_id, string $noise_threshold_id): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($noise_threshold_id !== null) { - $request_payload["noise_threshold_id"] = $noise_threshold_id; - } + $request_payload["device_id"] = $device_id; + $request_payload["noise_threshold_id"] = $noise_threshold_id; - $this->seam->request( - "POST", + $this->client->request( + "DELETE", "/noise_sensors/noise_thresholds/delete", - json: (object) $request_payload, + ["query" => $request_payload], ); } @@ -100,17 +108,23 @@ public function get(string $noise_threshold_id): NoiseThreshold { $request_payload = []; - if ($noise_threshold_id !== null) { - $request_payload["noise_threshold_id"] = $noise_threshold_id; - } + $request_payload["noise_threshold_id"] = $noise_threshold_id; - $res = $this->seam->request( - "POST", - "/noise_sensors/noise_thresholds/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/noise_sensors/noise_thresholds/get", + ["query" => $request_payload], + ), ); - return NoiseThreshold::from_json($res->noise_threshold); + return NoiseThreshold::from_json( + Body::read( + $res, + "noise_threshold", + "/noise_sensors/noise_thresholds/get", + ), + ); } /** @@ -123,19 +137,23 @@ public function list(string $device_id): array { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/noise_sensors/noise_thresholds/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/noise_sensors/noise_thresholds/list", + ["query" => $request_payload], + ), ); return array_map( fn($r) => NoiseThreshold::from_json($r), - $res->noise_thresholds, + Body::read_list( + $res, + "noise_thresholds", + "/noise_sensors/noise_thresholds/list", + ), ); } @@ -162,12 +180,8 @@ public function update( ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($noise_threshold_id !== null) { - $request_payload["noise_threshold_id"] = $noise_threshold_id; - } + $request_payload["device_id"] = $device_id; + $request_payload["noise_threshold_id"] = $noise_threshold_id; if ($ends_daily_at !== null) { $request_payload["ends_daily_at"] = $ends_daily_at; } @@ -186,10 +200,10 @@ public function update( $request_payload["starts_daily_at"] = $starts_daily_at; } - $this->seam->request( - "POST", + $this->client->request( + "PUT", "/noise_sensors/noise_thresholds/update", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } } diff --git a/src/Routes/NoiseSensorsSimulateClient.php b/src/Routes/NoiseSensorsSimulateClient.php index ac6c5891..7d387ece 100644 --- a/src/Routes/NoiseSensorsSimulateClient.php +++ b/src/Routes/NoiseSensorsSimulateClient.php @@ -2,15 +2,24 @@ namespace Seam\Routes; -use Seam\SeamClient; +use GuzzleHttp\ClientInterface; class NoiseSensorsSimulateClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -23,14 +32,12 @@ public function trigger_noise_threshold(string $device_id): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $this->seam->request( + $this->client->request( "POST", "/noise_sensors/simulate/trigger_noise_threshold", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } } diff --git a/src/Routes/PhonesClient.php b/src/Routes/PhonesClient.php index 922b3f64..d8576183 100644 --- a/src/Routes/PhonesClient.php +++ b/src/Routes/PhonesClient.php @@ -2,17 +2,27 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\Phone; -use Seam\SeamClient; class PhonesClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public PhonesSimulateClient $simulate; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->simulate = new PhonesSimulateClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->simulate = new PhonesSimulateClient($client, $defaults); } /** @@ -25,15 +35,11 @@ public function deactivate(string $device_id): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $this->seam->request( - "POST", - "/phones/deactivate", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/phones/deactivate", [ + "query" => $request_payload, + ]); } /** @@ -46,17 +52,15 @@ public function get(string $device_id): Phone { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/phones/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/phones/get", [ + "query" => $request_payload, + ]), ); - return Phone::from_json($res->phone); + return Phone::from_json(Body::read($res, "phone", "/phones/get")); } /** @@ -81,12 +85,15 @@ public function list( ] = $owner_user_identity_id; } - $res = $this->seam->request( - "POST", - "/phones/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/phones/list", [ + "query" => $request_payload, + ]), ); - return array_map(fn($r) => Phone::from_json($r), $res->phones); + return array_map( + fn($r) => Phone::from_json($r), + Body::read_list($res, "phones", "/phones/list"), + ); } } diff --git a/src/Routes/PhonesSimulateClient.php b/src/Routes/PhonesSimulateClient.php index e33ee226..353ceafb 100644 --- a/src/Routes/PhonesSimulateClient.php +++ b/src/Routes/PhonesSimulateClient.php @@ -2,16 +2,26 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\Phone; -use Seam\SeamClient; class PhonesSimulateClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -31,9 +41,7 @@ public function create_sandbox_phone( ): Phone { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; if ($assa_abloy_metadata !== null) { $request_payload["assa_abloy_metadata"] = $assa_abloy_metadata; } @@ -46,12 +54,16 @@ public function create_sandbox_phone( $request_payload["phone_metadata"] = $phone_metadata; } - $res = $this->seam->request( - "POST", - "/phones/simulate/create_sandbox_phone", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/phones/simulate/create_sandbox_phone", + ["json" => (object) $request_payload], + ), ); - return Phone::from_json($res->phone); + return Phone::from_json( + Body::read($res, "phone", "/phones/simulate/create_sandbox_phone"), + ); } } diff --git a/src/Routes/SpacesClient.php b/src/Routes/SpacesClient.php index b90f93b9..beb92404 100644 --- a/src/Routes/SpacesClient.php +++ b/src/Routes/SpacesClient.php @@ -2,23 +2,34 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\Batch; use Seam\Resources\Space; -use Seam\SeamClient; class SpacesClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** * Adds [entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) to a specific space. * - * @param array $acs_entrance_ids IDs of the entrances that you want to add to the space. + * @param list $acs_entrance_ids IDs of the entrances that you want to add to the space. * @param string $space_id ID of the space to which you want to add entrances. * @return void OK */ @@ -28,18 +39,12 @@ public function add_acs_entrances( ): void { $request_payload = []; - if ($acs_entrance_ids !== null) { - $request_payload["acs_entrance_ids"] = $acs_entrance_ids; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } + $request_payload["acs_entrance_ids"] = $acs_entrance_ids; + $request_payload["space_id"] = $space_id; - $this->seam->request( - "POST", - "/spaces/add_acs_entrances", - json: (object) $request_payload, - ); + $this->client->request("PUT", "/spaces/add_acs_entrances", [ + "json" => (object) $request_payload, + ]); } /** @@ -55,24 +60,18 @@ public function add_connected_account( ): void { $request_payload = []; - if ($connected_account_id !== null) { - $request_payload["connected_account_id"] = $connected_account_id; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } + $request_payload["connected_account_id"] = $connected_account_id; + $request_payload["space_id"] = $space_id; - $this->seam->request( - "POST", - "/spaces/add_connected_account", - json: (object) $request_payload, - ); + $this->client->request("PUT", "/spaces/add_connected_account", [ + "json" => (object) $request_payload, + ]); } /** * Adds devices to a specific space. * - * @param array $device_ids IDs of the devices that you want to add to the space. + * @param list $device_ids IDs of the devices that you want to add to the space. * @param string $space_id ID of the space to which you want to add devices. * @return void OK */ @@ -80,29 +79,23 @@ public function add_devices(array $device_ids, string $space_id): void { $request_payload = []; - if ($device_ids !== null) { - $request_payload["device_ids"] = $device_ids; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } + $request_payload["device_ids"] = $device_ids; + $request_payload["space_id"] = $space_id; - $this->seam->request( - "POST", - "/spaces/add_devices", - json: (object) $request_payload, - ); + $this->client->request("PUT", "/spaces/add_devices", [ + "json" => (object) $request_payload, + ]); } /** * Creates a new space. * * @param string $name Name of the space that you want to create. - * @param array $acs_entrance_ids IDs of the entrances that you want to add to the new space. - * @param array $connected_account_ids IDs of connected accounts to associate with the new space. Persisted on seam.location_third_party_account so the UI can show which provider account(s) a space came from. + * @param list $acs_entrance_ids IDs of the entrances that you want to add to the new space. + * @param list $connected_account_ids IDs of connected accounts to associate with the new space. Persisted on seam.location_third_party_account so the UI can show which provider account(s) a space came from. * @param mixed $customer_data Reservation/stay-related defaults for the space. * @param string $customer_key Customer key for which you want to create the space. - * @param array $device_ids IDs of the devices that you want to add to the new space. + * @param list $device_ids IDs of the devices that you want to add to the new space. * @param string $space_key Unique key for the space within the workspace. * @return Space OK */ @@ -117,9 +110,7 @@ public function create( ): Space { $request_payload = []; - if ($name !== null) { - $request_payload["name"] = $name; - } + $request_payload["name"] = $name; if ($acs_entrance_ids !== null) { $request_payload["acs_entrance_ids"] = $acs_entrance_ids; } @@ -139,13 +130,13 @@ public function create( $request_payload["space_key"] = $space_key; } - $res = $this->seam->request( - "POST", - "/spaces/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/spaces/create", [ + "json" => (object) $request_payload, + ]), ); - return Space::from_json($res->space); + return Space::from_json(Body::read($res, "space", "/spaces/create")); } /** @@ -158,15 +149,11 @@ public function delete(string $space_id): void { $request_payload = []; - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } + $request_payload["space_id"] = $space_id; - $this->seam->request( - "POST", - "/spaces/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/spaces/delete", [ + "query" => $request_payload, + ]); } /** @@ -180,6 +167,11 @@ public function get( ?string $space_id = null, ?string $space_key = null, ): Space { + if ($space_id === null && $space_key === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /spaces/get", + ); + } $request_payload = []; if ($space_id !== null) { @@ -189,22 +181,22 @@ public function get( $request_payload["space_key"] = $space_key; } - $res = $this->seam->request( - "POST", - "/spaces/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/spaces/get", [ + "query" => $request_payload, + ]), ); - return Space::from_json($res->space); + return Space::from_json(Body::read($res, "space", "/spaces/get")); } /** * Gets all related resources for one or more Spaces. * - * @param array $exclude - * @param array $include - * @param array $space_ids IDs of the spaces that you want to get along with their related resources. - * @param array $space_keys Keys of the spaces that you want to get along with their related resources. + * @param list $exclude + * @param list $include + * @param list $space_ids IDs of the spaces that you want to get along with their related resources. + * @param list $space_keys Keys of the spaces that you want to get along with their related resources. * @return Batch OK */ public function get_related( @@ -213,6 +205,16 @@ public function get_related( ?array $space_ids = null, ?array $space_keys = null, ): Batch { + if ( + $exclude === null && + $include === null && + $space_ids === null && + $space_keys === null + ) { + throw new \InvalidArgumentException( + "At least one parameter is required for /spaces/get_related", + ); + } $request_payload = []; if ($exclude !== null) { @@ -228,13 +230,15 @@ public function get_related( $request_payload["space_keys"] = $space_keys; } - $res = $this->seam->request( - "POST", - "/spaces/get_related", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/spaces/get_related", [ + "query" => $request_payload, + ]), ); - return Batch::from_json($res->batch); + return Batch::from_json( + Body::read($res, "batch", "/spaces/get_related"), + ); } /** @@ -242,15 +246,16 @@ public function get_related( * * @param string $customer_key Customer key for which you want to list spaces. * @param float $limit Maximum number of records to return per page. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned spaces to include all records that satisfy a partial match using `name`, `space_key`, or `customer_key`. * @param string $space_key Filter spaces by space_key. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( ?string $customer_key = null, ?float $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?string $space_key = null, ?callable $on_response = null, @@ -273,23 +278,26 @@ public function list( $request_payload["space_key"] = $space_key; } - $res = $this->seam->request( - "POST", - "/spaces/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/spaces/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { $on_response($res); } - return array_map(fn($r) => Space::from_json($r), $res->spaces); + return array_map( + fn($r) => Space::from_json($r), + Body::read_list($res, "spaces", "/spaces/list"), + ); } /** * Removes [entrances](https://docs.seam.co/low-level-apis/access-systems/retrieving-entrance-details) from a specific space. * - * @param array $acs_entrance_ids IDs of the entrances that you want to remove from the space. + * @param list $acs_entrance_ids IDs of the entrances that you want to remove from the space. * @param string $space_id ID of the space from which you want to remove entrances. * @return void OK */ @@ -299,18 +307,12 @@ public function remove_acs_entrances( ): void { $request_payload = []; - if ($acs_entrance_ids !== null) { - $request_payload["acs_entrance_ids"] = $acs_entrance_ids; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } + $request_payload["acs_entrance_ids"] = $acs_entrance_ids; + $request_payload["space_id"] = $space_id; - $this->seam->request( - "POST", - "/spaces/remove_acs_entrances", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/spaces/remove_acs_entrances", [ + "query" => $request_payload, + ]); } /** @@ -326,24 +328,18 @@ public function remove_connected_account( ): void { $request_payload = []; - if ($connected_account_id !== null) { - $request_payload["connected_account_id"] = $connected_account_id; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } + $request_payload["connected_account_id"] = $connected_account_id; + $request_payload["space_id"] = $space_id; - $this->seam->request( - "POST", - "/spaces/remove_connected_account", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/spaces/remove_connected_account", [ + "query" => $request_payload, + ]); } /** * Removes devices from a specific space. * - * @param array $device_ids IDs of the devices that you want to remove from the space. + * @param list $device_ids IDs of the devices that you want to remove from the space. * @param string $space_id ID of the space from which you want to remove devices. * @return void OK */ @@ -351,26 +347,20 @@ public function remove_devices(array $device_ids, string $space_id): void { $request_payload = []; - if ($device_ids !== null) { - $request_payload["device_ids"] = $device_ids; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } + $request_payload["device_ids"] = $device_ids; + $request_payload["space_id"] = $space_id; - $this->seam->request( - "POST", - "/spaces/remove_devices", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/spaces/remove_devices", [ + "query" => $request_payload, + ]); } /** * Updates an existing space. * - * @param array $acs_entrance_ids IDs of the entrances that you want to set for the space. If specified, this will replace all existing entrances. + * @param list $acs_entrance_ids IDs of the entrances that you want to set for the space. If specified, this will replace all existing entrances. * @param mixed $customer_data Reservation/stay-related defaults for the space. Only the keys you provide are updated; omit a key to leave it unchanged. Pass null on a key to clear it. - * @param array $device_ids IDs of the devices that you want to set for the space. If specified, this will replace all existing devices. + * @param list $device_ids IDs of the devices that you want to set for the space. If specified, this will replace all existing devices. * @param string $name Name of the space. * @param string $space_id ID of the space that you want to update. * @param string $space_key Unique key of the space that you want to update. @@ -405,12 +395,12 @@ public function update( $request_payload["space_key"] = $space_key; } - $res = $this->seam->request( - "POST", - "/spaces/update", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("PATCH", "/spaces/update", [ + "json" => (object) $request_payload, + ]), ); - return Space::from_json($res->space); + return Space::from_json(Body::read($res, "space", "/spaces/update")); } } diff --git a/src/Routes/ThermostatsClient.php b/src/Routes/ThermostatsClient.php index 650aa7d6..bac9241b 100644 --- a/src/Routes/ThermostatsClient.php +++ b/src/Routes/ThermostatsClient.php @@ -2,22 +2,37 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; +use Seam\NullValue; use Seam\Resources\ActionAttempt; use Seam\Resources\Device; -use Seam\SeamClient; class ThermostatsClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public ThermostatsDailyProgramsClient $daily_programs; public ThermostatsSchedulesClient $schedules; public ThermostatsSimulateClient $simulate; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->daily_programs = new ThermostatsDailyProgramsClient($seam); - $this->schedules = new ThermostatsSchedulesClient($seam); - $this->simulate = new ThermostatsSimulateClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->daily_programs = new ThermostatsDailyProgramsClient( + $client, + $defaults, + ); + $this->schedules = new ThermostatsSchedulesClient($client, $defaults); + $this->simulate = new ThermostatsSimulateClient($client, $defaults); } /** @@ -25,37 +40,39 @@ public function __construct(SeamClient $seam) * * @param string $climate_preset_key Climate preset key of the climate preset that you want to activate. * @param string $device_id ID of the thermostat device for which you want to activate a climate preset. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function activate_climate_preset( string $climate_preset_key, string $device_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($climate_preset_key !== null) { - $request_payload["climate_preset_key"] = $climate_preset_key; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["climate_preset_key"] = $climate_preset_key; + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/thermostats/activate_climate_preset", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/thermostats/activate_climate_preset", + ["json" => (object) $request_payload], + ), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/thermostats/activate_climate_preset", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -64,19 +81,18 @@ public function activate_climate_preset( * @param string $device_id ID of the thermostat device that you want to set to cool mode. * @param float $cooling_set_point_celsius [Cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °C that you want to set for the thermostat. You must set one of the `cooling_set_point` parameters. * @param float $cooling_set_point_fahrenheit [Cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °F that you want to set for the thermostat. You must set one of the `cooling_set_point` parameters. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function cool( string $device_id, ?float $cooling_set_point_celsius = null, ?float $cooling_set_point_fahrenheit = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($cooling_set_point_celsius !== null) { $request_payload[ "cooling_set_point_celsius" @@ -88,21 +104,20 @@ public function cool( ] = $cooling_set_point_fahrenheit; } - $res = $this->seam->request( - "POST", - "/thermostats/cool", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/thermostats/cool", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/thermostats/cool"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -119,7 +134,7 @@ public function cool( * @param float $heating_set_point_fahrenheit Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). * @param string $hvac_mode_setting Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. * @param bool $manual_override_allowed Indicates whether a person at the thermostat or using the API can change the thermostat's settings. - * @param string $name User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + * @param string|NullValue $name User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). * @return void OK */ public function create_climate_preset( @@ -134,16 +149,12 @@ public function create_climate_preset( ?float $heating_set_point_fahrenheit = null, ?string $hvac_mode_setting = null, ?bool $manual_override_allowed = null, - ?string $name = null, + string|NullValue|null $name = null, ): void { $request_payload = []; - if ($climate_preset_key !== null) { - $request_payload["climate_preset_key"] = $climate_preset_key; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["climate_preset_key"] = $climate_preset_key; + $request_payload["device_id"] = $device_id; if ($climate_preset_mode !== null) { $request_payload["climate_preset_mode"] = $climate_preset_mode; } @@ -185,11 +196,9 @@ public function create_climate_preset( $request_payload["name"] = $name; } - $this->seam->request( - "POST", - "/thermostats/create_climate_preset", - json: (object) $request_payload, - ); + $this->client->request("POST", "/thermostats/create_climate_preset", [ + "json" => (object) $request_payload, + ]); } /** @@ -205,18 +214,12 @@ public function delete_climate_preset( ): void { $request_payload = []; - if ($climate_preset_key !== null) { - $request_payload["climate_preset_key"] = $climate_preset_key; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["climate_preset_key"] = $climate_preset_key; + $request_payload["device_id"] = $device_id; - $this->seam->request( - "POST", - "/thermostats/delete_climate_preset", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/thermostats/delete_climate_preset", [ + "query" => $request_payload, + ]); } /** @@ -225,19 +228,18 @@ public function delete_climate_preset( * @param string $device_id ID of the thermostat device that you want to set to heat mode. * @param float $heating_set_point_celsius [Heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °C that you want to set for the thermostat. You must set one of the `heating_set_point` parameters. * @param float $heating_set_point_fahrenheit [Heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °F that you want to set for the thermostat. You must set one of the `heating_set_point` parameters. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function heat( string $device_id, ?float $heating_set_point_celsius = null, ?float $heating_set_point_fahrenheit = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($heating_set_point_celsius !== null) { $request_payload[ "heating_set_point_celsius" @@ -249,21 +251,20 @@ public function heat( ] = $heating_set_point_fahrenheit; } - $res = $this->seam->request( - "POST", - "/thermostats/heat", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/thermostats/heat", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/thermostats/heat"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -274,6 +275,7 @@ public function heat( * @param float $cooling_set_point_fahrenheit [Cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °F that you want to set for the thermostat. You must set one of the `cooling_set_point` parameters. * @param float $heating_set_point_celsius [Heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °C that you want to set for the thermostat. You must set one of the `heating_set_point` parameters. * @param float $heating_set_point_fahrenheit [Heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °F that you want to set for the thermostat. You must set one of the `heating_set_point` parameters. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function heat_cool( @@ -282,13 +284,11 @@ public function heat_cool( ?float $cooling_set_point_fahrenheit = null, ?float $heating_set_point_celsius = null, ?float $heating_set_point_fahrenheit = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($cooling_set_point_celsius !== null) { $request_payload[ "cooling_set_point_celsius" @@ -310,21 +310,20 @@ public function heat_cool( ] = $heating_set_point_fahrenheit; } - $res = $this->seam->request( - "POST", - "/thermostats/heat_cool", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/thermostats/heat_cool", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/thermostats/heat_cool"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -332,40 +331,19 @@ public function heat_cool( * * @param string $connect_webview_id ID of the Connect Webview for which you want to list devices. * @param string $connected_account_id ID of the connected account for which you want to list devices. - * @param array $connected_account_ids Array of IDs of the connected accounts for which you want to list devices. - * @param string $created_before Timestamp by which to limit returned devices. Returns devices created before this timestamp. - * @param mixed $custom_metadata_has Set of key:value [custom metadata](https://docs.seam.co/core-concepts/devices/adding-custom-metadata-to-a-device) pairs for which you want to list devices. * @param string $customer_key Customer key for which you want to list devices. - * @param array $device_ids Array of device IDs for which you want to list devices. * @param string $device_type Device type by which you want to filter thermostat devices. - * @param array $device_types Array of device types by which you want to filter thermostat devices. - * @param float $limit Numerical limit on the number of devices to return. + * @param list $device_types Array of device types by which you want to filter thermostat devices. * @param string $manufacturer Manufacturer by which you want to filter thermostat devices. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. - * @param string $search String for which to search. Filters returned devices to include all records that satisfy a partial match using `device_id` (full or partial UUID prefix, minimum 4 characters), `connected_account_id`, `display_name`, `custom_metadata` or `location.location_name`. - * @param string $space_id ID of the space for which you want to list devices. - * @param string $unstable_location_id - * @param string $user_identifier_key Your own internal user ID for the user for which you want to list devices. * @return array OK */ public function list( ?string $connect_webview_id = null, ?string $connected_account_id = null, - ?array $connected_account_ids = null, - ?string $created_before = null, - mixed $custom_metadata_has = null, ?string $customer_key = null, - ?array $device_ids = null, ?string $device_type = null, ?array $device_types = null, - ?float $limit = null, ?string $manufacturer = null, - ?string $page_cursor = null, - ?string $search = null, - ?string $space_id = null, - ?string $unstable_location_id = null, - ?string $user_identifier_key = null, - ?callable $on_response = null, ): array { $request_payload = []; @@ -375,93 +353,60 @@ public function list( if ($connected_account_id !== null) { $request_payload["connected_account_id"] = $connected_account_id; } - if ($connected_account_ids !== null) { - $request_payload["connected_account_ids"] = $connected_account_ids; - } - if ($created_before !== null) { - $request_payload["created_before"] = $created_before; - } - if ($custom_metadata_has !== null) { - $request_payload["custom_metadata_has"] = $custom_metadata_has; - } if ($customer_key !== null) { $request_payload["customer_key"] = $customer_key; } - if ($device_ids !== null) { - $request_payload["device_ids"] = $device_ids; - } if ($device_type !== null) { $request_payload["device_type"] = $device_type; } if ($device_types !== null) { $request_payload["device_types"] = $device_types; } - if ($limit !== null) { - $request_payload["limit"] = $limit; - } if ($manufacturer !== null) { $request_payload["manufacturer"] = $manufacturer; } - if ($page_cursor !== null) { - $request_payload["page_cursor"] = $page_cursor; - } - if ($search !== null) { - $request_payload["search"] = $search; - } - if ($space_id !== null) { - $request_payload["space_id"] = $space_id; - } - if ($unstable_location_id !== null) { - $request_payload["unstable_location_id"] = $unstable_location_id; - } - if ($user_identifier_key !== null) { - $request_payload["user_identifier_key"] = $user_identifier_key; - } - $res = $this->seam->request( - "POST", - "/thermostats/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/thermostats/list", [ + "query" => $request_payload, + ]), ); - if ($on_response !== null) { - $on_response($res); - } - - return array_map(fn($r) => Device::from_json($r), $res->devices); + return array_map( + fn($r) => Device::from_json($r), + Body::read_list($res, "devices", "/thermostats/list"), + ); } /** * Sets a specified [thermostat](https://docs.seam.co/capability-guides/thermostats) to ["off" mode](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings). * * @param string $device_id ID of the thermostat device that you want to set to off mode. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function off( string $device_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; - $res = $this->seam->request( - "POST", - "/thermostats/off", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/thermostats/off", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/thermostats/off"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -477,17 +422,13 @@ public function set_fallback_climate_preset( ): void { $request_payload = []; - if ($climate_preset_key !== null) { - $request_payload["climate_preset_key"] = $climate_preset_key; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["climate_preset_key"] = $climate_preset_key; + $request_payload["device_id"] = $device_id; - $this->seam->request( + $this->client->request( "POST", "/thermostats/set_fallback_climate_preset", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -497,19 +438,18 @@ public function set_fallback_climate_preset( * @param string $device_id ID of the thermostat device for which you want to set the fan mode. * @param string $fan_mode Fan mode setting for the thermostat, such as `auto`, `on`, or `circulate`. * @param string $fan_mode_setting [Fan mode setting](https://docs.seam.co/capability-guides/thermostats/configure-current-climate-settings#fan-mode-settings) that you want to set for the thermostat. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function set_fan_mode( string $device_id, ?string $fan_mode = null, ?string $fan_mode_setting = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($fan_mode !== null) { $request_payload["fan_mode"] = $fan_mode; } @@ -517,21 +457,20 @@ public function set_fan_mode( $request_payload["fan_mode_setting"] = $fan_mode_setting; } - $res = $this->seam->request( - "POST", - "/thermostats/set_fan_mode", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/thermostats/set_fan_mode", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/thermostats/set_fan_mode"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** @@ -543,6 +482,7 @@ public function set_fan_mode( * @param float $cooling_set_point_fahrenheit [Cooling set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °F that you want to set for the thermostat. You must set one of the `cooling_set_point` parameters. * @param float $heating_set_point_celsius [Heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °C that you want to set for the thermostat. You must set one of the `heating_set_point` parameters. * @param float $heating_set_point_fahrenheit [Heating set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °F that you want to set for the thermostat. You must set one of the `heating_set_point` parameters. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function set_hvac_mode( @@ -552,16 +492,12 @@ public function set_hvac_mode( ?float $cooling_set_point_fahrenheit = null, ?float $heating_set_point_celsius = null, ?float $heating_set_point_fahrenheit = null, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($hvac_mode_setting !== null) { - $request_payload["hvac_mode_setting"] = $hvac_mode_setting; - } + $request_payload["device_id"] = $device_id; + $request_payload["hvac_mode_setting"] = $hvac_mode_setting; if ($cooling_set_point_celsius !== null) { $request_payload[ "cooling_set_point_celsius" @@ -583,45 +519,46 @@ public function set_hvac_mode( ] = $heating_set_point_fahrenheit; } - $res = $this->seam->request( - "POST", - "/thermostats/set_hvac_mode", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/thermostats/set_hvac_mode", [ + "json" => (object) $request_payload, + ]), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/thermostats/set_hvac_mode", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } /** * Sets a [temperature threshold](https://docs.seam.co/capability-guides/thermostats/setting-and-monitoring-temperature-thresholds) for a specified thermostat. Seam emits a `thermostat.temperature_threshold_exceeded` event and adds a warning on a thermostat if it reports a temperature outside the threshold range. * * @param string $device_id ID of the thermostat device for which you want to set a temperature threshold. - * @param float $lower_limit_celsius Lower temperature limit in in °C. Seam alerts you if the reported temperature is lower than this value. You can specify either `lower_limit` but not both. - * @param float $lower_limit_fahrenheit Lower temperature limit in in °F. Seam alerts you if the reported temperature is lower than this value. You can specify either `lower_limit` but not both. - * @param float $upper_limit_celsius Upper temperature limit in in °C. Seam alerts you if the reported temperature is higher than this value. You can specify either `upper_limit` but not both. - * @param float $upper_limit_fahrenheit Upper temperature limit in in °C. Seam alerts you if the reported temperature is higher than this value. You can specify either `upper_limit` but not both. + * @param float|NullValue $lower_limit_celsius Lower temperature limit in in °C. Seam alerts you if the reported temperature is lower than this value. You can specify either `lower_limit` but not both. + * @param float|NullValue $lower_limit_fahrenheit Lower temperature limit in in °F. Seam alerts you if the reported temperature is lower than this value. You can specify either `lower_limit` but not both. + * @param float|NullValue $upper_limit_celsius Upper temperature limit in in °C. Seam alerts you if the reported temperature is higher than this value. You can specify either `upper_limit` but not both. + * @param float|NullValue $upper_limit_fahrenheit Upper temperature limit in in °C. Seam alerts you if the reported temperature is higher than this value. You can specify either `upper_limit` but not both. * @return void OK */ public function set_temperature_threshold( string $device_id, - ?float $lower_limit_celsius = null, - ?float $lower_limit_fahrenheit = null, - ?float $upper_limit_celsius = null, - ?float $upper_limit_fahrenheit = null, + float|NullValue|null $lower_limit_celsius = null, + float|NullValue|null $lower_limit_fahrenheit = null, + float|NullValue|null $upper_limit_celsius = null, + float|NullValue|null $upper_limit_fahrenheit = null, ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($lower_limit_celsius !== null) { $request_payload["lower_limit_celsius"] = $lower_limit_celsius; } @@ -639,10 +576,10 @@ public function set_temperature_threshold( ] = $upper_limit_fahrenheit; } - $this->seam->request( - "POST", + $this->client->request( + "PATCH", "/thermostats/set_temperature_threshold", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -660,7 +597,7 @@ public function set_temperature_threshold( * @param float $heating_set_point_fahrenheit Temperature to which the thermostat should heat (in °F). See also [Set Points](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points). * @param string $hvac_mode_setting Desired [HVAC mode](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/hvac-mode) setting, such as `heat`, `cool`, `heat_cool`, or `off`. * @param bool $manual_override_allowed Indicates whether a person at the thermostat can change the thermostat's settings. See [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). - * @param string $name User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). + * @param string|NullValue $name User-friendly name to identify the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets). * @return void OK */ public function update_climate_preset( @@ -675,16 +612,12 @@ public function update_climate_preset( ?float $heating_set_point_fahrenheit = null, ?string $hvac_mode_setting = null, ?bool $manual_override_allowed = null, - ?string $name = null, + string|NullValue|null $name = null, ): void { $request_payload = []; - if ($climate_preset_key !== null) { - $request_payload["climate_preset_key"] = $climate_preset_key; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["climate_preset_key"] = $climate_preset_key; + $request_payload["device_id"] = $device_id; if ($climate_preset_mode !== null) { $request_payload["climate_preset_mode"] = $climate_preset_mode; } @@ -726,42 +659,39 @@ public function update_climate_preset( $request_payload["name"] = $name; } - $this->seam->request( - "POST", - "/thermostats/update_climate_preset", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/thermostats/update_climate_preset", [ + "json" => (object) $request_payload, + ]); } /** * Updates the thermostat weekly program for a thermostat device. To configure a weekly program, specify the ID of the daily program that you want to use for each day of the week. When you update a weekly program, the set of programs that you specify overwrites any previous weekly program for the thermostat. * * @param string $device_id ID of the thermostat device for which you want to update the weekly program. - * @param string $friday_program_id ID of the thermostat daily program to run on Fridays. - * @param string $monday_program_id ID of the thermostat daily program to run on Mondays. - * @param string $saturday_program_id ID of the thermostat daily program to run on Saturdays. - * @param string $sunday_program_id ID of the thermostat daily program to run on Sundays. - * @param string $thursday_program_id ID of the thermostat daily program to run on Thursdays. - * @param string $tuesday_program_id ID of the thermostat daily program to run on Tuesdays. - * @param string $wednesday_program_id ID of the thermostat daily program to run on Wednesdays. + * @param string|NullValue $friday_program_id ID of the thermostat daily program to run on Fridays. + * @param string|NullValue $monday_program_id ID of the thermostat daily program to run on Mondays. + * @param string|NullValue $saturday_program_id ID of the thermostat daily program to run on Saturdays. + * @param string|NullValue $sunday_program_id ID of the thermostat daily program to run on Sundays. + * @param string|NullValue $thursday_program_id ID of the thermostat daily program to run on Thursdays. + * @param string|NullValue $tuesday_program_id ID of the thermostat daily program to run on Tuesdays. + * @param string|NullValue $wednesday_program_id ID of the thermostat daily program to run on Wednesdays. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function update_weekly_program( string $device_id, - ?string $friday_program_id = null, - ?string $monday_program_id = null, - ?string $saturday_program_id = null, - ?string $sunday_program_id = null, - ?string $thursday_program_id = null, - ?string $tuesday_program_id = null, - ?string $wednesday_program_id = null, - bool $wait_for_action_attempt = true, + string|NullValue|null $friday_program_id = null, + string|NullValue|null $monday_program_id = null, + string|NullValue|null $saturday_program_id = null, + string|NullValue|null $sunday_program_id = null, + string|NullValue|null $thursday_program_id = null, + string|NullValue|null $tuesday_program_id = null, + string|NullValue|null $wednesday_program_id = null, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($friday_program_id !== null) { $request_payload["friday_program_id"] = $friday_program_id; } @@ -784,20 +714,25 @@ public function update_weekly_program( $request_payload["wednesday_program_id"] = $wednesday_program_id; } - $res = $this->seam->request( - "POST", - "/thermostats/update_weekly_program", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/thermostats/update_weekly_program", + ["json" => (object) $request_payload], + ), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/thermostats/update_weekly_program", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } } diff --git a/src/Routes/ThermostatsDailyProgramsClient.php b/src/Routes/ThermostatsDailyProgramsClient.php index 6d786342..32458eca 100644 --- a/src/Routes/ThermostatsDailyProgramsClient.php +++ b/src/Routes/ThermostatsDailyProgramsClient.php @@ -2,17 +2,28 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; use Seam\Resources\ActionAttempt; use Seam\Resources\ThermostatDailyProgram; -use Seam\SeamClient; class ThermostatsDailyProgramsClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -20,7 +31,7 @@ public function __construct(SeamClient $seam) * * @param string $device_id ID of the thermostat device for which you want to create a daily program. * @param string $name Name of the thermostat daily program. - * @param array $periods Array of thermostat daily program periods. + * @param list|\stdClass> $periods Array of thermostat daily program periods. * @return ThermostatDailyProgram OK */ public function create( @@ -30,24 +41,24 @@ public function create( ): ThermostatDailyProgram { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($name !== null) { - $request_payload["name"] = $name; - } - if ($periods !== null) { - $request_payload["periods"] = $periods; - } - - $res = $this->seam->request( - "POST", - "/thermostats/daily_programs/create", - json: (object) $request_payload, + $request_payload["device_id"] = $device_id; + $request_payload["name"] = $name; + $request_payload["periods"] = $periods; + + $res = Body::decode( + $this->client->request( + "POST", + "/thermostats/daily_programs/create", + ["json" => (object) $request_payload], + ), ); return ThermostatDailyProgram::from_json( - $res->thermostat_daily_program, + Body::read( + $res, + "thermostat_daily_program", + "/thermostats/daily_programs/create", + ), ); } @@ -61,61 +72,57 @@ public function delete(string $thermostat_daily_program_id): void { $request_payload = []; - if ($thermostat_daily_program_id !== null) { - $request_payload[ - "thermostat_daily_program_id" - ] = $thermostat_daily_program_id; - } + $request_payload[ + "thermostat_daily_program_id" + ] = $thermostat_daily_program_id; - $this->seam->request( - "POST", - "/thermostats/daily_programs/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/thermostats/daily_programs/delete", [ + "query" => $request_payload, + ]); } /** * Updates a specified thermostat daily program. The periods that you specify overwrite any existing periods for the daily program. * * @param string $name Name of the thermostat daily program that you want to update. - * @param array $periods Array of thermostat daily program periods. The periods that you specify overwrite any existing periods for the daily program. + * @param list|\stdClass> $periods Array of thermostat daily program periods. The periods that you specify overwrite any existing periods for the daily program. * @param string $thermostat_daily_program_id ID of the thermostat daily program that you want to update. + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function update( string $name, array $periods, string $thermostat_daily_program_id, - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { $request_payload = []; - if ($name !== null) { - $request_payload["name"] = $name; - } - if ($periods !== null) { - $request_payload["periods"] = $periods; - } - if ($thermostat_daily_program_id !== null) { - $request_payload[ - "thermostat_daily_program_id" - ] = $thermostat_daily_program_id; - } - - $res = $this->seam->request( - "POST", - "/thermostats/daily_programs/update", - json: (object) $request_payload, + $request_payload["name"] = $name; + $request_payload["periods"] = $periods; + $request_payload[ + "thermostat_daily_program_id" + ] = $thermostat_daily_program_id; + + $res = Body::decode( + $this->client->request( + "PATCH", + "/thermostats/daily_programs/update", + ["json" => (object) $request_payload], + ), ); - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read( + $res, + "action_attempt", + "/thermostats/daily_programs/update", + ), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], ); - - return $action_attempt; } } diff --git a/src/Routes/ThermostatsSchedulesClient.php b/src/Routes/ThermostatsSchedulesClient.php index fbc504b3..5f716d7f 100644 --- a/src/Routes/ThermostatsSchedulesClient.php +++ b/src/Routes/ThermostatsSchedulesClient.php @@ -2,16 +2,27 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\ThermostatSchedule; -use Seam\SeamClient; class ThermostatsSchedulesClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -22,7 +33,7 @@ public function __construct(SeamClient $seam) * @param string $ends_at Date and time at which the new thermostat schedule ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. * @param string $starts_at Date and time at which the new thermostat schedule starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. * @param bool $is_override_allowed Indicates whether a person at the thermostat or using the API can change the thermostat's settings while the new schedule is active. See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). - * @param int $max_override_period_minutes Number of minutes for which a person at the thermostat or using the API can change the thermostat's settings after the activation of the scheduled climate preset. See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). + * @param int|NullValue $max_override_period_minutes Number of minutes for which a person at the thermostat or using the API can change the thermostat's settings after the activation of the scheduled climate preset. See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). * @param string $name Name of the thermostat schedule. * @return ThermostatSchedule OK */ @@ -32,23 +43,15 @@ public function create( string $ends_at, string $starts_at, ?bool $is_override_allowed = null, - ?int $max_override_period_minutes = null, + int|NullValue|null $max_override_period_minutes = null, ?string $name = null, ): ThermostatSchedule { $request_payload = []; - if ($climate_preset_key !== null) { - $request_payload["climate_preset_key"] = $climate_preset_key; - } - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($ends_at !== null) { - $request_payload["ends_at"] = $ends_at; - } - if ($starts_at !== null) { - $request_payload["starts_at"] = $starts_at; - } + $request_payload["climate_preset_key"] = $climate_preset_key; + $request_payload["device_id"] = $device_id; + $request_payload["ends_at"] = $ends_at; + $request_payload["starts_at"] = $starts_at; if ($is_override_allowed !== null) { $request_payload["is_override_allowed"] = $is_override_allowed; } @@ -61,13 +64,19 @@ public function create( $request_payload["name"] = $name; } - $res = $this->seam->request( - "POST", - "/thermostats/schedules/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/thermostats/schedules/create", [ + "json" => (object) $request_payload, + ]), ); - return ThermostatSchedule::from_json($res->thermostat_schedule); + return ThermostatSchedule::from_json( + Body::read( + $res, + "thermostat_schedule", + "/thermostats/schedules/create", + ), + ); } /** @@ -80,17 +89,11 @@ public function delete(string $thermostat_schedule_id): void { $request_payload = []; - if ($thermostat_schedule_id !== null) { - $request_payload[ - "thermostat_schedule_id" - ] = $thermostat_schedule_id; - } + $request_payload["thermostat_schedule_id"] = $thermostat_schedule_id; - $this->seam->request( - "POST", - "/thermostats/schedules/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/thermostats/schedules/delete", [ + "query" => $request_payload, + ]); } /** @@ -103,19 +106,21 @@ public function get(string $thermostat_schedule_id): ThermostatSchedule { $request_payload = []; - if ($thermostat_schedule_id !== null) { - $request_payload[ - "thermostat_schedule_id" - ] = $thermostat_schedule_id; - } + $request_payload["thermostat_schedule_id"] = $thermostat_schedule_id; - $res = $this->seam->request( - "POST", - "/thermostats/schedules/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/thermostats/schedules/get", [ + "query" => $request_payload, + ]), ); - return ThermostatSchedule::from_json($res->thermostat_schedule); + return ThermostatSchedule::from_json( + Body::read( + $res, + "thermostat_schedule", + "/thermostats/schedules/get", + ), + ); } /** @@ -131,22 +136,24 @@ public function list( ): array { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($user_identifier_key !== null) { $request_payload["user_identifier_key"] = $user_identifier_key; } - $res = $this->seam->request( - "POST", - "/thermostats/schedules/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/thermostats/schedules/list", [ + "query" => $request_payload, + ]), ); return array_map( fn($r) => ThermostatSchedule::from_json($r), - $res->thermostat_schedules, + Body::read_list( + $res, + "thermostat_schedules", + "/thermostats/schedules/list", + ), ); } @@ -157,7 +164,7 @@ public function list( * @param string $climate_preset_key Key of the [climate preset](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-climate-presets) to use for the thermostat schedule. * @param string $ends_at Date and time at which the thermostat schedule ends, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. * @param bool $is_override_allowed Indicates whether a person at the thermostat or using the API can change the thermostat's settings while the schedule is active. See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). - * @param int $max_override_period_minutes Number of minutes for which a person at the thermostat or using the API can change the thermostat's settings after the activation of the scheduled climate preset. See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). + * @param int|NullValue $max_override_period_minutes Number of minutes for which a person at the thermostat or using the API can change the thermostat's settings after the activation of the scheduled climate preset. See also [Specifying Manual Override Permissions](https://docs.seam.co/capability-guides/thermostats/creating-and-managing-thermostat-schedules#specifying-manual-override-permissions). * @param string $name Name of the thermostat schedule. * @param string $starts_at Date and time at which the thermostat schedule starts, in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. * @return void OK @@ -167,17 +174,13 @@ public function update( ?string $climate_preset_key = null, ?string $ends_at = null, ?bool $is_override_allowed = null, - ?int $max_override_period_minutes = null, + int|NullValue|null $max_override_period_minutes = null, ?string $name = null, ?string $starts_at = null, ): void { $request_payload = []; - if ($thermostat_schedule_id !== null) { - $request_payload[ - "thermostat_schedule_id" - ] = $thermostat_schedule_id; - } + $request_payload["thermostat_schedule_id"] = $thermostat_schedule_id; if ($climate_preset_key !== null) { $request_payload["climate_preset_key"] = $climate_preset_key; } @@ -199,10 +202,8 @@ public function update( $request_payload["starts_at"] = $starts_at; } - $this->seam->request( - "POST", - "/thermostats/schedules/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/thermostats/schedules/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/ThermostatsSimulateClient.php b/src/Routes/ThermostatsSimulateClient.php index e3cf413f..244539cd 100644 --- a/src/Routes/ThermostatsSimulateClient.php +++ b/src/Routes/ThermostatsSimulateClient.php @@ -2,15 +2,24 @@ namespace Seam\Routes; -use Seam\SeamClient; +use GuzzleHttp\ClientInterface; class ThermostatsSimulateClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -34,12 +43,8 @@ public function hvac_mode_adjusted( ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($hvac_mode !== null) { - $request_payload["hvac_mode"] = $hvac_mode; - } + $request_payload["device_id"] = $device_id; + $request_payload["hvac_mode"] = $hvac_mode; if ($cooling_set_point_celsius !== null) { $request_payload[ "cooling_set_point_celsius" @@ -61,10 +66,10 @@ public function hvac_mode_adjusted( ] = $heating_set_point_fahrenheit; } - $this->seam->request( + $this->client->request( "POST", "/thermostats/simulate/hvac_mode_adjusted", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -83,9 +88,7 @@ public function temperature_reached( ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } + $request_payload["device_id"] = $device_id; if ($temperature_celsius !== null) { $request_payload["temperature_celsius"] = $temperature_celsius; } @@ -95,10 +98,10 @@ public function temperature_reached( ] = $temperature_fahrenheit; } - $this->seam->request( + $this->client->request( "POST", "/thermostats/simulate/temperature_reached", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } } diff --git a/src/Routes/UserIdentitiesClient.php b/src/Routes/UserIdentitiesClient.php index 4dca58e2..3bb35ff6 100644 --- a/src/Routes/UserIdentitiesClient.php +++ b/src/Routes/UserIdentitiesClient.php @@ -2,22 +2,36 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\AcsEntrance; use Seam\Resources\AcsSystem; use Seam\Resources\AcsUser; use Seam\Resources\Device; use Seam\Resources\InstantKey; use Seam\Resources\UserIdentity; -use Seam\SeamClient; class UserIdentitiesClient { - private SeamClient $seam; + private ClientInterface $client; + + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; public UserIdentitiesUnmanagedClient $unmanaged; - public function __construct(SeamClient $seam) + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; - $this->unmanaged = new UserIdentitiesUnmanagedClient($seam); + $this->client = $client; + $this->defaults = $defaults; + $this->unmanaged = new UserIdentitiesUnmanagedClient( + $client, + $defaults, + ); } /** @@ -39,9 +53,7 @@ public function add_acs_user( ): void { $request_payload = []; - if ($acs_user_id !== null) { - $request_payload["acs_user_id"] = $acs_user_id; - } + $request_payload["acs_user_id"] = $acs_user_id; if ($user_identity_id !== null) { $request_payload["user_identity_id"] = $user_identity_id; } @@ -49,29 +61,27 @@ public function add_acs_user( $request_payload["user_identity_key"] = $user_identity_key; } - $this->seam->request( - "POST", - "/user_identities/add_acs_user", - json: (object) $request_payload, - ); + $this->client->request("PUT", "/user_identities/add_acs_user", [ + "json" => (object) $request_payload, + ]); } /** * Creates a new [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity). * - * @param array $acs_system_ids List of access system IDs to associate with the new user identity through access system users. If there's no user with the same email address or phone number in the specified access systems, a new access system user is created. If there is an existing user with the same email or phone number in the specified access systems, the user is linked to the user identity. - * @param string $email_address Unique email address for the new user identity. - * @param string $full_name Full name of the user associated with the new user identity. - * @param string $phone_number Unique phone number for the new user identity in E.164 format (for example, +15555550100). - * @param string $user_identity_key Unique key for the new user identity. + * @param list $acs_system_ids List of access system IDs to associate with the new user identity through access system users. If there's no user with the same email address or phone number in the specified access systems, a new access system user is created. If there is an existing user with the same email or phone number in the specified access systems, the user is linked to the user identity. + * @param string|NullValue $email_address Unique email address for the new user identity. + * @param string|NullValue $full_name Full name of the user associated with the new user identity. + * @param string|NullValue $phone_number Unique phone number for the new user identity in E.164 format (for example, +15555550100). + * @param string|NullValue $user_identity_key Unique key for the new user identity. * @return UserIdentity OK */ public function create( ?array $acs_system_ids = null, - ?string $email_address = null, - ?string $full_name = null, - ?string $phone_number = null, - ?string $user_identity_key = null, + string|NullValue|null $email_address = null, + string|NullValue|null $full_name = null, + string|NullValue|null $phone_number = null, + string|NullValue|null $user_identity_key = null, ): UserIdentity { $request_payload = []; @@ -91,13 +101,15 @@ public function create( $request_payload["user_identity_key"] = $user_identity_key; } - $res = $this->seam->request( - "POST", - "/user_identities/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/user_identities/create", [ + "json" => (object) $request_payload, + ]), ); - return UserIdentity::from_json($res->user_identity); + return UserIdentity::from_json( + Body::read($res, "user_identity", "/user_identities/create"), + ); } /** @@ -110,15 +122,11 @@ public function delete(string $user_identity_id): void { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; - $this->seam->request( - "POST", - "/user_identities/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/user_identities/delete", [ + "query" => $request_payload, + ]); } /** @@ -136,9 +144,7 @@ public function generate_instant_key( ): InstantKey { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; if ($customization_profile_id !== null) { $request_payload[ "customization_profile_id" @@ -148,13 +154,21 @@ public function generate_instant_key( $request_payload["max_use_count"] = $max_use_count; } - $res = $this->seam->request( - "POST", - "/user_identities/generate_instant_key", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "POST", + "/user_identities/generate_instant_key", + ["json" => (object) $request_payload], + ), ); - return InstantKey::from_json($res->instant_key); + return InstantKey::from_json( + Body::read( + $res, + "instant_key", + "/user_identities/generate_instant_key", + ), + ); } /** @@ -168,6 +182,11 @@ public function get( ?string $user_identity_id = null, ?string $user_identity_key = null, ): UserIdentity { + if ($user_identity_id === null && $user_identity_key === null) { + throw new \InvalidArgumentException( + "At least one parameter is required for /user_identities/get", + ); + } $request_payload = []; if ($user_identity_id !== null) { @@ -177,13 +196,15 @@ public function get( $request_payload["user_identity_key"] = $user_identity_key; } - $res = $this->seam->request( - "POST", - "/user_identities/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/user_identities/get", [ + "query" => $request_payload, + ]), ); - return UserIdentity::from_json($res->user_identity); + return UserIdentity::from_json( + Body::read($res, "user_identity", "/user_identities/get"), + ); } /** @@ -199,17 +220,13 @@ public function grant_access_to_device( ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["device_id"] = $device_id; + $request_payload["user_identity_id"] = $user_identity_id; - $this->seam->request( - "POST", + $this->client->request( + "PUT", "/user_identities/grant_access_to_device", - json: (object) $request_payload, + ["json" => (object) $request_payload], ); } @@ -219,16 +236,17 @@ public function grant_access_to_device( * @param string $created_before Timestamp by which to limit returned user identities. Returns user identities created before this timestamp. * @param string $credential_manager_acs_system_id `acs_system_id` of the credential manager by which you want to filter the list of user identities. * @param int $limit Maximum number of records to return per page. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned user identities to include all records that satisfy a partial match using `full_name`, `phone_number`, `email_address` or `user_identity_id`. - * @param array $user_identity_ids Array of user identity IDs by which to filter the list of user identities. + * @param list $user_identity_ids Array of user identity IDs by which to filter the list of user identities. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( ?string $created_before = null, ?string $credential_manager_acs_system_id = null, ?int $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?array $user_identity_ids = null, ?callable $on_response = null, @@ -256,10 +274,10 @@ public function list( $request_payload["user_identity_ids"] = $user_identity_ids; } - $res = $this->seam->request( - "POST", - "/user_identities/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/user_identities/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -268,7 +286,7 @@ public function list( return array_map( fn($r) => UserIdentity::from_json($r), - $res->user_identities, + Body::read_list($res, "user_identities", "/user_identities/list"), ); } @@ -282,17 +300,24 @@ public function list_accessible_devices(string $user_identity_id): array { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; - $res = $this->seam->request( - "POST", - "/user_identities/list_accessible_devices", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/user_identities/list_accessible_devices", + ["query" => $request_payload], + ), ); - return array_map(fn($r) => Device::from_json($r), $res->devices); + return array_map( + fn($r) => Device::from_json($r), + Body::read_list( + $res, + "devices", + "/user_identities/list_accessible_devices", + ), + ); } /** @@ -305,19 +330,23 @@ public function list_accessible_entrances(string $user_identity_id): array { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; - $res = $this->seam->request( - "POST", - "/user_identities/list_accessible_entrances", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request( + "GET", + "/user_identities/list_accessible_entrances", + ["query" => $request_payload], + ), ); return array_map( fn($r) => AcsEntrance::from_json($r), - $res->acs_entrances, + Body::read_list( + $res, + "acs_entrances", + "/user_identities/list_accessible_entrances", + ), ); } @@ -331,17 +360,22 @@ public function list_acs_systems(string $user_identity_id): array { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; - $res = $this->seam->request( - "POST", - "/user_identities/list_acs_systems", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/user_identities/list_acs_systems", [ + "query" => $request_payload, + ]), ); - return array_map(fn($r) => AcsSystem::from_json($r), $res->acs_systems); + return array_map( + fn($r) => AcsSystem::from_json($r), + Body::read_list( + $res, + "acs_systems", + "/user_identities/list_acs_systems", + ), + ); } /** @@ -354,17 +388,22 @@ public function list_acs_users(string $user_identity_id): array { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; - $res = $this->seam->request( - "POST", - "/user_identities/list_acs_users", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/user_identities/list_acs_users", [ + "query" => $request_payload, + ]), ); - return array_map(fn($r) => AcsUser::from_json($r), $res->acs_users); + return array_map( + fn($r) => AcsUser::from_json($r), + Body::read_list( + $res, + "acs_users", + "/user_identities/list_acs_users", + ), + ); } /** @@ -380,18 +419,12 @@ public function remove_acs_user( ): void { $request_payload = []; - if ($acs_user_id !== null) { - $request_payload["acs_user_id"] = $acs_user_id; - } - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["acs_user_id"] = $acs_user_id; + $request_payload["user_identity_id"] = $user_identity_id; - $this->seam->request( - "POST", - "/user_identities/remove_acs_user", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/user_identities/remove_acs_user", [ + "query" => $request_payload, + ]); } /** @@ -407,17 +440,13 @@ public function revoke_access_to_device( ): void { $request_payload = []; - if ($device_id !== null) { - $request_payload["device_id"] = $device_id; - } - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["device_id"] = $device_id; + $request_payload["user_identity_id"] = $user_identity_id; - $this->seam->request( - "POST", + $this->client->request( + "DELETE", "/user_identities/revoke_access_to_device", - json: (object) $request_payload, + ["query" => $request_payload], ); } @@ -425,24 +454,22 @@ public function revoke_access_to_device( * Updates a specified [user identity](https://docs.seam.co/capability-guides/mobile-access/managing-mobile-app-user-accounts-with-user-identities#what-is-a-user-identity). * * @param string $user_identity_id ID of the user identity that you want to update. - * @param string $email_address Unique email address for the user identity. - * @param string $full_name Full name of the user associated with the user identity. - * @param string $phone_number Unique phone number for the user identity. - * @param string $user_identity_key Unique key for the user identity. + * @param string|NullValue $email_address Unique email address for the user identity. + * @param string|NullValue $full_name Full name of the user associated with the user identity. + * @param string|NullValue $phone_number Unique phone number for the user identity. + * @param string|NullValue $user_identity_key Unique key for the user identity. * @return void OK */ public function update( string $user_identity_id, - ?string $email_address = null, - ?string $full_name = null, - ?string $phone_number = null, - ?string $user_identity_key = null, + string|NullValue|null $email_address = null, + string|NullValue|null $full_name = null, + string|NullValue|null $phone_number = null, + string|NullValue|null $user_identity_key = null, ): void { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; if ($email_address !== null) { $request_payload["email_address"] = $email_address; } @@ -456,10 +483,8 @@ public function update( $request_payload["user_identity_key"] = $user_identity_key; } - $this->seam->request( - "POST", - "/user_identities/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/user_identities/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/UserIdentitiesUnmanagedClient.php b/src/Routes/UserIdentitiesUnmanagedClient.php index e8f40773..9bf8f516 100644 --- a/src/Routes/UserIdentitiesUnmanagedClient.php +++ b/src/Routes/UserIdentitiesUnmanagedClient.php @@ -2,16 +2,27 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\NullValue; use Seam\Resources\UnmanagedUserIdentity; -use Seam\SeamClient; class UserIdentitiesUnmanagedClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -24,17 +35,17 @@ public function get(string $user_identity_id): UnmanagedUserIdentity { $request_payload = []; - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["user_identity_id"] = $user_identity_id; - $res = $this->seam->request( - "POST", - "/user_identities/unmanaged/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/user_identities/unmanaged/get", [ + "query" => $request_payload, + ]), ); - return UnmanagedUserIdentity::from_json($res->user_identity); + return UnmanagedUserIdentity::from_json( + Body::read($res, "user_identity", "/user_identities/unmanaged/get"), + ); } /** @@ -42,14 +53,15 @@ public function get(string $user_identity_id): UnmanagedUserIdentity * * @param string $created_before Timestamp by which to limit returned unmanaged user identities. Returns user identities created before this timestamp. * @param int $limit Maximum number of records to return per page. - * @param string $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. + * @param string|NullValue $page_cursor Identifies the specific page of results to return, obtained from the previous page's `next_page_cursor`. * @param string $search String for which to search. Filters returned unmanaged user identities to include all records that satisfy a partial match using `full_name`, `phone_number`, `email_address`, `user_identity_id` or `acs_system_id`. + * @param callable|null $on_response Called with the raw response envelope, used by the paginator to read the pagination metadata. * @return array OK */ public function list( ?string $created_before = null, ?int $limit = null, - ?string $page_cursor = null, + string|NullValue|null $page_cursor = null, ?string $search = null, ?callable $on_response = null, ): array { @@ -68,10 +80,10 @@ public function list( $request_payload["search"] = $search; } - $res = $this->seam->request( - "POST", - "/user_identities/unmanaged/list", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/user_identities/unmanaged/list", [ + "query" => $request_payload, + ]), ); if ($on_response !== null) { @@ -80,7 +92,11 @@ public function list( return array_map( fn($r) => UnmanagedUserIdentity::from_json($r), - $res->user_identities, + Body::read_list( + $res, + "user_identities", + "/user_identities/unmanaged/list", + ), ); } @@ -89,32 +105,26 @@ public function list( * * This endpoint can only be used to convert unmanaged user identities to managed ones by setting `is_managed` to `true`. It cannot be used to convert managed user identities back to unmanaged. * - * @param bool $is_managed Must be set to true to convert the unmanaged user identity to managed. + * @param true $is_managed Must be set to true to convert the unmanaged user identity to managed. * @param string $user_identity_id ID of the unmanaged user identity that you want to update. * @param string $user_identity_key Unique key for the user identity. If not provided, the existing key will be preserved. * @return void OK */ public function update( - bool $is_managed, + true $is_managed, string $user_identity_id, ?string $user_identity_key = null, ): void { $request_payload = []; - if ($is_managed !== null) { - $request_payload["is_managed"] = $is_managed; - } - if ($user_identity_id !== null) { - $request_payload["user_identity_id"] = $user_identity_id; - } + $request_payload["is_managed"] = $is_managed; + $request_payload["user_identity_id"] = $user_identity_id; if ($user_identity_key !== null) { $request_payload["user_identity_key"] = $user_identity_key; } - $this->seam->request( - "POST", - "/user_identities/unmanaged/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/user_identities/unmanaged/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/WebhooksClient.php b/src/Routes/WebhooksClient.php index 67fd844c..042dda4e 100644 --- a/src/Routes/WebhooksClient.php +++ b/src/Routes/WebhooksClient.php @@ -2,43 +2,53 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; use Seam\Resources\Webhook; -use Seam\SeamClient; class WebhooksClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** * Creates a new [webhook](https://docs.seam.co/developer-tools/webhooks). * * @param string $url URL for the new webhook. - * @param array $event_types Types of events that you want the new webhook to receive. + * @param list $event_types Types of events that you want the new webhook to receive. * @return Webhook OK */ public function create(string $url, ?array $event_types = null): Webhook { $request_payload = []; - if ($url !== null) { - $request_payload["url"] = $url; - } + $request_payload["url"] = $url; if ($event_types !== null) { $request_payload["event_types"] = $event_types; } - $res = $this->seam->request( - "POST", - "/webhooks/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/webhooks/create", [ + "json" => (object) $request_payload, + ]), ); - return Webhook::from_json($res->webhook); + return Webhook::from_json( + Body::read($res, "webhook", "/webhooks/create"), + ); } /** @@ -51,15 +61,11 @@ public function delete(string $webhook_id): void { $request_payload = []; - if ($webhook_id !== null) { - $request_payload["webhook_id"] = $webhook_id; - } + $request_payload["webhook_id"] = $webhook_id; - $this->seam->request( - "POST", - "/webhooks/delete", - json: (object) $request_payload, - ); + $this->client->request("DELETE", "/webhooks/delete", [ + "query" => $request_payload, + ]); } /** @@ -72,17 +78,15 @@ public function get(string $webhook_id): Webhook { $request_payload = []; - if ($webhook_id !== null) { - $request_payload["webhook_id"] = $webhook_id; - } + $request_payload["webhook_id"] = $webhook_id; - $res = $this->seam->request( - "POST", - "/webhooks/get", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("GET", "/webhooks/get", [ + "query" => $request_payload, + ]), ); - return Webhook::from_json($res->webhook); + return Webhook::from_json(Body::read($res, "webhook", "/webhooks/get")); } /** @@ -92,15 +96,18 @@ public function get(string $webhook_id): Webhook */ public function list(): array { - $res = $this->seam->request("POST", "/webhooks/list"); + $res = Body::decode($this->client->request("GET", "/webhooks/list")); - return array_map(fn($r) => Webhook::from_json($r), $res->webhooks); + return array_map( + fn($r) => Webhook::from_json($r), + Body::read_list($res, "webhooks", "/webhooks/list"), + ); } /** * Updates a specified [webhook](https://docs.seam.co/developer-tools/webhooks). * - * @param array $event_types Types of events that you want the webhook to receive. + * @param list $event_types Types of events that you want the webhook to receive. * @param string $webhook_id ID of the webhook that you want to update. * @return void OK */ @@ -108,17 +115,11 @@ public function update(array $event_types, string $webhook_id): void { $request_payload = []; - if ($event_types !== null) { - $request_payload["event_types"] = $event_types; - } - if ($webhook_id !== null) { - $request_payload["webhook_id"] = $webhook_id; - } + $request_payload["event_types"] = $event_types; + $request_payload["webhook_id"] = $webhook_id; - $this->seam->request( - "POST", - "/webhooks/update", - json: (object) $request_payload, - ); + $this->client->request("PUT", "/webhooks/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Routes/WorkspacesClient.php b/src/Routes/WorkspacesClient.php index 630f4755..ab1ce1ac 100644 --- a/src/Routes/WorkspacesClient.php +++ b/src/Routes/WorkspacesClient.php @@ -2,17 +2,29 @@ namespace Seam\Routes; +use GuzzleHttp\ClientInterface; +use Seam\Http\Body; +use Seam\Http\ResolveActionAttempt; +use Seam\NullValue; use Seam\Resources\ActionAttempt; use Seam\Resources\Workspace; -use Seam\SeamClient; class WorkspacesClient { - private SeamClient $seam; + private ClientInterface $client; - public function __construct(SeamClient $seam) + /** + * @var array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} + */ + private array $defaults; + + /** + * @param array{wait_for_action_attempt: bool|array{timeout?: float, polling_interval?: float}} $defaults + */ + public function __construct(ClientInterface $client, array $defaults) { - $this->seam = $seam; + $this->client = $client; + $this->defaults = $defaults; } /** @@ -20,7 +32,7 @@ public function __construct(SeamClient $seam) * * @param string $name Name of the new workspace. * @param string $company_name Company name for the new workspace. - * @param string $connect_partner_name Connect partner name for the new workspace. + * @param string|NullValue $connect_partner_name Connect partner name for the new workspace. * @param mixed $connect_webview_customization [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews) customizations for the new workspace. See also [Customize the Look and Feel of Your Connect Webviews](https://docs.seam.co/core-concepts/connect-webviews/customizing-connect-webviews#customize-the-look-and-feel-of-your-connect-webviews). * @param bool $is_sandbox Indicates whether the new workspace is a [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces). * @param string $organization_id ID of the organization to associate with the new workspace. @@ -33,7 +45,7 @@ public function __construct(SeamClient $seam) public function create( string $name, ?string $company_name = null, - ?string $connect_partner_name = null, + string|NullValue|null $connect_partner_name = null, mixed $connect_webview_customization = null, ?bool $is_sandbox = null, ?string $organization_id = null, @@ -44,9 +56,7 @@ public function create( ): Workspace { $request_payload = []; - if ($name !== null) { - $request_payload["name"] = $name; - } + $request_payload["name"] = $name; if ($company_name !== null) { $request_payload["company_name"] = $company_name; } @@ -83,13 +93,15 @@ public function create( ] = $webview_success_message; } - $res = $this->seam->request( - "POST", - "/workspaces/create", - json: (object) $request_payload, + $res = Body::decode( + $this->client->request("POST", "/workspaces/create", [ + "json" => (object) $request_payload, + ]), ); - return Workspace::from_json($res->workspace); + return Workspace::from_json( + Body::read($res, "workspace", "/workspaces/create"), + ); } /** @@ -99,9 +111,11 @@ public function create( */ public function get(): Workspace { - $res = $this->seam->request("POST", "/workspaces/get"); + $res = Body::decode($this->client->request("GET", "/workspaces/get")); - return Workspace::from_json($res->workspace); + return Workspace::from_json( + Body::read($res, "workspace", "/workspaces/get"), + ); } /** @@ -111,30 +125,35 @@ public function get(): Workspace */ public function list(): array { - $res = $this->seam->request("POST", "/workspaces/list"); + $res = Body::decode($this->client->request("GET", "/workspaces/list")); - return array_map(fn($r) => Workspace::from_json($r), $res->workspaces); + return array_map( + fn($r) => Workspace::from_json($r), + Body::read_list($res, "workspaces", "/workspaces/list"), + ); } /** * Resets the [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces) associated with the authentication value. Note that this endpoint is only available for sandbox workspaces. * + * @param bool|array|null $wait_for_action_attempt Whether to wait for the action attempt to finish, optionally with timeout and polling_interval in seconds. Defaults to the value set on the client. * @return ActionAttempt OK */ public function reset_sandbox( - bool $wait_for_action_attempt = true, + bool|array|null $wait_for_action_attempt = null, ): ActionAttempt { - $res = $this->seam->request("POST", "/workspaces/reset_sandbox"); - - if (!$wait_for_action_attempt) { - return ActionAttempt::from_json($res->action_attempt); - } - - $action_attempt = $this->seam->action_attempts->poll_until_ready( - $res->action_attempt->action_attempt_id, + $res = Body::decode( + $this->client->request("POST", "/workspaces/reset_sandbox"), ); - return $action_attempt; + return ResolveActionAttempt::resolve_action_attempt( + ActionAttempt::from_json( + Body::read($res, "action_attempt", "/workspaces/reset_sandbox"), + ), + $this->client, + $wait_for_action_attempt ?? + $this->defaults["wait_for_action_attempt"], + ); } /** @@ -181,10 +200,8 @@ public function update( $request_payload["organization_id"] = $organization_id; } - $this->seam->request( - "POST", - "/workspaces/update", - json: (object) $request_payload, - ); + $this->client->request("PATCH", "/workspaces/update", [ + "json" => (object) $request_payload, + ]); } } diff --git a/src/Seam.php b/src/Seam.php new file mode 100644 index 00000000..3f423eea --- /dev/null +++ b/src/Seam.php @@ -0,0 +1,258 @@ + $guzzle_options Options merged into the underlying Guzzle client, e.g. headers or proxy. + * @param int|null $retries How many times to retry a failed request. Defaults to 2; pass 0 to disable. + * @param float|null $timeout Request timeout in seconds, covering connecting and reading. Defaults to 30; pass 0 to disable. + * @param ClientInterface|null $client A preconfigured Guzzle client, used as is. It carries its own endpoint and authorization, so it cannot be combined with any option other than wait_for_action_attempt. + */ + public function __construct( + ?string $api_key = null, + ?string $personal_access_token = null, + ?string $workspace_id = null, + ?string $endpoint = null, + bool|array|null $wait_for_action_attempt = null, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null, + ?ClientInterface $client = null, + ) { + $this->defaults = [ + "wait_for_action_attempt" => $wait_for_action_attempt ?? true, + ]; + + // A client carries its own endpoint and authorization, so no option + // that would configure one can be combined with it. + Options::check_client_options($client, [ + "api_key" => $api_key, + "personal_access_token" => $personal_access_token, + "workspace_id" => $workspace_id, + "endpoint" => $endpoint, + "guzzle_options" => $guzzle_options, + "retries" => $retries, + "timeout" => $timeout, + ]); + + $this->client = SerializingClient::wrap( + $client ?? + ClientFactory::create( + Options::get_endpoint($endpoint), + Auth::get_auth_headers( + $api_key, + $personal_access_token, + $workspace_id, + ), + $guzzle_options, + $retries, + $timeout, + ), + ); + + $this->access_codes = new AccessCodesClient( + $this->client, + $this->defaults, + ); + $this->access_grants = new AccessGrantsClient( + $this->client, + $this->defaults, + ); + $this->access_methods = new AccessMethodsClient( + $this->client, + $this->defaults, + ); + $this->acs = new AcsClient($this->client, $this->defaults); + $this->action_attempts = new ActionAttemptsClient( + $this->client, + $this->defaults, + ); + $this->client_sessions = new ClientSessionsClient( + $this->client, + $this->defaults, + ); + $this->connect_webviews = new ConnectWebviewsClient( + $this->client, + $this->defaults, + ); + $this->connected_accounts = new ConnectedAccountsClient( + $this->client, + $this->defaults, + ); + $this->customers = new CustomersClient($this->client, $this->defaults); + $this->devices = new DevicesClient($this->client, $this->defaults); + $this->events = new EventsClient($this->client, $this->defaults); + $this->instant_keys = new InstantKeysClient( + $this->client, + $this->defaults, + ); + $this->locks = new LocksClient($this->client, $this->defaults); + $this->noise_sensors = new NoiseSensorsClient( + $this->client, + $this->defaults, + ); + $this->phones = new PhonesClient($this->client, $this->defaults); + $this->spaces = new SpacesClient($this->client, $this->defaults); + $this->thermostats = new ThermostatsClient( + $this->client, + $this->defaults, + ); + $this->user_identities = new UserIdentitiesClient( + $this->client, + $this->defaults, + ); + $this->webhooks = new WebhooksClient($this->client, $this->defaults); + $this->workspaces = new WorkspacesClient( + $this->client, + $this->defaults, + ); + } + + /** + * Creates a client authorized with an API key. + */ + public static function from_api_key( + string $api_key, + ?string $endpoint = null, + bool|array|null $wait_for_action_attempt = null, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null, + ): static { + return new static( + api_key: $api_key, + endpoint: $endpoint, + wait_for_action_attempt: $wait_for_action_attempt, + guzzle_options: $guzzle_options, + retries: $retries, + timeout: $timeout, + ); + } + + /** + * Creates a client authorized with a personal access token, scoped to the + * given workspace. + */ + public static function from_personal_access_token( + string $personal_access_token, + string $workspace_id, + ?string $endpoint = null, + bool|array|null $wait_for_action_attempt = null, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null, + ): static { + return new static( + personal_access_token: $personal_access_token, + workspace_id: $workspace_id, + endpoint: $endpoint, + wait_for_action_attempt: $wait_for_action_attempt, + retries: $retries, + timeout: $timeout, + guzzle_options: $guzzle_options, + ); + } + + /** + * Creates a client from a preconfigured Guzzle client. + */ + public static function from_client( + ClientInterface $client, + bool|array|null $wait_for_action_attempt = null, + ): static { + return new static( + client: $client, + wait_for_action_attempt: $wait_for_action_attempt, + ); + } + + /** + * Creates a paginator for a list endpoint. + * + * @param callable $request Invokes the list method with a params array, e.g. fn($params) => $seam->devices->list(...$params) + * @param array $params + */ + public function createPaginator( + callable $request, + array $params = [], + ): Paginator { + return new Paginator($request, $params); + } +} diff --git a/src/SeamClient.php b/src/SeamClient.php deleted file mode 100644 index f2384ab2..00000000 --- a/src/SeamClient.php +++ /dev/null @@ -1,155 +0,0 @@ -api_key = $api_key ?: (getenv("SEAM_API_KEY") ?: null); - $seam_sdk_version = PackageVersion::get(); - $this->client = new HTTPClient([ - "base_uri" => $endpoint, - "timeout" => 60.0, - "headers" => [ - "Authorization" => "Bearer " . $this->api_key, - "User-Agent" => "Seam PHP Client " . $seam_sdk_version, - "seam-sdk-name" => "seamapi/php", - "seam-sdk-version" => $seam_sdk_version, - "seam-lts-version" => $this->ltsVersion, - ], - "http_errors" => $throw_http_errors, - ]); - $this->access_codes = new AccessCodesClient($this); - $this->access_grants = new AccessGrantsClient($this); - $this->access_methods = new AccessMethodsClient($this); - $this->acs = new AcsClient($this); - $this->action_attempts = new ActionAttemptsClient($this); - $this->client_sessions = new ClientSessionsClient($this); - $this->connect_webviews = new ConnectWebviewsClient($this); - $this->connected_accounts = new ConnectedAccountsClient($this); - $this->customers = new CustomersClient($this); - $this->devices = new DevicesClient($this); - $this->events = new EventsClient($this); - $this->instant_keys = new InstantKeysClient($this); - $this->locks = new LocksClient($this); - $this->noise_sensors = new NoiseSensorsClient($this); - $this->phones = new PhonesClient($this); - $this->spaces = new SpacesClient($this); - $this->thermostats = new ThermostatsClient($this); - $this->user_identities = new UserIdentitiesClient($this); - $this->webhooks = new WebhooksClient($this); - $this->workspaces = new WorkspacesClient($this); - } - - public function request($method, $path, $json = null, $query = null) - { - $options = [ - "json" => $json, - "query" => $query, - ]; - $options = array_filter($options, fn($option) => $option !== null); - - $response = $this->client->request($method, $path, $options); - $status_code = $response->getStatusCode(); - $request_id = $response->getHeaderLine("seam-request-id"); - - $res_json = null; - try { - $res_json = json_decode($response->getBody()); - } catch (Exception $ignoreError) { - } - - if ($status_code >= 400) { - if ($status_code === 401) { - throw new HttpUnauthorizedError($request_id); - } - - if (($res_json->error ?? null) != null) { - if ($res_json->error->type === "invalid_input") { - throw new HttpInvalidInputError( - $res_json->error, - $status_code, - $request_id, - ); - } - - throw new HttpApiError( - $res_json->error, - $status_code, - $request_id, - ); - } - - throw \GuzzleHttp\Exception\RequestException::create( - new \GuzzleHttp\Psr7\Request($method, $path), - $response, - ); - } - - return $res_json; - } - - public function createPaginator($request, $params = []) - { - return new Paginator($request, $params); - } -} diff --git a/src/SeamException.php b/src/SeamException.php new file mode 100644 index 00000000..fc73fdcd --- /dev/null +++ b/src/SeamException.php @@ -0,0 +1,9 @@ +webhook = new Webhook($secret); + } + + /** + * Verifies an incoming webhook request and returns the event it carries. + * Known event types are returned as concrete Event subclasses for + * instanceof or match-based dispatch; newer types use Event itself. + * + * ```php + * match (true) { + * $event instanceof Event\AccessCodeCreated => $event->access_code_id, + * $event::class === Event::class => $event->event_type, + * default => null, + * }; + * ``` + * + * @param string $payload The raw HTTP request body. + * @param array $headers The HTTP request headers. + * + * @throws \Svix\Exception\WebhookVerificationException When the signature does not match. + * @throws InvalidWebhookPayloadError When the signature matches but the body is not a Seam event. + */ + public function verify(string $payload, array $headers): Event + { + $normalized_headers = []; + foreach ($headers as $name => $value) { + $normalized_headers[strtolower((string) $name)] = $value; + } + + $this->webhook->verify($payload, $normalized_headers); + + $decoded = json_decode($payload); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new InvalidWebhookPayloadError( + "The verified webhook payload is not valid JSON: " . + json_last_error_msg(), + ); + } + + $event = Event::from_json($decoded); + + if ($event === null || $event->event_id === null) { + throw new InvalidWebhookPayloadError( + "The verified webhook payload did not contain an event", + ); + } + + return $event; + } +} diff --git a/src/SeamWithoutWorkspace.php b/src/SeamWithoutWorkspace.php new file mode 100644 index 00000000..603ba22e --- /dev/null +++ b/src/SeamWithoutWorkspace.php @@ -0,0 +1,96 @@ + $guzzle_options + */ + public function __construct( + ?string $personal_access_token = null, + ?string $endpoint = null, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null, + ?ClientInterface $client = null, + ) { + // A client carries its own endpoint and authorization, so no option + // that would configure one can be combined with it. + Options::check_client_options($client, [ + "personal_access_token" => $personal_access_token, + "endpoint" => $endpoint, + "guzzle_options" => $guzzle_options, + "retries" => $retries, + "timeout" => $timeout, + ]); + + $this->client = SerializingClient::wrap( + $client ?? + ClientFactory::create( + Options::get_endpoint($endpoint), + Auth::get_auth_headers_without_workspace( + $personal_access_token, + ), + $guzzle_options, + $retries, + $timeout, + ), + ); + + $this->workspaces = new WorkspacesProxy( + new WorkspacesClient($this->client, [ + "wait_for_action_attempt" => false, + ]), + ); + } + + /** + * Creates a client authorized with a personal access token, not scoped to + * any workspace. + */ + public static function from_personal_access_token( + string $personal_access_token, + ?string $endpoint = null, + array $guzzle_options = [], + ?int $retries = null, + ?float $timeout = null, + ): static { + return new static( + personal_access_token: $personal_access_token, + endpoint: $endpoint, + guzzle_options: $guzzle_options, + retries: $retries, + timeout: $timeout, + ); + } + + /** + * Creates a client from a preconfigured Guzzle client. + */ + public static function from_client(ClientInterface $client): static + { + return new static(client: $client); + } +} diff --git a/src/StrictUrlSearchParamsSerializer.php b/src/StrictUrlSearchParamsSerializer.php new file mode 100644 index 00000000..a01be993 --- /dev/null +++ b/src/StrictUrlSearchParamsSerializer.php @@ -0,0 +1,52 @@ +|\stdClass $params + * + * @throws UnserializableParamError If any param could not be serialized + */ + public static function serialize(array|\stdClass $params): string + { + $search_params = new UrlSearchParams(); + self::update($search_params, $params); + + return $search_params->to_string(); + } + + /** + * Updates existing URL search params with serialized params and strict + * API validation enabled. + * + * @param array|\stdClass $params + * + * @throws UnserializableParamError If any param could not be serialized + */ + public static function update( + UrlSearchParams $search_params, + array|\stdClass $params, + ): void { + UrlSearchParamsSerializer::update($search_params, $params); + + if (count($search_params) > 0) { + $search_params->delete("_strict"); + $search_params->append("_strict", "true"); + } + } +} diff --git a/src/Token.php b/src/Token.php new file mode 100644 index 00000000..90349502 --- /dev/null +++ b/src/Token.php @@ -0,0 +1,67 @@ +name; + } +} diff --git a/src/UrlSearchParams.php b/src/UrlSearchParams.php new file mode 100644 index 00000000..4789f902 --- /dev/null +++ b/src/UrlSearchParams.php @@ -0,0 +1,288 @@ + + */ +class UrlSearchParams implements \Countable, \IteratorAggregate +{ + /** @var list */ + private array $pairs = []; + + /** + * @param string|array|list|null $init + * A query string, a map of names to values, or a list of + * name-value pairs + * + * @throws UnserializableParamError If a map value cannot be rendered + */ + public function __construct(string|array|null $init = null) + { + if ($init === null) { + return; + } + + if (is_string($init)) { + $query = str_starts_with($init, "?") ? substr($init, 1) : $init; + + foreach (explode("&", $query) as $pair) { + if ($pair === "") { + continue; + } + + $parts = explode("=", $pair, 2); + $this->pairs[] = [ + urldecode($parts[0]), + urldecode($parts[1] ?? ""), + ]; + } + + return; + } + + foreach ($init as $name => $value) { + if (is_array($value) && is_int($name)) { + $this->pairs[] = [(string) $value[0], (string) $value[1]]; + continue; + } + + if (is_array($value)) { + foreach ($value as $element) { + $this->pairs[] = [ + (string) $name, + self::stringify((string) $name, $element), + ]; + } + continue; + } + + $this->pairs[] = [ + (string) $name, + self::stringify((string) $name, $value), + ]; + } + } + + /** + * Renders a map value as a search param value. + * + * @throws UnserializableParamError If the value has no string form here + */ + private static function stringify(string $name, mixed $value): string + { + if ($value === null || $value instanceof NullValue) { + return ""; + } + + if (is_string($value)) { + return $value; + } + + if (is_bool($value)) { + return $value ? "true" : "false"; + } + + if (is_int($value)) { + return (string) $value; + } + + throw new UnserializableParamError( + $name, + "is a " . + get_debug_type($value) . + ", which UrlSearchParams cannot render; serialize it with UrlSearchParamsSerializer first", + ); + } + + /** + * Appends a name-value pair, keeping any existing pairs with this name. + */ + public function append(string $name, string $value): void + { + $this->pairs[] = [$name, $value]; + } + + /** + * Sets the value associated with a name. + * + * Replaces the first pair with this name and removes any others, so the + * pair keeps its position. Appends a new pair if no pair with this name + * exists. + */ + public function set(string $name, string $value): void + { + if (!$this->has($name)) { + $this->append($name, $value); + return; + } + + $pairs = []; + $is_set = false; + + foreach ($this->pairs as $pair) { + if ($pair[0] !== $name) { + $pairs[] = $pair; + } elseif (!$is_set) { + $pairs[] = [$name, $value]; + $is_set = true; + } + } + + $this->pairs = $pairs; + } + + /** + * Returns the value of the first pair with this name, or null if no pair + * with this name exists. + */ + public function get(string $name): ?string + { + foreach ($this->pairs as [$existing_name, $value]) { + if ($existing_name === $name) { + return $value; + } + } + + return null; + } + + /** + * Returns the values of all pairs with this name, in insertion order. + * + * @return list + */ + public function get_all(string $name): array + { + $values = []; + + foreach ($this->pairs as [$existing_name, $value]) { + if ($existing_name === $name) { + $values[] = $value; + } + } + + return $values; + } + + /** + * Returns whether a pair with this name exists. + */ + public function has(string $name): bool + { + foreach ($this->pairs as [$existing_name, $_]) { + if ($existing_name === $name) { + return true; + } + } + + return false; + } + + /** + * Removes all pairs with this name. + */ + public function delete(string $name): void + { + $this->pairs = array_values( + array_filter($this->pairs, fn(array $pair) => $pair[0] !== $name), + ); + } + + /** + * Sorts all pairs by name, comparing bytes. + * + * Sorting is stable, so the relative order of pairs with the same name + * is preserved, which is what keeps array element order. Byte order + * matches URLSearchParams.sort() for ASCII names; a name beyond the + * Basic Multilingual Plane may sort differently than in JavaScript. + */ + public function sort(): void + { + usort($this->pairs, fn(array $a, array $b) => strcmp($a[0], $b[0])); + } + + /** + * Serializes all pairs to a query string, without a leading `?`. + * + * Every pair gets an `=`, including empty values, e.g. `name=`. + */ + public function to_string(): string + { + return implode( + "&", + array_map( + fn(array $pair) => self::encode_form_component($pair[0]) . + "=" . + self::encode_form_component($pair[1]), + $this->pairs, + ), + ); + } + + public function __toString(): string + { + return $this->to_string(); + } + + #[\Override] + public function count(): int + { + return count($this->pairs); + } + + /** + * @return \ArrayIterator, array{string, string}> + */ + #[\Override] + public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->pairs); + } + + /** + * Percent-encodes a string with the WHATWG + * application/x-www-form-urlencoded serializer, applied to the UTF-8 + * bytes of the string. + * + * The safe set is not the RFC 3986 unreserved set, so neither urlencode + * nor rawurlencode produces it: `*` is emitted literally and `~` is + * escaped, the exact opposite of both. + */ + private static function encode_form_component(string $value): string + { + $encoded = ""; + $length = strlen($value); + + for ($i = 0; $i < $length; $i++) { + $character = $value[$i]; + $byte = ord($character); + + $is_safe = + ($byte >= 0x30 && $byte <= 0x39) || + ($byte >= 0x41 && $byte <= 0x5a) || + ($byte >= 0x61 && $byte <= 0x7a) || + $character === "*" || + $character === "-" || + $character === "." || + $character === "_"; + + if ($is_safe) { + $encoded .= $character; + } elseif ($character === " ") { + $encoded .= "+"; + } else { + $encoded .= sprintf("%%%02X", $byte); + } + } + + return $encoded; + } +} diff --git a/src/UrlSearchParamsSerializer.php b/src/UrlSearchParamsSerializer.php new file mode 100644 index 00000000..592c20d2 --- /dev/null +++ b/src/UrlSearchParamsSerializer.php @@ -0,0 +1,364 @@ +|\stdClass $params + * + * @throws UnserializableParamError If any param could not be serialized + */ + public static function serialize(array|\stdClass $params): string + { + $search_params = new UrlSearchParams(); + self::update($search_params, $params); + + return $search_params->to_string(); + } + + /** + * Updates existing URL search params with serialized params. + * + * Existing params are preserved unless overwritten by a serialized + * param. All params are sorted by name. + * + * @param array|\stdClass $params + * + * @throws UnserializableParamError If any param could not be serialized + */ + public static function update( + UrlSearchParams $search_params, + array|\stdClass $params, + ): void { + self::nested_update($search_params, $params, []); + $search_params->sort(); + } + + /** + * @param array|\stdClass $params + * @param list $path + */ + private static function nested_update( + UrlSearchParams $search_params, + array|\stdClass $params, + array $path, + ): void { + $entries = + $params instanceof \stdClass ? get_object_vars($params) : $params; + + foreach ($entries as $key => $value) { + // PHP silently casts a numeric-string key to an integer, so a + // non-string key cannot be told apart from one; both are + // rejected rather than serialized ambiguously. + if (!is_string($key)) { + throw new UnserializableParamError( + (string) $key, + "is a " . + get_debug_type($key) . + " which is unsupported as a parameter name", + ); + } + + if (str_contains($key, ".")) { + throw new UnserializableParamError( + $key, + 'contains one or more dots "." in its name which is unsupported', + ); + } + + $current_path = [...$path, $key]; + + if (self::is_plain_object($value)) { + /** @var array|\stdClass $value */ + self::nested_update($search_params, $value, $current_path); + continue; + } + + $name = implode(".", $current_path); + + if ($value === null) { + continue; + } + + if ($value === "") { + continue; + } + + if (is_array($value)) { + self::update_from_array($search_params, $name, $value); + continue; + } + + $search_params->set($name, self::serialize_value($name, $value)); + } + } + + /** + * An array is a plain object when its keys are not the sequential + * integers of a list. The empty array is a list: it is the empty + * JavaScript Array, not an empty plain object. + */ + private static function is_plain_object(mixed $value): bool + { + if ($value instanceof \stdClass) { + return true; + } + + return is_array($value) && $value !== [] && !array_is_list($value); + } + + /** + * @param list $values + */ + private static function update_from_array( + UrlSearchParams $search_params, + string $name, + array $values, + ): void { + if ($values === []) { + // The one case where an empty value is meaningful: the parser + // reads `name=` as the empty array. + $search_params->set($name, ""); + return; + } + + if (count($values) === 1 && $values[0] === "") { + throw new UnserializableParamError( + $name, + "is a single element array containing the empty string which is unsupported", + ); + } + + if (in_array("", $values, true)) { + throw new UnserializableParamError( + $name, + "is an array containing the empty string which is unsupported", + ); + } + + foreach ($values as $value) { + if ($value === null || $value instanceof NullValue) { + throw new UnserializableParamError( + $name, + "is an array containing null or undefined values which is unsupported", + ); + } + } + + foreach ($values as $value) { + $search_params->append($name, self::serialize_value($name, $value)); + } + } + + /** + * @throws UnserializableParamError If the value could not be serialized + */ + private static function serialize_value(string $name, mixed $value): string + { + if ($value instanceof NullValue) { + return ""; + } + + if (is_string($value)) { + return $value; + } + + if (is_bool($value)) { + return $value ? "true" : "false"; + } + + if (is_int($value)) { + return (string) $value; + } + + if (is_float($value)) { + return self::format_number($name, $value); + } + + if ($value instanceof \DateTimeInterface) { + return self::format_datetime($value); + } + + throw new UnserializableParamError( + $name, + "is a " . get_debug_type($value), + ); + } + + /** + * Formats an instant as JavaScript's Date.prototype.toISOString does: + * always UTC, always exactly three fractional digits, always a literal + * `Z`. Sub-millisecond precision is truncated, not rounded. + */ + private static function format_datetime(\DateTimeInterface $value): string + { + $utc = \DateTimeImmutable::createFromInterface($value)->setTimezone( + new \DateTimeZone("UTC"), + ); + $milliseconds = intdiv((int) $utc->format("u"), 1000); + + return sprintf( + "%s-%s.%03dZ", + self::format_year((int) $utc->format("Y")), + $utc->format("m-d\TH:i:s"), + $milliseconds, + ); + } + + /** + * Renders the year of a date time string: four digits in the ordinary + * range, and otherwise the expanded year, which is always signed and + * always six digits. + */ + private static function format_year(int $year): string + { + if ($year >= 0 && $year <= 9999) { + return sprintf("%04d", $year); + } + + return sprintf("%s%06d", $year < 0 ? "-" : "+", abs($year)); + } + + /** + * Formats a float with the ECMAScript Number::toString algorithm. + * + * PHP's own float formatting differs from it in several ways: an + * integral float renders as `1.0` rather than `1`, the exponent + * threshold is not at 1e21 and 1e-7, and exponents are spelled `E+21` + * rather than `e+21`. + */ + private static function format_number(string $name, float $value): string + { + if (is_nan($value)) { + throw new UnserializableParamError($name, "is NaN"); + } + + if (is_infinite($value)) { + throw new UnserializableParamError( + $name, + $value > 0 ? "is Infinity" : "is -Infinity", + ); + } + + if ($value === 0.0) { + return "0"; + } + + $sign = $value < 0 ? "-" : ""; + [$digits, $point] = self::shortest_digits(abs($value)); + + return $sign . self::format_digits($digits, $point); + } + + /** + * Returns the shortest digit string that round-trips to the float, with + * the position of the decimal point relative to those digits, as the + * ECMAScript Number::toString algorithm requires. + * + * @return array{string, int} + */ + private static function shortest_digits(float $value): array + { + $repr = sprintf("%.16E", $value); + + for ($precision = 0; $precision < 16; $precision++) { + $candidate = sprintf("%.{$precision}E", $value); + + if ((float) $candidate === $value) { + $repr = $candidate; + break; + } + } + + if ( + preg_match('/^(\d)(?:\.(\d+))?E([+-]\d+)$/i', $repr, $matches) !== 1 + ) { + throw new \RuntimeException( + "Could not parse the PHP float representation: {$repr}", + ); + } + + // %E normalizes to one digit before the point, so the exponent + // places the point directly. + $digits = $matches[1] . ($matches[2] ?? ""); + $point = (int) $matches[3] + 1; + + $stripped = ltrim($digits, "0"); + $point -= strlen($digits) - strlen($stripped); + $digits = rtrim($stripped, "0"); + + return [$digits, $point]; + } + + /** + * Formats digits and a decimal point position per ECMAScript + * Number::toString. The four branches and the constants 21 and -6 are + * the specification. + * + * @param string $digits Significant digits, without trailing zeros + * @param int $point Position of the decimal point relative to the digits + */ + private static function format_digits(string $digits, int $point): string + { + $count = strlen($digits); + + if ($count <= $point && $point <= 21) { + return $digits . str_repeat("0", $point - $count); + } + + if (0 < $point && $point <= 21) { + return substr($digits, 0, $point) . "." . substr($digits, $point); + } + + if (-6 < $point && $point <= 0) { + return "0." . str_repeat("0", -$point) . $digits; + } + + $exponent = $point - 1; + $exponent_sign = $exponent >= 0 ? "+" : "-"; + $mantissa = + $count === 1 ? $digits : $digits[0] . "." . substr($digits, 1); + + return $mantissa . "e" . $exponent_sign . abs($exponent); + } +} diff --git a/src/Utils/PackageVersion.php b/src/Version.php similarity index 78% rename from src/Utils/PackageVersion.php rename to src/Version.php index 66fbf871..d486de52 100644 --- a/src/Utils/PackageVersion.php +++ b/src/Version.php @@ -1,8 +1,8 @@ workspaces = $workspaces; + } + + /** + * @return Workspace[] + */ + public function list(): array + { + return $this->workspaces->list(); + } + + /** + * Creates a new [workspace](https://docs.seam.co/core-concepts/workspaces). + * + * @param string $name Name of the new workspace. + * @param string $company_name Company name for the new workspace. + * @param string|NullValue $connect_partner_name Connect partner name for the new workspace. + * @param mixed $connect_webview_customization [Connect Webview](https://docs.seam.co/core-concepts/connect-webviews) customizations for the new workspace. + * @param bool $is_sandbox Indicates whether the new workspace is a [sandbox workspace](https://docs.seam.co/core-concepts/workspaces#sandbox-workspaces). + * @param string $organization_id ID of the organization to associate with the new workspace. + * @return Workspace OK + */ + public function create( + string $name, + ?string $company_name = null, + string|NullValue|null $connect_partner_name = null, + mixed $connect_webview_customization = null, + ?bool $is_sandbox = null, + ?string $organization_id = null, + ?string $webview_logo_shape = null, + ?string $webview_primary_button_color = null, + ?string $webview_primary_button_text_color = null, + ?string $webview_success_message = null, + ): Workspace { + // Forwarded by name: the generated parameter order follows the API + // definition, so a positional call could silently shift after a + // regeneration. + return $this->workspaces->create( + name: $name, + company_name: $company_name, + connect_partner_name: $connect_partner_name, + connect_webview_customization: $connect_webview_customization, + is_sandbox: $is_sandbox, + organization_id: $organization_id, + webview_logo_shape: $webview_logo_shape, + webview_primary_button_color: $webview_primary_button_color, + webview_primary_button_text_color: $webview_primary_button_text_color, + webview_success_message: $webview_success_message, + ); + } +} diff --git a/tests/ApiKeyTest.php b/tests/ApiKeyTest.php new file mode 100644 index 00000000..838fa24a --- /dev/null +++ b/tests/ApiKeyTest.php @@ -0,0 +1,89 @@ +seed["seam_apikey1_token"], + endpoint: $this->endpoint, + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + $this->assertSame( + $this->seed["seed_workspace_1"], + $device->workspace_id, + ); + } + + public function testConstructorReturnsAnAuthorizedClient(): void + { + $seam = new Seam( + api_key: $this->seed["seam_apikey1_token"], + endpoint: $this->endpoint, + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testInvalidApiKeyIsRejectedByTheServer(): void + { + $seam = new Seam( + api_key: "seam_invalid_api_key", + endpoint: $this->endpoint, + ); + + $this->expectException(HttpUnauthorizedError::class); + + $seam->devices->list(); + } + + /** + * @dataProvider unusableTokens + */ + public function testApiKeyFormatIsChecked( + string $token, + string $expected_message, + ): void { + $this->expectException(InvalidTokenError::class); + $this->expectExceptionMessage($expected_message); + + new Seam(api_key: $token, endpoint: $this->endpoint); + } + + public static function unusableTokens(): array + { + return [ + "client session token" => [ + "seam_cst_1234", + "A Client Session Token cannot be used as an api_key", + ], + "jwt" => ["ey_some_jwt", "A JWT cannot be used as an api_key"], + "access token" => [ + "seam_at_1234", + "An Access Token cannot be used as an api_key", + ], + "publishable key" => [ + "seam_pk_1234", + "A Publishable Key cannot be used as an api_key", + ], + "unknown format" => [ + "some-random-token", + "Unknown or invalid api_key format", + ], + ]; + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php new file mode 100644 index 00000000..0f6c5feb --- /dev/null +++ b/tests/ClientTest.php @@ -0,0 +1,352 @@ +seam(); + + $res = Body::decode( + $seam->client->request("POST", "/devices/get", [ + "json" => (object) [ + "device_id" => $this->seed["august_device_1"], + ], + ]), + ); + + $this->assertSame( + $this->seed["august_device_1"], + $res->device->device_id, + ); + $this->assertSame( + $this->seed["seed_workspace_1"], + $res->device->workspace_id, + ); + } + + public function testClientIsTheGuzzleClient(): void + { + $this->assertInstanceOf( + \GuzzleHttp\ClientInterface::class, + $this->seam()->client, + ); + } + + /** + * A client carries its own endpoint and authorization, so it has to work + * on its own without any authentication option beside it. + */ + public function testFromClientNeedsNoCredentials(): void + { + $authorized = $this->seam(); + + $seam = Seam::from_client($authorized->client); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + $this->assertSame( + $this->seed["seed_workspace_1"], + $device->workspace_id, + ); + } + + private function foreign_client(array $options = []): \GuzzleHttp\Client + { + return new \GuzzleHttp\Client( + array_merge( + [ + "base_uri" => $this->endpoint, + "headers" => [ + "authorization" => + "Bearer " . $this->seed["seam_apikey1_token"], + ], + ], + $options, + ), + ); + } + + public function testAnInjectedClientIsUsedAsGiven(): void + { + $seam = Seam::from_client($this->foreign_client()); + + $device = $seam->devices->get($this->seed["august_device_1"]); + $this->assertSame($this->seed["august_device_1"], $device->device_id); + + $this->expectException(\GuzzleHttp\Exception\ClientException::class); + + $seam->devices->get("nonexistent-device-id"); + } + + public function testAddMiddlewareGivesAnInjectedClientSeamErrors(): void + { + $handler = \GuzzleHttp\HandlerStack::create(); + ClientFactory::add_middleware($handler); + + $seam = Seam::from_client( + $this->foreign_client([ + "handler" => $handler, + "http_errors" => false, + ]), + ); + + $this->expectException(HttpApiError::class); + + $seam->devices->get("nonexistent-device-id"); + } + + public function testAddMiddlewareGivesAnInjectedClientRetries(): void + { + $recorder = RecordingClient::repeating( + RecordingClient::json(503, [ + "error" => [ + "type" => "unavailable", + "message" => "Service Unavailable", + ], + ]), + times: 5, + ); + + $handler = \GuzzleHttp\HandlerStack::create( + $recorder->guzzle_options()["handler"], + ); + ClientFactory::add_middleware($handler); + + $seam = Seam::from_client( + $this->foreign_client([ + "handler" => $handler, + "http_errors" => false, + ]), + ); + + try { + $seam->devices->get("d1"); + $this->fail("Expected an HttpApiError"); + } catch (HttpApiError) { + $this->assertSame(3, $recorder->attempt_count()); + } + } + + public function testAddMiddlewareHonoursARetryCount(): void + { + $recorder = RecordingClient::repeating( + RecordingClient::json(503, [ + "error" => [ + "type" => "unavailable", + "message" => "Service Unavailable", + ], + ]), + times: 5, + ); + + $handler = \GuzzleHttp\HandlerStack::create( + $recorder->guzzle_options()["handler"], + ); + ClientFactory::add_middleware($handler, retries: 0); + + $seam = Seam::from_client( + $this->foreign_client([ + "handler" => $handler, + "http_errors" => false, + ]), + ); + + try { + $seam->devices->get("d1"); + $this->fail("Expected an HttpApiError"); + } catch (HttpApiError) { + $this->assertSame(1, $recorder->attempt_count()); + } + } + + public function testClientOptionReusesAnotherInstancesClient(): void + { + $seam = new Seam(client: $this->seam()->client); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + /** + * A credential passed beside a client would be silently discarded, since + * the client carries its own authorization, so the mix-up is rejected. + */ + public function testClientOptionRejectsAnyOtherOption(): void + { + $client = $this->seam()->client; + + $this->expectException(InvalidOptionsError::class); + $this->expectExceptionMessage( + "The api_key option cannot be used with the client option", + ); + + new Seam(api_key: $this->seed["seam_apikey1_token"], client: $client); + } + + /** + * wait_for_action_attempt does not configure the client, so it is the + * one option a client can be combined with. + */ + public function testClientOptionStillTakesAWaitForActionAttemptDefault(): void + { + $seam = new Seam( + client: $this->seam()->client, + wait_for_action_attempt: false, + ); + + $this->assertFalse($seam->defaults["wait_for_action_attempt"]); + } + + public function testWithoutWorkspaceClientOptionRejectsAnyOtherOption(): void + { + $client = $this->seam()->client; + + $this->expectException(InvalidOptionsError::class); + $this->expectExceptionMessage( + "The endpoint option cannot be used with the client option", + ); + + new SeamWithoutWorkspace(endpoint: $this->endpoint, client: $client); + } + + /** + * A bare handler, such as a MockHandler, is wrapped in a handler stack + * so the error mapping and retries still apply to it. + */ + public function testABareHandlerStillGetsTheErrorMapping(): void + { + $mock = new MockHandler([ + RecordingClient::json(404, [ + "error" => [ + "type" => "device_not_found", + "message" => "Device not found", + ], + ]), + ]); + + $seam = new Seam( + api_key: $this->seed["seam_apikey1_token"], + endpoint: "https://example.com", + guzzle_options: ["handler" => $mock], + ); + + try { + $seam->devices->list(); + $this->fail("Expected the error to be mapped"); + } catch (HttpApiError $error) { + $this->assertSame("device_not_found", $error->getErrorCode()); + } + } + + public function testGuzzleOptionsAreMergedIntoTheClient(): void + { + $seam = $this->seam( + guzzle_options: [ + "headers" => ["Custom-Header" => "Test-Value"], + "timeout" => 30, + ], + ); + + $config = $seam->client->getConfig(); + + $this->assertSame(30, $config["timeout"]); + $this->assertSame("Test-Value", $config["headers"]["Custom-Header"]); + // The custom headers must not displace the authorization or the SDK + // headers. + $this->assertSame( + "Bearer " . $this->seed["seam_apikey1_token"], + $config["headers"]["authorization"], + ); + $this->assertSame("seamapi/php", $config["headers"]["seam-sdk-name"]); + } + + public function testGuzzleOptionsStillAuthorizeRequests(): void + { + $seam = $this->seam( + guzzle_options: ["headers" => ["Custom-Header" => "Test-Value"]], + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testTimeoutDefaultsToThirtySeconds(): void + { + $config = $this->seam()->client->getConfig(); + + $this->assertSame(30.0, ClientFactory::DEFAULT_TIMEOUT); + $this->assertSame(30.0, $config["timeout"]); + $this->assertSame(30.0, $config["connect_timeout"]); + } + + public function testTimeoutCanBeSet(): void + { + $seam = Seam::from_api_key( + $this->seed["seam_apikey1_token"], + endpoint: $this->endpoint, + timeout: 5.0, + ); + + $config = $seam->client->getConfig(); + + $this->assertSame(5.0, $config["timeout"]); + $this->assertSame(5.0, $config["connect_timeout"]); + } + + /** + * Every constructor and factory has to agree on the default, otherwise + * the same call waits or does not depending on how the client was built. + */ + public function testWaitForActionAttemptDefaultsToTrueEverywhere(): void + { + $api_key = $this->seed["seam_apikey1_token"]; + + $clients = [ + "constructor" => new Seam( + api_key: $api_key, + endpoint: $this->endpoint, + ), + "from_api_key" => Seam::from_api_key( + $api_key, + endpoint: $this->endpoint, + ), + "from_personal_access_token" => Seam::from_personal_access_token( + $this->seed["seam_at1_token"], + $this->seed["seed_workspace_1"], + endpoint: $this->endpoint, + ), + "from_client" => Seam::from_client($this->seam()->client), + ]; + + foreach ($clients as $name => $seam) { + $this->assertTrue( + $seam->defaults["wait_for_action_attempt"], + "{$name} should wait for action attempts by default", + ); + } + } + + public function testWaitForActionAttemptDefaultCanBeDisabled(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + + $this->assertFalse($seam->defaults["wait_for_action_attempt"]); + } +} diff --git a/tests/EnvTest.php b/tests/EnvTest.php new file mode 100644 index 00000000..02b25763 --- /dev/null +++ b/tests/EnvTest.php @@ -0,0 +1,198 @@ + */ + private array $saved_env = []; + + protected function setUp(): void + { + parent::setUp(); + + foreach (self::VARIABLES as $name) { + $this->saved_env[$name] = getenv($name); + putenv($name); + } + } + + protected function tearDown(): void + { + foreach ($this->saved_env as $name => $value) { + if ($value === false) { + putenv($name); + } else { + putenv("{$name}={$value}"); + } + } + + parent::tearDown(); + } + + public function testReadsTheApiKeyFromTheEnvironment(): void + { + putenv("SEAM_API_KEY=" . $this->seed["seam_apikey1_token"]); + + $seam = new Seam(endpoint: $this->endpoint); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testReadsTheEndpointFromTheEnvironment(): void + { + putenv("SEAM_ENDPOINT=" . $this->endpoint); + + $seam = new Seam(api_key: $this->seed["seam_apikey1_token"]); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testFallsBackToTheDefaultEndpoint(): void + { + $this->assertSame(Options::DEFAULT_ENDPOINT, Options::get_endpoint()); + } + + public function testEndpointOptionWinsOverTheEnvironment(): void + { + putenv("SEAM_ENDPOINT=https://from-the-environment.example.com"); + + $this->assertSame( + "https://from-the-option.example.com", + Options::get_endpoint("https://from-the-option.example.com"), + ); + } + + public function testReadsThePersonalAccessTokenAndWorkspaceIdFromTheEnvironment(): void + { + putenv("SEAM_PERSONAL_ACCESS_TOKEN=" . $this->seed["seam_at1_token"]); + putenv("SEAM_WORKSPACE_ID=" . $this->seed["seed_workspace_1"]); + + $seam = new Seam(endpoint: $this->endpoint); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testReadsOnlyTheWorkspaceIdFromTheEnvironment(): void + { + putenv("SEAM_WORKSPACE_ID=" . $this->seed["seed_workspace_1"]); + + $seam = new Seam( + personal_access_token: $this->seed["seam_at1_token"], + endpoint: $this->endpoint, + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testWorkspaceIdOptionWinsOverTheEnvironment(): void + { + putenv("SEAM_WORKSPACE_ID=workspace-from-the-environment"); + + $seam = new Seam( + personal_access_token: $this->seed["seam_at1_token"], + workspace_id: $this->seed["seed_workspace_1"], + endpoint: $this->endpoint, + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + /** + * Two credentials in the environment are ambiguous, so neither is picked. + */ + public function testFailsWhenBothCredentialEnvironmentVariablesAreSet(): void + { + putenv("SEAM_API_KEY=" . $this->seed["seam_apikey1_token"]); + putenv("SEAM_PERSONAL_ACCESS_TOKEN=" . $this->seed["seam_at1_token"]); + putenv("SEAM_WORKSPACE_ID=" . $this->seed["seed_workspace_1"]); + + $this->expectException(InvalidOptionsError::class); + $this->expectExceptionMessage( + "Both SEAM_API_KEY and SEAM_PERSONAL_ACCESS_TOKEN", + ); + + new Seam(endpoint: $this->endpoint); + } + + public function testFailsWhenNoCredentialsAreAvailable(): void + { + $this->expectException(InvalidOptionsError::class); + $this->expectExceptionMessage("SEAM_API_KEY"); + + new Seam(endpoint: $this->endpoint); + } + + public function testApiKeyEnvironmentVariableIsIgnoredForAPersonalAccessToken(): void + { + putenv("SEAM_API_KEY=" . $this->seed["seam_apikey1_token"]); + + $seam = new Seam( + personal_access_token: $this->seed["seam_at1_token"], + workspace_id: $this->seed["seed_workspace_1"], + endpoint: $this->endpoint, + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testPersonalAccessTokenEnvironmentVariableIsIgnoredForAnApiKey(): void + { + putenv("SEAM_PERSONAL_ACCESS_TOKEN=" . $this->seed["seam_at1_token"]); + + $seam = new Seam( + api_key: $this->seed["seam_apikey1_token"], + endpoint: $this->endpoint, + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testWithoutWorkspaceReadsThePersonalAccessTokenFromTheEnvironment(): void + { + putenv("SEAM_PERSONAL_ACCESS_TOKEN=" . $this->seed["seam_at1_token"]); + + $seam = new SeamWithoutWorkspace(endpoint: $this->endpoint); + + $workspaces = $seam->workspaces->list(); + + $this->assertNotEmpty($workspaces); + } + + public function testWithoutWorkspaceFailsWhenNoTokenIsAvailable(): void + { + $this->expectException(InvalidOptionsError::class); + $this->expectExceptionMessage("SEAM_PERSONAL_ACCESS_TOKEN is not set"); + + new SeamWithoutWorkspace(endpoint: $this->endpoint); + } +} diff --git a/tests/HeadersTest.php b/tests/HeadersTest.php new file mode 100644 index 00000000..35eb3632 --- /dev/null +++ b/tests/HeadersTest.php @@ -0,0 +1,161 @@ + ["device_id" => "d1"]]), + ]); + + $seam = Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: $recorder->guzzle_options(), + ); + + $device = $seam->devices->get("d1"); + + $this->assertSame("d1", $device->device_id); + + $request = $recorder->request(); + + $this->assertSame("/devices/get", $request->getUri()->getPath()); + $this->assertSame( + "device_id=d1&_strict=true", + $request->getUri()->getQuery(), + ); + + $this->assertSame( + "Bearer seam_apikey_token", + $request->getHeaderLine("authorization"), + ); + $this->assertSame( + "seamapi/php", + $request->getHeaderLine("seam-sdk-name"), + ); + $this->assertSame( + Version::get(), + $request->getHeaderLine("seam-sdk-version"), + ); + $this->assertStringStartsWith( + "GuzzleHttp/", + $request->getHeaderLine("User-Agent"), + ); + } + + public function testSendsTheCallerUserAgentUnchanged(): void + { + $recorder = new RecordingClient([ + RecordingClient::json(200, ["device" => ["device_id" => "d1"]]), + ]); + + $seam = Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: array_merge($recorder->guzzle_options(), [ + "headers" => ["User-Agent" => "acme-app/1.0"], + ]), + ); + + $seam->devices->get("d1"); + + $this->assertSame( + "acme-app/1.0", + $recorder->request()->getHeaderLine("User-Agent"), + ); + } + + public function testSendsWorkspaceHeaderWithAPersonalAccessToken(): void + { + $recorder = new RecordingClient([ + RecordingClient::json(200, ["device" => ["device_id" => "d1"]]), + ]); + + $seam = Seam::from_personal_access_token( + "seam_at_token", + "workspace-1", + endpoint: "https://example.com", + guzzle_options: $recorder->guzzle_options(), + ); + + $seam->devices->get("d1"); + + $request = $recorder->request(); + + $this->assertSame( + "Bearer seam_at_token", + $request->getHeaderLine("authorization"), + ); + $this->assertSame( + "workspace-1", + $request->getHeaderLine("seam-workspace"), + ); + } + + public function testCustomHeadersAreSentAlongsideTheSdkHeaders(): void + { + $recorder = new RecordingClient([ + RecordingClient::json(200, ["device" => ["device_id" => "d1"]]), + ]); + + $seam = Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: array_merge($recorder->guzzle_options(), [ + "headers" => ["Custom-Header" => "Test-Value"], + ]), + ); + + $seam->devices->get("d1"); + + $request = $recorder->request(); + + $this->assertSame( + "Test-Value", + $request->getHeaderLine("Custom-Header"), + ); + $this->assertSame( + "seamapi/php", + $request->getHeaderLine("seam-sdk-name"), + ); + } + + /** + * The SDK headers identify the SDK, so a caller cannot displace them. + */ + public function testSdkHeadersCannotBeOverridden(): void + { + $recorder = new RecordingClient([ + RecordingClient::json(200, ["device" => ["device_id" => "d1"]]), + ]); + + $seam = Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: array_merge($recorder->guzzle_options(), [ + "headers" => ["seam-sdk-name" => "not-the-sdk"], + ]), + ); + + $seam->devices->get("d1"); + + $this->assertSame( + "seamapi/php", + $recorder->request()->getHeaderLine("seam-sdk-name"), + ); + } +} diff --git a/tests/HttpErrorTest.php b/tests/HttpErrorTest.php index 648eb882..25af1c50 100644 --- a/tests/HttpErrorTest.php +++ b/tests/HttpErrorTest.php @@ -2,29 +2,108 @@ declare(strict_types=1); -use PHPUnit\Framework\TestCase; +namespace Tests; -final class HttpErrorTest extends TestCase +use Seam\HttpApiError; +use Seam\HttpInvalidInputError; +use Seam\HttpUnauthorizedError; +use Seam\Seam; +use Tests\Support\FakeSeamConnectTestCase; + +final class HttpErrorTest extends FakeSeamConnectTestCase { - public function testNonSeamError(): void + public function testThrowsUnauthorizedError(): void { - $seam = new \Seam\SeamClient( - "seam_apikey1_token", - "https://nonexistent.example.com", + $seam = new Seam( + api_key: "seam_invalid_api_key", + endpoint: $this->endpoint, ); try { $seam->devices->list(); - $this->fail("Expected GuzzleHttp ConnectException"); - } catch (\GuzzleHttp\Exception\ConnectException $e) { - $this->assertInstanceOf( - \GuzzleHttp\Exception\ConnectException::class, - $e, + $this->fail("Expected HttpUnauthorizedError"); + } catch (HttpUnauthorizedError $error) { + $this->assertSame(401, $error->getStatusCode()); + $this->assertSame("unauthorized", $error->getErrorCode()); + $this->assertStringStartsWith( + "request", + (string) $error->getRequestId(), + ); + } + } + + public function testThrowsApiErrorOnStandardErrorResponse(): void + { + try { + $this->seam()->devices->get("unknown-device"); + $this->fail("Expected HttpApiError"); + } catch (HttpApiError $error) { + $this->assertSame(404, $error->getStatusCode()); + $this->assertSame("device_not_found", $error->getErrorCode()); + $this->assertStringStartsWith( + "request", + (string) $error->getRequestId(), ); - $this->assertStringContainsString( - "Could not resolve host", - $e->getMessage(), + } + } + + public function testThrowsInvalidInputErrorWithValidationMessages(): void + { + try { + $this->seam()->client->request("POST", "/devices/list", [ + "json" => (object) ["device_ids" => 4242], + ]); + $this->fail("Expected HttpInvalidInputError"); + } catch (HttpInvalidInputError $error) { + $this->assertSame(400, $error->getStatusCode()); + $this->assertSame("invalid_input", $error->getErrorCode()); + $this->assertStringStartsWith( + "request", + (string) $error->getRequestId(), + ); + $this->assertSame( + ["Expected array, received number"], + $error->getValidationErrorMessages("device_ids"), ); } } + + public function testValidationMessagesAreEmptyForAnUnknownParam(): void + { + try { + $this->seam()->client->request("POST", "/devices/list", [ + "json" => (object) ["device_ids" => 4242], + ]); + $this->fail("Expected HttpInvalidInputError"); + } catch (HttpInvalidInputError $error) { + $this->assertSame( + [], + $error->getValidationErrorMessages("non_existent_param"), + ); + } + } + + /** + * A workspace outage answers with a 503 that is not a Seam error + * envelope, so it surfaces as the underlying transport error rather than + * a Seam exception. + */ + public function testWorkspaceOutageSurfacesTheTransportError(): void + { + $seam = $this->seam(retries: 0); + + $seam->client->request("POST", "/_fake/simulate_workspace_outage", [ + "json" => (object) [ + "workspace_id" => $this->seed["seed_workspace_1"], + "routes" => ["/devices/list"], + ], + ]); + + try { + $seam->devices->list(); + $this->fail("Expected a Guzzle BadResponseException"); + } catch (\GuzzleHttp\Exception\BadResponseException $error) { + $this->assertSame(503, $error->getResponse()->getStatusCode()); + } + } } diff --git a/tests/LiteralBooleanTypesTest.php b/tests/LiteralBooleanTypesTest.php new file mode 100644 index 00000000..d0f5fadc --- /dev/null +++ b/tests/LiteralBooleanTypesTest.php @@ -0,0 +1,84 @@ +assertSame( + "?true", + (string) (new ReflectionProperty( + AccessCode::class, + "is_managed", + ))->getType(), + ); + $this->assertSame( + "?false", + (string) (new ReflectionProperty( + UnmanagedAccessCode::class, + "is_managed", + ))->getType(), + ); + + foreach ( + [ + AccessCode\Errors\ProviderIssue::class, + UnmanagedAccessCode\Errors\ProviderIssue::class, + ] + as $class + ) { + $this->assertSame( + "?true", + (string) (new ReflectionProperty( + $class, + "is_access_code_error", + ))->getType(), + ); + } + + foreach ( + [ + AccessCode\Errors\BridgeDisconnected::class, + UnmanagedAccessCode\Errors\BridgeDisconnected::class, + ] + as $class + ) { + $this->assertSame( + "?bool", + (string) (new ReflectionProperty( + $class, + "is_connected_account_error", + ))->getType(), + ); + } + + $this->assertSame( + "?bool", + (string) (new ReflectionProperty( + ActionAttempt\ScanCredential\Result\AcsCredentialOnSeam::class, + "is_managed", + ))->getType(), + ); + } + + public function testUnmanagedAccessCodeRejectsManagedLiteral(): void + { + $code = (new ReflectionClass( + UnmanagedAccessCode::class, + ))->newInstanceWithoutConstructor(); + + $this->expectException(TypeError::class); + $code->is_managed = true; + } +} diff --git a/tests/MalformedResponseTest.php b/tests/MalformedResponseTest.php new file mode 100644 index 00000000..e8cc0993 --- /dev/null +++ b/tests/MalformedResponseTest.php @@ -0,0 +1,215 @@ +guzzle_options(), + retries: 0, + ); + } + + /** + * @dataProvider nonSeamErrorResponses + */ + public function testNonSeamErrorResponsesSurfaceTheTransportError( + Response $response, + ): void { + $seam = $this->seam(new RecordingClient([$response])); + + try { + $seam->devices->list(); + $this->fail("Expected a Guzzle BadResponseException"); + } catch (HttpApiError $error) { + $this->fail( + "Expected a transport error, got " . + $error::class . + ": " . + $error->getMessage(), + ); + } catch (BadResponseException $error) { + $this->assertSame(500, $error->getResponse()->getStatusCode()); + } + } + + public static function nonSeamErrorResponses(): array + { + return [ + "plain text body" => [ + RecordingClient::raw(500, "Internal Server Error"), + ], + "html body" => [ + RecordingClient::raw( + 500, + "Gateway", + "text/html", + ), + ], + "malformed json" => [ + RecordingClient::raw(500, "{invalid json", "application/json"), + ], + "json without an error object" => [ + RecordingClient::json(500, ["message" => "Some error"]), + ], + "error without a type and message" => [ + RecordingClient::json(500, ["error" => ["code" => 500]]), + ], + "json that is not an object" => [ + RecordingClient::json(500, [1, 2]), + ], + "error that is not an object" => [ + RecordingClient::json(500, ["error" => "boom"]), + ], + "empty body" => [RecordingClient::raw(500, "", "application/json")], + ]; + } + + /** + * @dataProvider malformedSuccessResponses + */ + public function testMalformedSuccessResponsesRaiseASeamError( + Response $response, + string $expected_message, + ): void { + $seam = $this->seam(new RecordingClient([$response])); + + try { + $seam->devices->get("d1"); + $this->fail("Expected an InvalidResponseError"); + } catch (InvalidResponseError $error) { + $this->assertInstanceOf(SeamException::class, $error); + $this->assertSame("/devices/get", $error->getPath()); + $this->assertSame("device", $error->getKey()); + $this->assertStringContainsString( + $expected_message, + $error->getMessage(), + ); + } + } + + public static function malformedSuccessResponses(): array + { + return [ + "missing the response key" => [ + RecordingClient::json(200, ["ok" => true]), + "which the response does not contain", + ], + "the wrong response key" => [ + RecordingClient::json(200, [ + "devices" => [["device_id" => "d1"]], + ]), + "which the response does not contain", + ], + "json that is not an object" => [ + RecordingClient::json(200, [1, 2]), + "instead of a response object", + ], + "empty body" => [ + RecordingClient::raw(200, "", "application/json"), + "instead of a response object", + ], + "malformed json" => [ + RecordingClient::raw(200, "{invalid", "application/json"), + "instead of a response object", + ], + "a gateway page with a json content type" => [ + RecordingClient::raw( + 200, + "Maintenance", + "application/json", + ), + "instead of a response object", + ], + ]; + } + + public function testAListResponseThatIsNotAListRaisesASeamError(): void + { + $seam = $this->seam( + new RecordingClient([ + RecordingClient::json(200, ["devices" => "not-a-list"]), + ]), + ); + + try { + $seam->devices->list(); + $this->fail("Expected an InvalidResponseError"); + } catch (InvalidResponseError $error) { + $this->assertSame("devices", $error->getKey()); + $this->assertStringContainsString( + "instead of a list", + $error->getMessage(), + ); + } + } + + public function testAMalformedActionAttemptPollRaisesASeamError(): void + { + $seam = $this->seam( + new RecordingClient([ + RecordingClient::json(200, [ + "action_attempt" => [ + "action_attempt_id" => "aa_1", + "status" => "pending", + ], + ]), + RecordingClient::json(200, ["ok" => true]), + ]), + ); + + $this->expectException(InvalidResponseError::class); + + $seam->action_attempts->get("aa_1", [ + "timeout" => 5.0, + "polling_interval" => 0.01, + ]); + } + + /** + * A redirect is not a success, so it must not be handed back to the + * caller as though it were a resource. + */ + public function testRedirectIsNotTreatedAsSuccess(): void + { + $recorder = new RecordingClient([ + new Response(302, ["location" => "https://example.com/elsewhere"]), + ]); + + $seam = Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: array_merge($recorder->guzzle_options(), [ + "allow_redirects" => false, + ]), + retries: 0, + ); + + // A 3xx is not a Seam error envelope either, so it surfaces as the + // transport error rather than being handed back as a resource. + $this->expectException(RequestException::class); + + $seam->devices->list(); + } +} diff --git a/tests/NullValueTest.php b/tests/NullValueTest.php new file mode 100644 index 00000000..4dacdf76 --- /dev/null +++ b/tests/NullValueTest.php @@ -0,0 +1,78 @@ +assertInstanceOf(NullValue::class, NullValue::NULL); + $this->assertNotInstanceOf(NullValue::class, null); + $this->assertNotInstanceOf(NullValue::class, "NULL"); + } + + public function testIsASingleton(): void + { + $this->assertSame(NullValue::NULL, NullValue::NULL); + $this->assertCount(1, NullValue::cases()); + } + + public function testReadsAsItsOwnName(): void + { + $this->assertSame("NULL", NullValue::NULL->name); + } + + public function testReplaceReplacesTheSentinel(): void + { + $this->assertNull(NullValue::replace(NullValue::NULL)); + } + + public function testReplaceLeavesOtherValuesUnchanged(): void + { + foreach ([null, "NULL", 0, false, 20.5, ["a"]] as $value) { + $this->assertSame($value, NullValue::replace($value)); + } + } + + public function testReplaceRecursesIntoArrays(): void + { + $this->assertSame( + [ + "name" => null, + "codes" => [null, "1234"], + "nested" => ["code" => null], + ], + NullValue::replace([ + "name" => NullValue::NULL, + "codes" => [NullValue::NULL, "1234"], + "nested" => ["code" => NullValue::NULL], + ]), + ); + } + + public function testReplaceCopiesStdClassObjectsWithoutMutatingThem(): void + { + $payload = (object) [ + "name" => NullValue::NULL, + "nested" => (object) ["code" => NullValue::NULL], + ]; + + $replaced = NullValue::replace($payload); + + $this->assertNotSame($payload, $replaced); + $this->assertNull($replaced->name); + $this->assertNull($replaced->nested->code); + $this->assertSame(NullValue::NULL, $payload->name); + $this->assertSame(NullValue::NULL, $payload->nested->code); + } + + public function testReplaceDoesNotDescendIntoStrings(): void + { + $this->assertSame("a NULL b", NullValue::replace("a NULL b")); + } +} diff --git a/tests/PaginatorTest.php b/tests/PaginatorTest.php new file mode 100644 index 00000000..a71d1095 --- /dev/null +++ b/tests/PaginatorTest.php @@ -0,0 +1,202 @@ + 2]): Paginator + { + $seam = $this->seam(); + + return $seam->createPaginator( + fn($p) => $seam->connected_accounts->list(...$p), + $params, + ); + } + + public function testCreatePaginatorReturnsAPaginator(): void + { + $this->assertInstanceOf(Paginator::class, $this->paginator()); + } + + public function testFirstPageReturnsTheFirstPage(): void + { + [$accounts, $pagination] = $this->paginator()->firstPage(); + + $this->assertCount(2, $accounts); + $this->assertInstanceOf(Pagination::class, $pagination); + $this->assertTrue($pagination->has_next_page); + $this->assertNotNull($pagination->next_page_cursor); + } + + public function testNextPageReturnsTheNextPage(): void + { + $pages = $this->paginator(); + + [$first, $pagination] = $pages->firstPage(); + [$second] = $pages->nextPage($pagination->next_page_cursor); + + $this->assertNotEmpty($second); + + $first_ids = array_map( + fn($account) => $account->connected_account_id, + $first, + ); + $second_ids = array_map( + fn($account) => $account->connected_account_id, + $second, + ); + + $this->assertEmpty(array_intersect($first_ids, $second_ids)); + } + + /** + * The paginator reads the pagination metadata through on_response, but + * must not swallow a callback the caller passed in themselves. + */ + public function testOnResponseParamIsChainedNotReplaced(): void + { + $seen = 0; + + [, $pagination] = $this->paginator([ + "limit" => 2, + "on_response" => function ($response) use (&$seen): void { + $seen++; + $this->assertObjectHasProperty("pagination", $response); + }, + ])->firstPage(); + + $this->assertSame(1, $seen); + // The paginator's own callback has to keep working as well. + $this->assertTrue($pagination->has_next_page); + } + + public function testNextPageRequiresACursor(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("next_page_cursor"); + + $this->paginator()->nextPage(null); + } + + public function testNextPageRejectsAnEmptyCursor(): void + { + $this->expectException(\InvalidArgumentException::class); + + $this->paginator()->nextPage(""); + } + + public function testLastPageHasNoNextPage(): void + { + $pages = $this->paginator(["limit" => 100]); + + [, $pagination] = $pages->firstPage(); + + $this->assertFalse($pagination->has_next_page); + $this->assertNull($pagination->next_page_cursor); + } + + public function testFlattenToArrayReturnsEveryResource(): void + { + $all = $this->paginator()->flattenToArray(); + $expected = $this->seam()->connected_accounts->list(); + + $this->assertCount(count($expected), $all); + } + + public function testFlattenIteratesEveryResource(): void + { + $ids = []; + + foreach ($this->paginator()->flatten() as $account) { + $ids[] = $account->connected_account_id; + } + + $expected = $this->seam()->connected_accounts->list(); + + $this->assertCount(count($expected), $ids); + $this->assertSame(array_unique($ids), $ids); + } + + public function testEndpointWithoutPaginationIsRejected(): void + { + $seam = $this->seam(); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("unpaginated endpoint"); + + $seam + ->createPaginator(fn($p) => $seam->workspaces->list()) + ->firstPage(); + } + + private function pinned_cursor_paginator(string $cursor): Paginator + { + $recorder = RecordingClient::repeating( + RecordingClient::json(200, [ + "connected_accounts" => [ + ["connected_account_id" => "ca_1"], + ["connected_account_id" => "ca_2"], + ], + "pagination" => [ + "has_next_page" => true, + "next_page_cursor" => $cursor, + ], + "ok" => true, + ]), + times: 200, + ); + + $seam = Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: $recorder->guzzle_options(), + retries: 0, + ); + + return $seam->createPaginator( + fn($p) => $seam->connected_accounts->list(...$p), + ["limit" => 2], + ); + } + + public function testFlattenToArrayStopsWhenTheCursorRepeats(): void + { + $all = $this->pinned_cursor_paginator("stuck")->flattenToArray(); + + $this->assertCount(4, $all); + } + + public function testFlattenStopsWhenTheCursorRepeats(): void + { + $ids = []; + + foreach ($this->pinned_cursor_paginator("stuck")->flatten() as $item) { + $ids[] = $item->connected_account_id; + $this->assertLessThan(10, count($ids), "flatten did not terminate"); + } + + $this->assertCount(4, $ids); + } + + public function testACursorNamedFirstPageStillAdvances(): void + { + $pages = $this->pinned_cursor_paginator("FIRST_PAGE"); + + [, $pagination] = $pages->firstPage(); + $this->assertSame("FIRST_PAGE", $pagination->next_page_cursor); + + [$second] = $pages->nextPage($pagination->next_page_cursor); + + $this->assertNotEmpty($second); + $this->assertCount(4, $pages->flattenToArray()); + } +} diff --git a/tests/PersonalAccessTokenTest.php b/tests/PersonalAccessTokenTest.php new file mode 100644 index 00000000..850e1016 --- /dev/null +++ b/tests/PersonalAccessTokenTest.php @@ -0,0 +1,140 @@ +seed["seam_at1_token"], + $this->seed["seed_workspace_1"], + endpoint: $this->endpoint, + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + $this->assertSame( + $this->seed["seed_workspace_1"], + $device->workspace_id, + ); + } + + public function testConstructorReturnsAnAuthorizedClient(): void + { + $seam = new Seam( + personal_access_token: $this->seed["seam_at1_token"], + workspace_id: $this->seed["seed_workspace_1"], + endpoint: $this->endpoint, + ); + + $device = $seam->devices->get($this->seed["august_device_1"]); + + $this->assertSame($this->seed["august_device_1"], $device->device_id); + } + + public function testWorkspaceIdIsRequired(): void + { + $this->expectException(InvalidOptionsError::class); + $this->expectExceptionMessage( + "Must pass a workspace_id when using a personal_access_token", + ); + + new Seam( + personal_access_token: $this->seed["seam_at1_token"], + endpoint: $this->endpoint, + ); + } + + public function testApiKeyCannotBeCombinedWithAPersonalAccessToken(): void + { + $this->expectException(InvalidOptionsError::class); + + new Seam( + api_key: $this->seed["seam_apikey1_token"], + personal_access_token: $this->seed["seam_at1_token"], + workspace_id: $this->seed["seed_workspace_1"], + endpoint: $this->endpoint, + ); + } + + public function testPersonalAccessTokenFormatIsChecked(): void + { + $this->expectException(InvalidTokenError::class); + + new Seam( + personal_access_token: "seam_cst_1234", + workspace_id: $this->seed["seed_workspace_1"], + endpoint: $this->endpoint, + ); + } + + public function testWithoutWorkspaceClientListsWorkspaces(): void + { + $seam = SeamWithoutWorkspace::from_personal_access_token( + $this->seed["seam_at1_token"], + endpoint: $this->endpoint, + ); + + $workspaces = $seam->workspaces->list(); + + $workspace_ids = array_map( + fn($workspace) => $workspace->workspace_id, + $workspaces, + ); + + $this->assertContains($this->seed["seed_workspace_1"], $workspace_ids); + } + + public function testWithoutWorkspaceConstructorListsWorkspaces(): void + { + $seam = new SeamWithoutWorkspace( + personal_access_token: $this->seed["seam_at1_token"], + endpoint: $this->endpoint, + ); + + $this->assertNotEmpty($seam->workspaces->list()); + } + + public function testWithoutWorkspaceClientCreatesAWorkspace(): void + { + $seam = SeamWithoutWorkspace::from_personal_access_token( + $this->seed["seam_at1_token"], + endpoint: $this->endpoint, + ); + + $workspace = $seam->workspaces->create( + name: "Test Workspace", + connect_partner_name: "Test Partner", + is_sandbox: true, + ); + + $this->assertSame("Test Workspace", $workspace->name); + } + + public function testWithoutWorkspaceClientRequiresAToken(): void + { + $this->expectException(InvalidOptionsError::class); + + new SeamWithoutWorkspace(endpoint: $this->endpoint); + } + + public function testWithoutWorkspaceClientChecksTheTokenFormat(): void + { + $this->expectException(InvalidTokenError::class); + + SeamWithoutWorkspace::from_personal_access_token( + $this->seed["seam_apikey1_token"], + endpoint: $this->endpoint, + ); + } +} diff --git a/tests/RecordTypesTest.php b/tests/RecordTypesTest.php new file mode 100644 index 00000000..41a072a2 --- /dev/null +++ b/tests/RecordTypesTest.php @@ -0,0 +1,71 @@ +getDocComment(); + $methodDoc = (new ReflectionMethod( + DevicesClient::class, + "update", + ))->getDocComment(); + + $this->assertIsString($propertyDoc); + $this->assertStringContainsString( + "@var array|\\stdClass|null", + $propertyDoc, + ); + $this->assertStringContainsString( + "@var array|\\stdClass|null", + (string) (new ReflectionProperty( + AccessCodeCreated::class, + "connected_account_custom_metadata", + ))->getDocComment(), + ); + $this->assertStringContainsString( + "@var array|\\stdClass|null", + (string) (new ReflectionProperty( + NoiseSensorNoiseThresholdTriggered::class, + "minut_metadata", + ))->getDocComment(), + ); + $this->assertIsString($methodDoc); + $this->assertStringContainsString( + '@param array|\stdClass $custom_metadata', + $methodDoc, + ); + } + + public function testRecordResponsesPreserveJsonObjects(): void + { + $device = Device::from_json( + (object) [ + "custom_metadata" => (object) [ + "label" => "front door", + "active" => true, + ], + ], + ); + + $this->assertNotNull($device); + $this->assertEquals( + (object) ["label" => "front door", "active" => true], + $device->custom_metadata, + ); + } +} diff --git a/tests/RedirectTest.php b/tests/RedirectTest.php new file mode 100644 index 00000000..0be0105d --- /dev/null +++ b/tests/RedirectTest.php @@ -0,0 +1,72 @@ +guzzle_options(), + $guzzle_options, + ), + ); + } + + public function testFollowsARedirectByDefault(): void + { + $recorder = new RecordingClient([ + new Response(302, [ + "location" => "https://example.com/devices/list_moved", + ]), + RecordingClient::json(200, ["devices" => []]), + ]); + + $devices = $this->seam($recorder)->devices->list(); + + $this->assertSame([], $devices); + $this->assertSame(2, $recorder->attempt_count()); + } + + /** + * With following disabled, a redirect is a response outside the success + * range like any other, and must not be silently decoded as a success. + */ + public function testARedirectIsAnErrorWhenFollowingIsDisabled(): void + { + $recorder = new RecordingClient([ + new Response(302, [ + "location" => "https://example.com/devices/list_moved", + ]), + ]); + + $seam = $this->seam($recorder, ["allow_redirects" => false]); + + try { + $seam->devices->list(); + $this->fail("Expected the redirect to surface as an error"); + } catch (RequestException $error) { + $this->assertSame(302, $error->getResponse()?->getStatusCode()); + } + + $this->assertSame(1, $recorder->attempt_count()); + } +} diff --git a/tests/RequiredParametersTest.php b/tests/RequiredParametersTest.php new file mode 100644 index 00000000..e3c5d584 --- /dev/null +++ b/tests/RequiredParametersTest.php @@ -0,0 +1,165 @@ + [], "events" => []]), + ]); + + return [ + Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: $recorder->guzzle_options(), + retries: 0, + ), + $recorder, + ]; + } + + private function assertRejected(callable $call, string $path): void + { + [$seam, $recorder] = $this->recorded(); + + try { + $call($seam); + $this->fail("Expected InvalidArgumentException for $path"); + } catch (\InvalidArgumentException $error) { + $this->assertSame( + "At least one parameter is required for $path", + $error->getMessage(), + ); + } + + $this->assertSame(0, $recorder->request_count()); + } + + public function testRejectsACallThatNamesNothing(): void + { + $this->assertRejected( + fn(Seam $seam) => $seam->access_codes->list(), + "/access_codes/list", + ); + } + + /** + * @dataProvider paginationOnlyCalls + */ + public function testPaginationParamsAloneDoNotSatisfyTheGuard( + callable $call, + string $path, + ): void { + $this->assertRejected($call, $path); + } + + public static function paginationOnlyCalls(): array + { + return [ + "limit" => [ + fn(Seam $seam) => $seam->access_codes->list(limit: 20), + "/access_codes/list", + ], + "page cursor" => [ + fn(Seam $seam) => $seam->access_codes->list( + page_cursor: "cursor", + ), + "/access_codes/list", + ], + "limit and page cursor" => [ + fn(Seam $seam) => $seam->access_codes->list( + limit: 20, + page_cursor: "cursor", + ), + "/access_codes/list", + ], + "limit on an unpaginated list" => [ + fn(Seam $seam) => $seam->events->list(limit: 20), + "/events/list", + ], + ]; + } + + /** + * @dataProvider filteredCalls + */ + public function testAcceptsACallThatNamesAFilter( + callable $call, + string $expected_query, + ): void { + [$seam, $recorder] = $this->recorded(); + + $call($seam); + + $this->assertSame(1, $recorder->request_count()); + $this->assertStringContainsString( + $expected_query, + $recorder->request()->getUri()->getQuery(), + ); + } + + public static function filteredCalls(): array + { + return [ + "a filter" => [ + fn(Seam $seam) => $seam->access_codes->list( + device_id: "device-1", + ), + "device_id=device-1", + ], + "a filter alongside pagination" => [ + fn(Seam $seam) => $seam->access_codes->list( + device_id: "device-1", + limit: 20, + ), + "device_id=device-1", + ], + "a filter on an unpaginated list" => [ + fn(Seam $seam) => $seam->events->list( + event_type: "device.connected", + ), + "event_type=device.connected", + ], + ]; + } + + public function testAPaginatorOverAnUnfilteredListIsRejectedThroughout(): void + { + [$seam] = $this->recorded(); + + $pages = $seam->createPaginator( + fn($params) => $seam->access_codes->list(...$params), + ); + + foreach ( + [ + fn() => $pages->firstPage(), + fn() => $pages->flattenToArray(), + fn() => iterator_to_array($pages->flatten()), + ] + as $call + ) { + try { + $call(); + $this->fail("Expected InvalidArgumentException"); + } catch (\InvalidArgumentException $error) { + $this->assertSame( + "At least one parameter is required for /access_codes/list", + $error->getMessage(), + ); + } + } + } +} diff --git a/tests/ResourceTest.php b/tests/ResourceTest.php new file mode 100644 index 00000000..582489b0 --- /dev/null +++ b/tests/ResourceTest.php @@ -0,0 +1,190 @@ + [ + "battery" => ["level" => 0.4, "status" => "low"], + "accessory_keypad" => ["battery" => ["level" => 0.25]], + ], + "errors" => [ + [ + "error_code" => "device_offline", + "is_device_error" => true, + ], + ], + ]), + ), + ); + + $this->assertNotNull($device); + + return $device; + } + + public function testUnknownEnumValuesRemainReadable(): void + { + $device = Device::from_json( + (object) [ + "device_type" => "future_device_type", + ], + ); + + $this->assertSame("future_device_type", $device->device_type); + } + + public function testUnknownDiscriminatedValuesUseTheBaseClass(): void + { + $device = Device::from_json( + (object) [ + "errors" => [ + (object) [ + "error_code" => "future_error", + "message" => "Future error", + ], + ], + ], + ); + $error = $device->errors[0]; + + $this->assertSame(Device\Errors::class, $error::class); + $this->assertSame("future_error", $error->error_code); + $this->assertSame("Future error", $error->message); + } + + public function testNestedPropertyClassesAreNamespacedByTheirOwner(): void + { + $device = $this->device(); + + $this->assertInstanceOf(Device\Properties::class, $device->properties); + $this->assertInstanceOf( + Device\Properties\Battery::class, + $device->properties->battery, + ); + $this->assertInstanceOf( + Device\Properties\AccessoryKeypad::class, + $device->properties->accessory_keypad, + ); + $this->assertInstanceOf( + Device\Properties\AccessoryKeypad\Battery::class, + $device->properties->accessory_keypad->battery, + ); + } + + /** + * The two batteries used to collapse onto one Seam\Resources\DeviceBattery, + * and whichever was generated first won, so the device battery lost its + * status. + */ + public function testSameNamedPropertiesAtDifferentDepthsKeepTheirShapes(): void + { + $device = $this->device(); + + $this->assertNotSame( + Device\Properties\Battery::class, + Device\Properties\AccessoryKeypad\Battery::class, + ); + + $this->assertSame(0.4, $device->properties->battery->level); + $this->assertSame("low", $device->properties->battery->status); + $this->assertSame("low", Status::LOW->value); + + $this->assertSame( + 0.25, + $device->properties->accessory_keypad->battery->level, + ); + $this->assertObjectNotHasProperty( + "status", + $device->properties->accessory_keypad->battery, + ); + } + + public function testNestedClassesAreNotDeclaredInTheResourcesNamespace(): void + { + $this->assertFalse( + class_exists("Seam\\Resources\\DeviceBattery"), + "Nested classes must not be flattened into Seam\\Resources", + ); + $this->assertFalse( + class_exists("Seam\\Resources\\DeviceProperties"), + "Nested classes must not be flattened into Seam\\Resources", + ); + } + + public function testDiscriminatedListReturnsTheSpecificVariant(): void + { + $error = $this->device()->errors[0]; + + $this->assertInstanceOf(DeviceOffline::class, $error); + $this->assertSame("device_offline", $error->error_code); + $this->assertSame("device_offline", ErrorCode::DEVICE_OFFLINE->value); + $this->assertTrue($error->is_device_error); + $this->assertObjectNotHasProperty("is_bridge_error", $error); + } + + /** + * Every class in the file has to be registered, because src/Resources is + * autoloaded by classmap rather than PSR-4. + */ + public function testNestedClassesAreAutoloadable(): void + { + foreach ( + [ + Device\Properties\Battery::class, + Device\Properties\AccessoryKeypad\Battery::class, + Device\Properties\AvailableClimatePresets::class, + Device\Properties\AvailableClimatePresets\EcobeeMetadata::class, + ] + as $className + ) { + $this->assertTrue( + class_exists($className), + "{$className} should be autoloadable", + ); + } + } + + /** + * The climate preset metadata used to take the shape of the device level + * ecobee_metadata, losing these three fields. + */ + public function testClimatePresetMetadataKeepsItsOwnShape(): void + { + $preset = Device\Properties\AvailableClimatePresets\EcobeeMetadata::from_json( + json_decode( + json_encode([ + "climate_ref" => "sleep", + "is_optimized" => true, + "owner" => "user", + ]), + ), + ); + + $this->assertNotNull($preset); + $this->assertSame("sleep", $preset->climate_ref); + $this->assertTrue($preset->is_optimized); + $this->assertSame("user", $preset->owner); + $this->assertSame("user", Owner::USER->value); + } +} diff --git a/tests/RetryTest.php b/tests/RetryTest.php new file mode 100644 index 00000000..967658ba --- /dev/null +++ b/tests/RetryTest.php @@ -0,0 +1,288 @@ + ["type" => "service_unavailable", "message" => "Down"], + ]); + } + + private static function devices(): \GuzzleHttp\Psr7\Response + { + return RecordingClient::json(200, ["devices" => []]); + } + + private function seam(RecordingClient $recorder, ?int $retries = null): Seam + { + return new Seam( + api_key: self::API_KEY, + endpoint: "https://example.com", + guzzle_options: $recorder->guzzle_options(), + retries: $retries, + ); + } + + /** + * A POST the server may already have processed could duplicate a write if + * repeated, so a status code never triggers a retry for one. + */ + public function testDoesNotRetryPostOnServiceUnavailable(): void + { + $recorder = RecordingClient::repeating(self::service_unavailable()); + + try { + $this->seam($recorder)->client->request("POST", "/devices/list"); + $this->fail("Expected the 503 to surface"); + } catch (\Throwable) { + // The error mapping is covered in HttpErrorTest. + } + + $this->assertSame(1, $recorder->attempt_count()); + } + + /** + * Non-idempotent methods are never retried, even when the failure is + * known to have happened before the request reached the server. + */ + public function testDoesNotRetryPostOnConnectionFailure(): void + { + $connect_error = new ConnectException( + "Could not resolve host", + new Request("POST", "/devices/list"), + ); + + $recorder = RecordingClient::repeating_throwable($connect_error); + + try { + $this->seam($recorder)->client->request("POST", "/devices/list"); + $this->fail("Expected the connection failure to surface"); + } catch (ConnectException) { + // Expected. + } + + $this->assertSame(1, $recorder->attempt_count()); + } + + /** + * A timeout may have fired while waiting on a response to a request the + * server received and is still processing, so repeating the POST could + * duplicate a write. + */ + public function testDoesNotRetryPostOnTimeout(): void + { + $timeout = new ConnectException( + "cURL error 28: Operation timed out after 30001 milliseconds with 0 bytes received", + new Request("POST", "/devices/list"), + null, + ["errno" => 28], + ); + + $recorder = RecordingClient::repeating_throwable($timeout); + + try { + $this->seam($recorder)->client->request("POST", "/devices/list"); + $this->fail("Expected the timeout to surface"); + } catch (ConnectException) { + // Expected. + } + + $this->assertSame(1, $recorder->attempt_count()); + } + + /** + * A handler other than curl reports no errno, so the timeout is + * recognized by its message. + */ + public function testDoesNotRetryPostOnTimeoutWithoutAnErrno(): void + { + $timeout = new ConnectException( + "Connection timed out", + new Request("POST", "/devices/list"), + ); + + $recorder = RecordingClient::repeating_throwable($timeout); + + try { + $this->seam($recorder)->client->request("POST", "/devices/list"); + $this->fail("Expected the timeout to surface"); + } catch (ConnectException) { + // Expected. + } + + $this->assertSame(1, $recorder->attempt_count()); + } + + /** + * Repeating an idempotent request is safe even when the server may have + * received it, so a timeout does get retried there. + */ + public function testRetriesIdempotentRequestsOnTimeout(): void + { + $timeout = new ConnectException( + "cURL error 28: Operation timed out after 30001 milliseconds with 0 bytes received", + new Request("GET", "/devices/list"), + null, + ["errno" => 28], + ); + + $recorder = new RecordingClient([$timeout, $timeout, self::devices()]); + + $devices = $this->seam($recorder)->devices->list(); + + $this->assertSame([], $devices); + $this->assertSame(3, $recorder->attempt_count()); + } + + /** + * A connection reset is a transport error, but POST is still not safe to + * repeat. + */ + public function testDoesNotRetryPostOnConnectionReset(): void + { + $reset = new RequestException( + "Connection reset by peer", + new Request("POST", "/devices/list"), + null, + null, + ["errno" => 104], + ); + + $recorder = RecordingClient::repeating_throwable($reset); + + try { + $this->seam($recorder)->client->request("POST", "/devices/list"); + $this->fail("Expected the connection reset to surface"); + } catch (RequestException) { + // Expected. + } + + $this->assertSame(1, $recorder->attempt_count()); + } + + public function testStopsRetryingOnceRetriesAreExhausted(): void + { + $connect_error = new ConnectException( + "Could not resolve host", + new Request("GET", "/devices/list"), + ); + + $recorder = RecordingClient::repeating_throwable($connect_error); + + $this->expectException(ConnectException::class); + + try { + $this->seam($recorder, retries: 1)->devices->list(); + } finally { + $this->assertSame(2, $recorder->attempt_count()); + } + } + + public function testDoesNotRetryWhenRetriesAreDisabled(): void + { + $connect_error = new ConnectException( + "Could not resolve host", + new Request("GET", "/devices/list"), + ); + + $recorder = RecordingClient::repeating_throwable($connect_error); + + try { + $this->seam($recorder, retries: 0)->devices->list(); + $this->fail("Expected the connection failure to surface"); + } catch (ConnectException) { + // Expected. + } + + $this->assertSame(1, $recorder->attempt_count()); + } + + /** + * Repeating a read is safe, so a 503 on one should be retried. + */ + public function testSdkReadsAreRetriedOnServiceUnavailable(): void + { + $recorder = new RecordingClient([ + self::service_unavailable(), + self::service_unavailable(), + self::devices(), + ]); + + $devices = $this->seam($recorder)->devices->list(); + + $this->assertSame([], $devices); + $this->assertSame(3, $recorder->attempt_count()); + } + + /** + * Building a client must not mutate a handler stack the caller may + * reuse: a second client built from the same options would otherwise + * stack the retry middleware twice and multiply the retries. + */ + public function testBuildingASecondClientDoesNotStackRetries(): void + { + $recorder = RecordingClient::repeating(self::service_unavailable()); + $options = $recorder->guzzle_options(); + + $first = new Seam( + api_key: self::API_KEY, + endpoint: "https://example.com", + guzzle_options: $options, + ); + $second = new Seam( + api_key: self::API_KEY, + endpoint: "https://example.com", + guzzle_options: $options, + ); + + $this->assertNotSame($first->client, $second->client); + + try { + Body::decode($second->client->request("GET", "/devices/list")); + $this->fail("Expected the 503 to surface"); + } catch (\Throwable) { + // The error mapping is covered in HttpErrorTest. + } + + $this->assertSame(3, $recorder->attempt_count()); + } + + /** + * A caller reaching for the client directly with an idempotent method + * gets status based retries. + */ + public function testRetriesIdempotentRequestsOnServiceUnavailable(): void + { + $recorder = new RecordingClient([ + self::service_unavailable(), + self::service_unavailable(), + self::devices(), + ]); + + $res = Body::decode( + $this->seam($recorder)->client->request("GET", "/devices/list"), + ); + + $this->assertSame([], $res->devices); + $this->assertSame(3, $recorder->attempt_count()); + } +} diff --git a/tests/SeamWebhookTest.php b/tests/SeamWebhookTest.php new file mode 100644 index 00000000..37b9d888 --- /dev/null +++ b/tests/SeamWebhookTest.php @@ -0,0 +1,214 @@ + "8d7e0b26-5e6c-4a1f-9b3d-1b0f0e5a9c11", + "event_type" => "device.connected", + "workspace_id" => "398d80b7-3f96-47c2-b85a-6f8ba21d07be", + "device_id" => "054765c8-a2fc-4599-b486-14c19f462c45", + "created_at" => "2024-01-01T00:00:00.000Z", + "occurred_at" => "2024-01-01T00:00:00.000Z", + ]); + } + + /** + * @return array + */ + private function signed_headers(string $payload, ?int $at = null): array + { + $id = "msg_test"; + $timestamp = (string) ($at ?? time()); + + $signature = (new Webhook(self::SECRET))->sign( + $id, + $timestamp, + $payload, + ); + + return [ + "svix-id" => $id, + "svix-timestamp" => $timestamp, + "svix-signature" => $signature, + ]; + } + + public function testVerifyReturnsTheEvent(): void + { + $payload = $this->payload(); + + $event = (new SeamWebhook(self::SECRET))->verify( + $payload, + $this->signed_headers($payload), + ); + + $this->assertInstanceOf(DeviceConnected::class, $event); + $this->assertSame("device.connected", $event->event_type); + $this->assertSame( + "device.connected", + EventType::DEVICE_CONNECTED->value, + ); + $this->assertSame( + "8d7e0b26-5e6c-4a1f-9b3d-1b0f0e5a9c11", + $event->event_id, + ); + } + + public function testVerifyAcceptsHeadersInAnyCase(): void + { + $payload = $this->payload(); + + $headers = []; + foreach ($this->signed_headers($payload) as $name => $value) { + $headers[strtoupper($name)] = $value; + } + + $event = (new SeamWebhook(self::SECRET))->verify($payload, $headers); + + $this->assertSame("device.connected", $event->event_type); + } + + public function testDecodesAnAccessCodeEventIntoItsSpecificClass(): void + { + $event = Event::from_json( + json_decode( + json_encode([ + "event_id" => "event_1", + "event_type" => "access_code.created", + "workspace_id" => "workspace_1", + "access_code_id" => "access_code_1", + "connected_account_id" => "connected_account_1", + "device_id" => "device_1", + "created_at" => "2024-01-01T00:00:00.000Z", + "occurred_at" => "2024-01-01T00:00:00.000Z", + ]), + ), + ); + + $this->assertInstanceOf(AccessCodeCreated::class, $event); + $this->assertSame("access_code_1", $event->access_code_id); + } + + public function testUnknownEventTypeUsesTheBaseClass(): void + { + $event = Event::from_json( + (object) [ + "event_id" => "event_1", + "event_type" => "device.future_event", + ], + ); + + $this->assertSame(Event::class, $event::class); + $this->assertSame("device.future_event", $event->event_type); + } + + public function testVerifyRejectsATamperedPayload(): void + { + $payload = $this->payload(); + $headers = $this->signed_headers($payload); + + $this->expectException(WebhookVerificationException::class); + + (new SeamWebhook(self::SECRET))->verify( + str_replace("device.connected", "device.disconnected", $payload), + $headers, + ); + } + + public function testVerifyRejectsTheWrongSecret(): void + { + $payload = $this->payload(); + $headers = $this->signed_headers($payload); + + $this->expectException(WebhookVerificationException::class); + + (new SeamWebhook( + "whsec_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ))->verify($payload, $headers); + } + + public function testVerifyRejectsAnExpiredTimestamp(): void + { + $payload = $this->payload(); + + $this->expectException(WebhookVerificationException::class); + + (new SeamWebhook(self::SECRET))->verify( + $payload, + $this->signed_headers($payload, time() - 3600), + ); + } + + /** + * @dataProvider missingHeaders + */ + public function testVerifyRejectsAMissingHeader(string $missing): void + { + $payload = $this->payload(); + $headers = $this->signed_headers($payload); + unset($headers[$missing]); + + $this->expectException(WebhookVerificationException::class); + + (new SeamWebhook(self::SECRET))->verify($payload, $headers); + } + + public static function missingHeaders(): array + { + return [ + "svix-id" => ["svix-id"], + "svix-timestamp" => ["svix-timestamp"], + "svix-signature" => ["svix-signature"], + ]; + } + + /** + * @dataProvider unreadablePayloads + */ + public function testVerifyDistinguishesAnUnreadablePayload( + string $payload, + ): void { + $headers = $this->signed_headers($payload); + + try { + (new SeamWebhook(self::SECRET))->verify($payload, $headers); + $this->fail("Expected InvalidWebhookPayloadError"); + } catch (InvalidWebhookPayloadError $error) { + $this->assertInstanceOf(SeamException::class, $error); + $this->assertNotInstanceOf( + WebhookVerificationException::class, + $error, + ); + } + } + + public static function unreadablePayloads(): array + { + return [ + "malformed json" => ["{not json"], + "json that is not an object" => ["[1, 2]"], + "json null" => ["null"], + "empty body" => [""], + "object that is not an event" => ['{"hello":"world"}'], + ]; + } +} diff --git a/tests/SearchParamsTest.php b/tests/SearchParamsTest.php new file mode 100644 index 00000000..eda7adbb --- /dev/null +++ b/tests/SearchParamsTest.php @@ -0,0 +1,267 @@ + []]; + private const DEVICE = ["device" => ["device_id" => "device1"]]; + + private function seam(RecordingClient $recording): Seam + { + return Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: $recording->guzzle_options(), + ); + } + + public function testClientSerializesSearchParams(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + + $this->seam($recording)->client->request("GET", "/devices/list", [ + "query" => [ + "device_ids" => ["device1", "device2"], + "custom_metadata_has" => ["tag" => "front", "floor" => 2], + "limit" => 20, + ], + ]); + + $this->assertSame( + "custom_metadata_has.floor=2" . + "&custom_metadata_has.tag=front" . + "&device_ids=device1" . + "&device_ids=device2" . + "&limit=20" . + "&_strict=true", + $recording->request()->getUri()->getQuery(), + ); + $this->assertSame("GET", $recording->request()->getMethod()); + $this->assertSame( + "/devices/list", + $recording->request()->getUri()->getPath(), + ); + } + + public function testClientDoesNotReencodeTheSerializedSearchParams(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + + $this->seam($recording)->client->request("GET", "/devices/list", [ + "query" => ["search" => "a *~ b"], + ]); + + $this->assertSame( + "search=a+*%7E+b&_strict=true", + $recording->request()->getUri()->getQuery(), + ); + } + + public function testClientSerializesEmptyArraysToAnEmptyValue(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + + $this->seam($recording)->client->request("GET", "/devices/list", [ + "query" => ["device_ids" => []], + ]); + + $this->assertSame( + "device_ids=&_strict=true", + $recording->request()->getUri()->getQuery(), + ); + } + + public function testClientOmitsSearchParamsSetToNull(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + + $this->seam($recording)->client->request("GET", "/devices/list", [ + "query" => ["search" => null, "limit" => 20], + ]); + + $this->assertSame( + "limit=20&_strict=true", + $recording->request()->getUri()->getQuery(), + ); + } + + public function testClientSerializesSearchParamsSetToNullValue(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + + $this->seam($recording)->client->request("GET", "/devices/list", [ + "query" => ["search" => NullValue::NULL, "limit" => 20], + ]); + + $this->assertSame( + "limit=20&search=&_strict=true", + $recording->request()->getUri()->getQuery(), + ); + } + + public function testClientSendsNoQueryStringWithoutSearchParams(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + $seam = $this->seam($recording); + + $seam->client->request("GET", "/devices/list", ["query" => []]); + $seam->client->request("GET", "/devices/list", [ + "query" => ["search" => null], + ]); + $seam->client->request("GET", "/devices/list"); + + foreach ([0, 1, 2] as $index) { + $this->assertSame( + "/devices/list", + $recording->request($index)->getRequestTarget(), + ); + } + } + + public function testClientSerializesSearchParamsOfEveryVerb(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + $seam = $this->seam($recording); + + $verbs = ["GET", "POST", "PUT", "PATCH", "DELETE"]; + + foreach ($verbs as $verb) { + $seam->client->request($verb, "/devices/list", [ + "query" => ["sync" => true], + ]); + } + + foreach ($verbs as $index => $verb) { + $this->assertSame($verb, $recording->request($index)->getMethod()); + $this->assertSame( + "sync=true&_strict=true", + $recording->request($index)->getUri()->getQuery(), + ); + } + } + + public function testClientPassesSearchParamsItDidNotSerializeToGuzzle(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + + $this->seam($recording)->client->request("GET", "/devices/list", [ + "query" => "device_ids=device1", + ]); + + $this->assertSame( + "device_ids=device1", + $recording->request()->getUri()->getQuery(), + ); + } + + public function testClientRejectsASearchParamItCannotSerialize(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICES), + ); + $seam = $this->seam($recording); + + try { + $seam->client->request("GET", "/devices/list", [ + "query" => ["search" => new \SplStack()], + ]); + $this->fail("Expected an UnserializableParamError"); + } catch (UnserializableParamError $error) { + $this->assertSame("search", $error->getName()); + } + + $this->assertSame(0, $recording->request_count()); + } + + public function testClientSerializesNullValueInAJsonBodyToNull(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICE), + ); + + $this->seam($recording)->client->request("POST", "/devices/update", [ + "json" => (object) [ + "device_id" => "device1", + "name" => NullValue::NULL, + "properties" => ["code" => NullValue::NULL], + ], + ]); + + $this->assertSame("POST", $recording->request()->getMethod()); + $this->assertSame( + [ + "device_id" => "device1", + "name" => null, + "properties" => ["code" => null], + ], + json_decode((string) $recording->request()->getBody(), true), + ); + } + + public function testClientLeavesAJsonBodyWithoutNullValueUnchanged(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICE), + ); + + $body = [ + "device_id" => "device1", + "name" => "Front Door", + "limit" => 20, + "sync" => true, + ]; + + $this->seam($recording)->client->request("POST", "/devices/update", [ + "json" => (object) $body, + ]); + + $this->assertSame( + $body, + json_decode((string) $recording->request()->getBody(), true), + ); + } + + public function testClientSerializesTheSearchParamsOfAGeneratedRoute(): void + { + $recording = RecordingClient::repeating( + RecordingClient::json(200, self::DEVICE), + ); + + $this->seam($recording)->devices->get(name: "Front Door"); + + $this->assertSame("GET", $recording->request()->getMethod()); + $this->assertSame( + "/devices/get", + $recording->request()->getUri()->getPath(), + ); + $this->assertSame( + "name=Front+Door&_strict=true", + $recording->request()->getUri()->getQuery(), + ); + } +} diff --git a/tests/SerializationTest.php b/tests/SerializationTest.php new file mode 100644 index 00000000..83310710 --- /dev/null +++ b/tests/SerializationTest.php @@ -0,0 +1,55 @@ +seam()->devices->list(); + + $this->assertNotEmpty($devices); + } + + public function testNullArrayParameterIsNotSent(): void + { + $devices = $this->seam()->devices->list(device_ids: null); + + $this->assertCount(count($this->seam()->devices->list()), $devices); + } + + public function testEmptyArrayParameterIsSent(): void + { + $devices = $this->seam()->devices->list(device_ids: []); + + $this->assertCount(0, $devices); + } + + public function testPopulatedArrayParameterFiltersTheResults(): void + { + $device_ids = [ + $this->seed["august_device_1"], + $this->seed["ecobee_device_1"], + ]; + + $devices = $this->seam()->devices->list(device_ids: $device_ids); + + $this->assertCount(2, $devices); + + $returned_ids = array_map(fn($device) => $device->device_id, $devices); + + sort($returned_ids); + sort($device_ids); + + $this->assertSame($device_ids, $returned_ids); + } +} diff --git a/tests/StrictUrlSearchParamsSerializerTest.php b/tests/StrictUrlSearchParamsSerializerTest.php new file mode 100644 index 00000000..e75affc3 --- /dev/null +++ b/tests/StrictUrlSearchParamsSerializerTest.php @@ -0,0 +1,45 @@ +assertSame("", StrictUrlSearchParamsSerializer::serialize([])); + $this->assertSame( + "foo=d&_strict=true", + StrictUrlSearchParamsSerializer::serialize(["foo" => "d"]), + ); + } + + public function testAppendsStrictAfterTheSortedParams(): void + { + $this->assertSame( + "B=2&a=1&_strict=true", + StrictUrlSearchParamsSerializer::serialize(["a" => 1, "B" => 2]), + ); + } + + public function testReplacesACallerSuppliedStrictParam(): void + { + $this->assertSame( + "_strict=true", + StrictUrlSearchParamsSerializer::serialize(["_strict" => false]), + ); + } + + public function testUpdateCountsExistingParamsAsNonEmpty(): void + { + $search_params = new UrlSearchParams([["foo", "bar"]]); + StrictUrlSearchParamsSerializer::update($search_params, []); + + $this->assertSame("foo=bar&_strict=true", $search_params->to_string()); + } +} diff --git a/tests/Support/FakeSeamConnect.php b/tests/Support/FakeSeamConnect.php new file mode 100644 index 00000000..7a8cbe2e --- /dev/null +++ b/tests/Support/FakeSeamConnect.php @@ -0,0 +1,170 @@ + */ + private array $pipes = []; + + public static function start(): self + { + $fake = new self(); + $fake->run(); + + return $fake; + } + + public function endpoint(): string + { + return $this->endpoint; + } + + /** + * The ids and tokens of the seeded records. + */ + public function seed(): array + { + return $this->seed; + } + + private function run(): void + { + $binary = dirname(__DIR__, 2) . "/node_modules/.bin/fake-seam-connect"; + + if (!is_executable($binary)) { + throw new \RuntimeException( + "Could not find {$binary}, run npm install before the tests.", + ); + } + + $port = self::unused_port(); + $this->endpoint = "http://127.0.0.1:{$port}"; + + // The binary is spawned directly rather than through npm so the + // process handle is the server itself and stopping it does not leave + // an orphan behind. PORT goes to the child only, leaving the parent + // environment alone for the tests that read it. + $this->process = proc_open( + [$binary, "--seed"], + [ + 0 => ["file", "/dev/null", "r"], + 1 => ["file", "/dev/null", "w"], + 2 => ["file", "/dev/null", "w"], + ], + $this->pipes, + dirname(__DIR__, 2), + ["PORT" => (string) $port] + getenv(), + ); + + if (!is_resource($this->process)) { + throw new \RuntimeException("Could not start Fake Seam Connect."); + } + + $this->wait_for_health(); + $this->seed = $this->fetch_seed(); + } + + public function stop(): void + { + if (!is_resource($this->process)) { + return; + } + + proc_terminate($this->process, SIGTERM); + + $deadline = microtime(true) + self::SHUTDOWN_TIMEOUT; + while (microtime(true) < $deadline) { + if (!proc_get_status($this->process)["running"]) { + break; + } + usleep(self::POLL_INTERVAL); + } + + if (proc_get_status($this->process)["running"]) { + proc_terminate($this->process, SIGKILL); + } + + proc_close($this->process); + $this->process = null; + } + + private function wait_for_health(): void + { + $deadline = microtime(true) + self::STARTUP_TIMEOUT; + + while (microtime(true) < $deadline) { + if (!proc_get_status($this->process)["running"]) { + throw new \RuntimeException( + "Fake Seam Connect exited before becoming healthy.", + ); + } + + if ($this->get("/health") !== null) { + return; + } + + usleep(self::POLL_INTERVAL); + } + + throw new \RuntimeException( + "Fake Seam Connect did not become healthy within " . + self::STARTUP_TIMEOUT . + "s.", + ); + } + + private function fetch_seed(): array + { + $body = $this->get("/_fake/default_seed"); + + if ($body === null) { + throw new \RuntimeException( + "Could not read the seed from Fake Seam Connect.", + ); + } + + return json_decode($body, true); + } + + private function get(string $path): ?string + { + $context = stream_context_create([ + "http" => ["timeout" => 5, "ignore_errors" => true], + ]); + + $body = @file_get_contents($this->endpoint . $path, false, $context); + + return $body === false ? null : $body; + } + + private static function unused_port(): int + { + $socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr); + + if ($socket === false) { + throw new \RuntimeException( + "Could not find an unused port: {$errstr}", + ); + } + + $name = stream_socket_get_name($socket, false); + fclose($socket); + + return (int) substr($name, strrpos($name, ":") + 1); + } +} diff --git a/tests/Support/FakeSeamConnectTestCase.php b/tests/Support/FakeSeamConnectTestCase.php new file mode 100644 index 00000000..81f91400 --- /dev/null +++ b/tests/Support/FakeSeamConnectTestCase.php @@ -0,0 +1,45 @@ +fake = FakeSeamConnect::start(); + $this->endpoint = $this->fake->endpoint(); + $this->seed = $this->fake->seed(); + } + + protected function tearDown(): void + { + $this->fake->stop(); + } + + /** + * A client authorized against the fake with the seeded API key. + */ + protected function seam( + bool|array|null $wait_for_action_attempt = null, + array $guzzle_options = [], + ?int $retries = null, + ): Seam { + return new Seam( + api_key: $this->seed["seam_apikey1_token"], + endpoint: $this->endpoint, + wait_for_action_attempt: $wait_for_action_attempt, + guzzle_options: $guzzle_options, + retries: $retries, + ); + } +} diff --git a/tests/Support/RecordingClient.php b/tests/Support/RecordingClient.php new file mode 100644 index 00000000..d2494332 --- /dev/null +++ b/tests/Support/RecordingClient.php @@ -0,0 +1,107 @@ + */ + public array $transactions = []; + + private MockHandler $mock; + + /** + * @param array $responses Served in order; the last one repeats. + */ + public function __construct(private array $responses) + { + $this->mock = new MockHandler($responses); + } + + /** + * Builds the Guzzle options to hand to a Seam client so its requests land + * here instead of on the network. + * + * @return array + */ + public function guzzle_options(): array + { + $stack = HandlerStack::create($this->mock); + $stack->push(Middleware::history($this->transactions)); + + return ["handler" => $stack]; + } + + /** + * How many logical calls the SDK made. Retries are invisible here because + * the SDK pushes its retry middleware inside this recorder; use + * attempt_count() to count those. + */ + public function request_count(): int + { + return count($this->transactions); + } + + /** + * How many requests actually reached the handler, retries included. + */ + public function attempt_count(): int + { + return count($this->responses) - $this->mock->count(); + } + + public function request(int $index = 0): RequestInterface + { + return $this->transactions[$index]["request"]; + } + + public function body(int $index = 0): mixed + { + return json_decode((string) $this->request($index)->getBody()); + } + + /** + * A response whose body repeats for every request, so retries keep + * failing the same way. + */ + public static function repeating(Response $response, int $times = 20): self + { + return new self(array_fill(0, $times, $response)); + } + + public static function repeating_throwable( + \Throwable $error, + int $times = 20, + ): self { + return new self(array_fill(0, $times, $error)); + } + + public static function json(int $status, mixed $body): Response + { + return new Response( + $status, + ["content-type" => "application/json"], + json_encode($body), + ); + } + + public static function raw( + int $status, + string $body, + string $content_type = "text/plain", + ): Response { + return new Response($status, ["content-type" => $content_type], $body); + } +} diff --git a/tests/UrlSearchParamsSerializerTest.php b/tests/UrlSearchParamsSerializerTest.php new file mode 100644 index 00000000..c228ff24 --- /dev/null +++ b/tests/UrlSearchParamsSerializerTest.php @@ -0,0 +1,763 @@ +assertSame("", self::serialize([])); + $this->assertSame("", self::serialize(new \stdClass())); + } + + public function testSerializesString(): void + { + $this->assertSame("foo=d", self::serialize(["foo" => "d"])); + $this->assertSame("foo=null", self::serialize(["foo" => "null"])); + $this->assertSame( + "foo=undefined", + self::serialize(["foo" => "undefined"]), + ); + $this->assertSame("foo=0", self::serialize(["foo" => "0"])); + } + + public function testRemovesTheEmptyString(): void + { + $this->assertSame("", self::serialize(["foo" => ""])); + $this->assertSame( + "foo=d", + self::serialize(["foo" => "d", "bar" => ""]), + ); + } + + public function testSerializesInt(): void + { + $this->assertSame("foo=1", self::serialize(["foo" => 1])); + $this->assertSame("foo=0", self::serialize(["foo" => 0])); + $this->assertSame("foo=-42", self::serialize(["foo" => -42])); + } + + public function testSerializesLargeIntWithFullPrecision(): void + { + $this->assertSame( + "foo=9007199254740993", + self::serialize(["foo" => 9007199254740993]), + ); + $this->assertSame( + "foo=9223372036854775807", + self::serialize(["foo" => PHP_INT_MAX]), + ); + } + + public function testSerializesFloat(): void + { + $this->assertSame("foo=23.8", self::serialize(["foo" => 23.8])); + $this->assertSame("foo=-23.8", self::serialize(["foo" => -23.8])); + $this->assertSame( + "foo=0.30000000000000004", + self::serialize(["foo" => 0.1 + 0.2]), + ); + } + + public function testSerializesFloatUsingTheEcmascriptNumberFormat(): void + { + $this->assertSame("foo=1", self::serialize(["foo" => 1.0])); + $this->assertSame("foo=0", self::serialize(["foo" => -0.0])); + $this->assertSame("foo=100", self::serialize(["foo" => 100.0])); + $this->assertSame( + "foo=10000000000000000", + self::serialize(["foo" => 1e16]), + ); + $this->assertSame( + "foo=100000000000000000000", + self::serialize(["foo" => 1e20]), + ); + $this->assertSame("foo=1e%2B21", self::serialize(["foo" => 1e21])); + $this->assertSame("foo=0.0001", self::serialize(["foo" => 0.0001])); + $this->assertSame("foo=0.000001", self::serialize(["foo" => 1e-6])); + $this->assertSame("foo=1e-7", self::serialize(["foo" => 1e-7])); + $this->assertSame("foo=5e-324", self::serialize(["foo" => 5e-324])); + $this->assertSame( + "foo=1.7976931348623157e%2B308", + self::serialize(["foo" => PHP_FLOAT_MAX]), + ); + } + + /** + * @dataProvider serializePrecisionSettings + */ + public function testSerializesFloatIndependentlyOfSerializePrecision( + string $setting, + ): void { + $original = ini_get("serialize_precision"); + + try { + ini_set("serialize_precision", $setting); + + $this->assertSame("foo=23.8", self::serialize(["foo" => 23.8])); + $this->assertSame( + "foo=0.30000000000000004", + self::serialize(["foo" => 0.1 + 0.2]), + ); + $this->assertSame( + "foo=1.7976931348623157e%2B308", + self::serialize(["foo" => PHP_FLOAT_MAX]), + ); + + $this->assertSame($setting, ini_get("serialize_precision")); + } finally { + if ($original !== false) { + ini_set("serialize_precision", $original); + } + } + } + + public static function serializePrecisionSettings(): array + { + return [ + "shortest round trip" => ["-1"], + "seventeen digits" => ["17"], + "php default precision" => ["14"], + "very low" => ["3"], + ]; + } + + public function testSerializesBool(): void + { + $this->assertSame("foo=true", self::serialize(["foo" => true])); + $this->assertSame("foo=false", self::serialize(["foo" => false])); + $this->assertSame( + "bar=false&foo=true", + self::serialize(["foo" => true, "bar" => false]), + ); + } + + public function testRemovesNullParams(): void + { + $this->assertSame("", self::serialize(["bar" => null])); + $this->assertSame( + "foo=1", + self::serialize(["foo" => 1, "bar" => null]), + ); + } + + public function testSerializesNullValueParams(): void + { + $this->assertSame("bar=", self::serialize(["bar" => NullValue::NULL])); + $this->assertSame( + "bar=&foo=1", + self::serialize(["foo" => 1, "bar" => NullValue::NULL]), + ); + } + + public function testRemovesNullParamsAtAnyDepth(): void + { + $this->assertSame( + "foo.baz=1", + self::serialize(["foo" => ["bar" => null, "baz" => 1]]), + ); + $this->assertSame("", self::serialize(["foo" => ["bar" => null]])); + } + + public function testSerializesEmptyArrayParams(): void + { + $this->assertSame("bar=", self::serialize(["bar" => []])); + $this->assertSame( + "bar=&foo=1", + self::serialize(["foo" => 1, "bar" => []]), + ); + } + + public function testSerializesArrayParamsWithOneValue(): void + { + $this->assertSame("bar=a", self::serialize(["bar" => ["a"]])); + $this->assertSame( + "bar=a&foo=1", + self::serialize(["foo" => 1, "bar" => ["a"]]), + ); + } + + public function testSerializesArrayParamsWithManyValues(): void + { + $this->assertSame( + "bar=a&bar=2&foo=1", + self::serialize(["foo" => 1, "bar" => ["a", "2"]]), + ); + $this->assertSame( + "bar=null&bar=2&bar=undefined&foo=1", + self::serialize(["foo" => 1, "bar" => ["null", "2", "undefined"]]), + ); + } + + public function testSerializesArrayParamsWithMixedValues(): void + { + $this->assertSame( + "bar=1&bar=a&bar=true&bar=1970-01-01T00%3A00%3A00.000Z", + self::serialize([ + "bar" => [ + 1, + "a", + true, + new \DateTimeImmutable("1970-01-01T00:00:00Z"), + ], + ]), + ); + } + + public function testSerializesDatetime(): void + { + $this->assertSame( + "foo=1&now=2025-02-24T18%3A44%3A39.000Z", + self::serialize([ + "foo" => 1, + "now" => new \DateTimeImmutable("2025-02-24T18:44:39Z"), + ]), + ); + } + + public function testSerializesMutableDatetime(): void + { + $now = new \DateTime("2025-02-24T18:44:39Z"); + + $this->assertSame( + "now=2025-02-24T18%3A44%3A39.000Z", + self::serialize(["now" => $now]), + ); + $this->assertSame("2025-02-24T18:44:39+00:00", $now->format("c")); + } + + public function testSerializesDatetimeWithMilliseconds(): void + { + $this->assertSame( + "now=2025-02-24T18%3A44%3A39.123Z", + self::serialize([ + "now" => new \DateTimeImmutable("2025-02-24T18:44:39.123Z"), + ]), + ); + } + + public function testTruncatesDatetimeMicroseconds(): void + { + $this->assertSame( + "now=2025-02-24T18%3A44%3A39.123Z", + self::serialize([ + "now" => new \DateTimeImmutable("2025-02-24T18:44:39.123999Z"), + ]), + ); + } + + public function testSerializesDatetimeAsUtc(): void + { + $this->assertSame( + "now=2025-02-24T18%3A44%3A39.000Z", + self::serialize([ + "now" => new \DateTimeImmutable("2025-02-24T13:44:39-05:00"), + ]), + ); + } + + public function testSerializesDatetimeBeforeTheEpoch(): void + { + $this->assertSame( + "then=1969-12-31T23%3A59%3A59.000Z", + self::serialize([ + "then" => new \DateTimeImmutable("1969-12-31T23:59:59Z"), + ]), + ); + } + + /** + * @dataProvider expandedYears + */ + public function testSerializesTheExpandedYear( + int $year, + string $expected, + ): void { + $date = (new \DateTimeImmutable("2000-01-02T03:04:05Z"))->setDate( + $year, + 1, + 2, + ); + + $this->assertSame( + "then=" . rawurlencode($expected), + self::serialize(["then" => $date]), + ); + } + + public static function expandedYears(): array + { + return [ + "five digits" => [12345, "+012345-01-02T03:04:05.000Z"], + "six digits" => [275760, "+275760-01-02T03:04:05.000Z"], + "negative" => [-1, "-000001-01-02T03:04:05.000Z"], + "negative four digits" => [-2024, "-002024-01-02T03:04:05.000Z"], + ]; + } + + public function testZeroPadsTheYearToFourDigits(): void + { + $this->assertSame( + "then=0050-01-02T03%3A04%3A05.000Z", + self::serialize([ + "then" => new \DateTimeImmutable("0050-01-02T03:04:05Z"), + ]), + ); + } + + public function testSerializesNestedParams(): void + { + $this->assertSame( + "bar.baz=a&foo=1", + self::serialize(["foo" => 1, "bar" => ["baz" => "a"]]), + ); + + $this->assertSame( + "bar.baz.x.z=1&foo=1", + self::serialize([ + "foo" => 1, + "bar" => ["baz" => ["x" => ["z" => 1]]], + ]), + ); + + $this->assertSame( + "bar.baz.x.z=&foo=1", + self::serialize([ + "foo" => 1, + "bar" => ["baz" => ["x" => ["z" => NullValue::NULL]]], + ]), + ); + + $this->assertSame( + "bar.baz=1&bar.baz=a&foo=1", + self::serialize(["foo" => 1, "bar" => ["baz" => [1, "a"]]]), + ); + + $this->assertSame( + "bar=2", + self::serialize(["foo" => new \stdClass(), "bar" => 2]), + ); + + $this->assertSame( + "bar=2", + self::serialize(["foo" => ["x" => new \stdClass()], "bar" => 2]), + ); + + $this->assertSame( + "bar.baz.x.z=", + self::serialize([ + "foo" => new \stdClass(), + "bar" => [ + "baz" => [ + "x" => ["z" => NullValue::NULL, "t" => new \stdClass()], + "q" => new \stdClass(), + ], + ], + ]), + ); + } + + public function testSerializesStdClassParams(): void + { + $this->assertSame( + "bar.baz=a&foo=1", + self::serialize( + (object) ["foo" => 1, "bar" => (object) ["baz" => "a"]], + ), + ); + } + + public function testSortsParamsByName(): void + { + $this->assertSame( + "a=2&b=1&c=3", + self::serialize(["b" => 1, "a" => 2, "c" => 3]), + ); + $this->assertSame( + "A=2&B=4&a=3&b=1", + self::serialize(["b" => 1, "A" => 2, "a" => 3, "B" => 4]), + ); + $this->assertSame( + "a1=3&a10=1&a2=2", + self::serialize(["a10" => 1, "a2" => 2, "a1" => 3]), + ); + $this->assertSame( + "a.b=3&a.z=2&zz=1", + self::serialize(["zz" => 1, "a" => ["z" => 2, "b" => 3]]), + ); + $this->assertSame( + "a.b=2&ab=1", + self::serialize(["ab" => 1, "a" => ["b" => 2]]), + ); + } + + public function testSortingPreservesArrayOrder(): void + { + $this->assertSame( + "a=1&b=3&b=1&b=2", + self::serialize(["b" => ["3", "1", "2"], "a" => 1]), + ); + } + + public function testEncodesParamsAsFormUrlencoded(): void + { + $this->assertSame("foo=a+b", self::serialize(["foo" => "a b"])); + $this->assertSame("foo=a%2Bb", self::serialize(["foo" => "a+b"])); + $this->assertSame("foo=a%7Eb", self::serialize(["foo" => "a~b"])); + $this->assertSame("foo=a*b", self::serialize(["foo" => "a*b"])); + $this->assertSame( + "foo=abcXYZ019*-._", + self::serialize(["foo" => "abcXYZ019*-._"]), + ); + $this->assertSame("foo=a+*%7E+b", self::serialize(["foo" => "a *~ b"])); + $this->assertSame( + "foo=a%26b%3Dc%3Fd%23e%2Ff", + self::serialize(["foo" => "a&b=c?d#e/f"]), + ); + $this->assertSame("foo=100%25", self::serialize(["foo" => "100%"])); + $this->assertSame("foo=a%0Ab", self::serialize(["foo" => "a\nb"])); + } + + public function testEncodesUnicodeParams(): void + { + $this->assertSame( + "foo=h%C3%A9llo+w%C3%B6rld", + self::serialize(["foo" => "héllo wörld"]), + ); + $this->assertSame( + "foo=%E6%97%A5%E6%9C%AC%E8%AA%9E", + self::serialize(["foo" => "日本語"]), + ); + $this->assertSame("%F0%9F%94%92=a", self::serialize(["🔒" => "a"])); + $this->assertSame("a+b=1", self::serialize(["a b" => 1])); + } + + public function testCannotSerializeKeysContainingADot(): void + { + $this->expectException(UnserializableParamError::class); + + self::serialize(["foo.bar" => 1]); + } + + public function testCannotSerializeNestedKeysContainingADot(): void + { + $this->expectException(UnserializableParamError::class); + + self::serialize(["foo" => ["bar.baz" => 1]]); + } + + public function testCannotSerializeNonStringKeys(): void + { + $this->expectException(UnserializableParamError::class); + + self::serialize(["foo" => [1 => "a", "b" => "c"]]); + } + + public function testCannotSerializeClosures(): void + { + $this->expectException(UnserializableParamError::class); + + self::serialize(["foo" => fn() => null]); + } + + /** + * @dataProvider provideNonFiniteFloats + */ + public function testCannotSerializeNonFiniteFloats( + float $value, + string $message, + ): void { + try { + self::serialize(["foo" => $value]); + $this->fail("Expected an UnserializableParamError"); + } catch (UnserializableParamError $error) { + $this->assertSame( + "Could not serialize parameter: 'foo' {$message}", + $error->getMessage(), + ); + $this->assertSame("foo", $error->getName()); + } + } + + public static function provideNonFiniteFloats(): array + { + return [ + "NaN" => [NAN, "is NaN"], + "Infinity" => [INF, "is Infinity"], + "-Infinity" => [-INF, "is -Infinity"], + ]; + } + + public function testCannotSerializeArbitraryObjects(): void + { + $this->expectException(UnserializableParamError::class); + + self::serialize([ + "foo" => new class { + public string $device_id = "a"; + }, + ]); + } + + /** + * @dataProvider provideUnserializableArrays + */ + public function testCannotSerializeArrayParamsWithUnserializableValues( + array $params, + ): void { + $this->expectException(UnserializableParamError::class); + + self::serialize($params); + } + + public static function provideUnserializableArrays(): array + { + return [ + "single empty string" => [["foo" => [""]]], + "null element" => [["bar" => ["a", null]]], + "NullValue element" => [["bar" => ["a", NullValue::NULL]]], + "nested list" => [["bar" => ["a", ["s"]]]], + "nested empty list" => [["bar" => ["a", []]]], + "nested list with empty string" => [["bar" => ["a", [""]]]], + "nested object" => [["bar" => ["a", new \stdClass()]]], + "nested map" => [["bar" => ["a", ["x" => 2]]]], + "closure element" => [["bar" => ["a", fn() => null]]], + "empty strings around values" => [ + ["foo" => 1, "bar" => ["", "a", ""]], + ], + "leading empty string" => [["foo" => 1, "bar" => ["", "a", "2"]]], + "only empty strings" => [["foo" => 1, "bar" => ["", "", ""]]], + "NaN element" => [["foo" => [1, NAN]]], + ]; + } + + public function testUnserializableParamErrorMessage(): void + { + try { + self::serialize(["foo" => ["bar.baz" => 1]]); + $this->fail("Expected an UnserializableParamError"); + } catch (UnserializableParamError $error) { + $this->assertSame( + "Could not serialize parameter: 'bar.baz' contains one or " . + 'more dots "." in its name which is unsupported', + $error->getMessage(), + ); + $this->assertSame("bar.baz", $error->getName()); + } + } + + public function testUnserializableParamErrorMessageUsesTheFullPath(): void + { + try { + self::serialize(["foo" => ["bar" => NAN]]); + $this->fail("Expected an UnserializableParamError"); + } catch (UnserializableParamError $error) { + $this->assertSame( + "Could not serialize parameter: 'foo.bar' is NaN", + $error->getMessage(), + ); + } + } + + public function testUpdateUrlSearchParams(): void + { + $search_params = new UrlSearchParams(); + UrlSearchParamsSerializer::update($search_params, [ + "foo" => "d", + "bar" => 2, + ]); + + $this->assertSame("bar=2&foo=d", $search_params->to_string()); + } + + public function testUpdatePreservesExistingParams(): void + { + $search_params = new UrlSearchParams([["foo", "bar"]]); + UrlSearchParamsSerializer::update($search_params, [ + "name" => "Dax", + "age" => 27, + "is_admin" => true, + "tags" => ["cars", "planes"], + ]); + + $this->assertSame( + "age=27&foo=bar&is_admin=true&name=Dax&tags=cars&tags=planes", + $search_params->to_string(), + ); + } + + public function testUpdateOverwritesExistingParams(): void + { + $search_params = new UrlSearchParams([ + ["foo", "a"], + ["bar", "x"], + ["foo", "b"], + ]); + UrlSearchParamsSerializer::update($search_params, ["foo" => "new"]); + + $this->assertSame("bar=x&foo=new", $search_params->to_string()); + } + + public function testUpdateAppendsArrayParams(): void + { + $search_params = new UrlSearchParams([["foo", "old"]]); + UrlSearchParamsSerializer::update($search_params, ["foo" => [1, 2]]); + + $this->assertSame("foo=old&foo=1&foo=2", $search_params->to_string()); + } + + public function testUpdateKeepsExistingParamsForAbsentValues(): void + { + foreach ([null, "", new \stdClass()] as $value) { + $search_params = new UrlSearchParams([["foo", "a"]]); + UrlSearchParamsSerializer::update($search_params, [ + "foo" => $value, + ]); + + $this->assertSame("foo=a", $search_params->to_string()); + } + } + + public function testUrlSearchParamsFromQueryString(): void + { + $search_params = new UrlSearchParams( + "?a=1&b=hello+world&c=%F0%9F%94%92&d", + ); + + $this->assertSame("1", $search_params->get("a")); + $this->assertSame("hello world", $search_params->get("b")); + $this->assertSame("🔒", $search_params->get("c")); + $this->assertSame("", $search_params->get("d")); + $this->assertSame( + "a=1&b=hello+world&c=%F0%9F%94%92&d=", + $search_params->to_string(), + ); + } + + public function testUrlSearchParamsFromMap(): void + { + $this->assertSame( + "a=1&b=2", + (new UrlSearchParams(["a" => "1", "b" => "2"]))->to_string(), + ); + } + + public function testUrlSearchParamsFromMapExpandsAList(): void + { + $this->assertSame( + "device_ids=d1&device_ids=d2", + (new UrlSearchParams([ + "device_ids" => ["d1", "d2"], + ]))->to_string(), + ); + } + + public function testUrlSearchParamsFromMapRendersValuesLikeTheStandard(): void + { + $params = ["t" => true, "f" => false, "n" => 42, "u" => null]; + + $this->assertSame( + "t=true&f=false&n=42&u=", + (new UrlSearchParams($params))->to_string(), + ); + + $this->assertSame( + "f=false&n=42&t=true&u=", + UrlSearchParamsSerializer::serialize([ + "t" => true, + "f" => false, + "n" => 42, + "u" => NullValue::NULL, + ]), + ); + } + + /** + * @dataProvider valuesTheMapFormCannotRender + */ + public function testUrlSearchParamsFromMapRejectsWhatItCannotRender( + mixed $value, + ): void { + $this->expectException(UnserializableParamError::class); + + new UrlSearchParams(["a" => $value]); + } + + public static function valuesTheMapFormCannotRender(): array + { + return [ + "float" => [20.5], + "date" => [new \DateTimeImmutable("2024-01-01T00:00:00Z")], + "object" => [new \stdClass()], + ]; + } + + public function testUrlSearchParamsAppendAndGet(): void + { + $search_params = new UrlSearchParams(); + $search_params->append("foo", "a"); + $search_params->append("foo", "b"); + + $this->assertSame("a", $search_params->get("foo")); + $this->assertSame(["a", "b"], $search_params->get_all("foo")); + $this->assertNull($search_params->get("bar")); + $this->assertSame([], $search_params->get_all("bar")); + $this->assertCount(2, $search_params); + $this->assertSame( + [["foo", "a"], ["foo", "b"]], + iterator_to_array($search_params), + ); + } + + public function testUrlSearchParamsSetKeepsTheFirstPairsPosition(): void + { + $search_params = new UrlSearchParams([ + ["foo", "a"], + ["bar", "x"], + ["foo", "b"], + ]); + $search_params->set("foo", "c"); + + $this->assertSame( + [["foo", "c"], ["bar", "x"]], + iterator_to_array($search_params), + ); + + $search_params->set("baz", "y"); + + $this->assertSame("y", $search_params->get("baz")); + } + + public function testUrlSearchParamsHasAndDelete(): void + { + $search_params = new UrlSearchParams([["foo", "a"], ["foo", "b"]]); + + $this->assertTrue($search_params->has("foo")); + + $search_params->delete("foo"); + + $this->assertFalse($search_params->has("foo")); + $this->assertCount(0, $search_params); + } + + public function testUrlSearchParamsCastsToString(): void + { + $this->assertSame( + "foo=a+b", + (string) new UrlSearchParams([["foo", "a b"]]), + ); + } +} diff --git a/tests/PackageVersionTest.php b/tests/VersionTest.php similarity index 71% rename from tests/PackageVersionTest.php rename to tests/VersionTest.php index d45e970d..b6f5c209 100644 --- a/tests/PackageVersionTest.php +++ b/tests/VersionTest.php @@ -3,9 +3,9 @@ declare(strict_types=1); use PHPUnit\Framework\TestCase; -use Seam\Utils\PackageVersion; +use Seam\Version; -final class PackageVersionTest extends TestCase +final class VersionTest extends TestCase { public function testVersionMatchesPackageJson(): void { @@ -19,8 +19,8 @@ public function testVersionMatchesPackageJson(): void $this->assertSame( $package["version"], - PackageVersion::get(), - "Seam\\Utils\\PackageVersion is out of date with package.json. " . + Version::get(), + "Seam\\Version is out of date with package.json. " . "It is injected by the version lifecycle script when a " . "version is cut and should not be edited by hand.", ); @@ -28,9 +28,9 @@ public function testVersionMatchesPackageJson(): void public function testVersionIsUsedAsTheSdkVersionHeader(): void { - $seam = new \Seam\SeamClient("seam_apikey1_token"); + $seam = new \Seam\Seam("seam_apikey1_token"); $headers = $seam->client->getConfig("headers"); - $this->assertSame(PackageVersion::get(), $headers["seam-sdk-version"]); + $this->assertSame(Version::get(), $headers["seam-sdk-version"]); } } diff --git a/tests/WaitForActionAttemptTest.php b/tests/WaitForActionAttemptTest.php new file mode 100644 index 00000000..00906988 --- /dev/null +++ b/tests/WaitForActionAttemptTest.php @@ -0,0 +1,449 @@ + "attempt_1", + "action_type" => "FUTURE_ACTION", + "status" => "future_status", + ], + ); + + $this->assertSame(ActionAttempt::class, $attempt::class); + $this->assertSame("FUTURE_ACTION", $attempt->action_type); + $this->assertSame("future_status", $attempt->status); + } + + private function pending_action_attempt(Seam $seam): ActionAttempt + { + $action_attempt = $seam->locks->unlock_door( + $this->seed["august_device_1"], + ); + + $this->assertInstanceOf(UnlockDoor::class, $action_attempt); + $this->assertSame("pending", $action_attempt->status); + $this->assertSame( + Status::PENDING, + Status::tryFrom($action_attempt->status), + ); + $this->assertNull($action_attempt->error); + $this->assertNull($action_attempt->result); + + $this->set_status($seam, $action_attempt, "pending"); + + return $action_attempt; + } + + /** + * A list of action attempts is returned as is: only a single returned + * attempt is ever resolved, so listing must not poll pending attempts. + */ + public function testListReturnsActionAttemptsWithoutResolvingThem(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + $pending = $this->pending_action_attempt($seam); + + $attempts = $seam->action_attempts->list( + action_attempt_ids: [$pending->action_attempt_id], + ); + + $this->assertContainsOnlyInstancesOf(ActionAttempt::class, $attempts); + $this->assertCount(1, $attempts); + $this->assertSame( + $pending->action_attempt_id, + $attempts[0]->action_attempt_id, + ); + $this->assertSame("pending", $attempts[0]->status); + } + + private function set_status( + Seam $seam, + ActionAttempt $action_attempt, + string $status, + ?array $error = null, + ): void { + $seam->client->request("POST", "/_fake/update_action_attempt", [ + "json" => (object) array_filter([ + "action_attempt_id" => $action_attempt->action_attempt_id, + "status" => $status, + "error" => $error, + ]), + ]); + } + + public function testWaitsByDefault(): void + { + $action_attempt = $this->seam()->locks->unlock_door( + $this->seed["august_device_1"], + ); + + $this->assertSame("success", $action_attempt->status); + } + + public function testClientDefaultCanDisableWaiting(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + + $action_attempt = $seam->locks->unlock_door( + $this->seed["august_device_1"], + ); + + $this->assertSame("pending", $action_attempt->status); + } + + /** + * The options form of the client default has to wait just like `true` + * does; treating it as "no waiting" would hand back a pending attempt + * with no indication anything was skipped. + */ + public function testClientDefaultCanBeAnOptionsArray(): void + { + $seam = $this->seam( + wait_for_action_attempt: [ + "timeout" => 5.0, + "polling_interval" => 0.05, + ], + ); + + $action_attempt = $seam->locks->unlock_door( + $this->seed["august_device_1"], + ); + + $this->assertSame("success", $action_attempt->status); + } + + public function testPerCallOptionCanDisableWaiting(): void + { + $action_attempt = $this->seam()->locks->unlock_door( + $this->seed["august_device_1"], + wait_for_action_attempt: false, + ); + + $this->assertSame("pending", $action_attempt->status); + } + + public function testPerCallOptionCanEnableWaiting(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + + $action_attempt = $seam->locks->unlock_door( + $this->seed["august_device_1"], + wait_for_action_attempt: true, + ); + + $this->assertSame("success", $action_attempt->status); + } + + public function testReturnsAnAlreadySuccessfulActionAttempt(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + + $action_attempt = $seam->locks->unlock_door( + $this->seed["august_device_1"], + ); + $this->set_status($seam, $action_attempt, "success"); + + $resolved = $seam->action_attempts->get( + $action_attempt->action_attempt_id, + wait_for_action_attempt: true, + ); + + $this->assertSame("success", $resolved->status); + $this->assertSame( + $action_attempt->action_attempt_id, + $resolved->action_attempt_id, + ); + } + + /** + * Proves the resolver really re-reads the action attempt: it starts out + * pending and is moved to success by something outside this process, the + * way the JavaScript and Ruby suites do it. + */ + public function testWaitsForAnActionAttemptResolvedOutOfBand(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + $action_attempt = $this->pending_action_attempt($seam); + + $resolver = $this->resolve_after( + $action_attempt->action_attempt_id, + 0.5, + ); + + try { + $resolved = $seam->action_attempts->get( + $action_attempt->action_attempt_id, + wait_for_action_attempt: [ + "timeout" => 15.0, + "polling_interval" => 0.1, + ], + ); + + $this->assertSame("success", $resolved->status); + } finally { + proc_close($resolver); + } + } + + /** + * @return resource + */ + private function resolve_after(string $action_attempt_id, float $delay) + { + $payload = json_encode([ + "action_attempt_id" => $action_attempt_id, + "status" => "success", + ]); + + $script = sprintf( + "usleep(%d); file_get_contents(%s, false, stream_context_create(%s));", + (int) ($delay * 1000000.0), + var_export($this->endpoint . "/_fake/update_action_attempt", true), + var_export( + [ + "http" => [ + "method" => "POST", + "header" => "Content-Type: application/json", + "content" => $payload, + "ignore_errors" => true, + ], + ], + true, + ), + ); + + $process = proc_open( + [PHP_BINARY, "-r", $script], + [ + 1 => ["file", "/dev/null", "w"], + 2 => ["file", "/dev/null", "w"], + ], + $pipes, + ); + + if (!is_resource($process)) { + $this->fail("Could not start the out of band resolver"); + } + + return $process; + } + + public function testThrowsWhenTheActionAttemptFails(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + + $action_attempt = $seam->locks->unlock_door( + $this->seed["august_device_1"], + ); + $this->set_status($seam, $action_attempt, "error", [ + "type" => "foo", + "message" => "Failed", + ]); + + try { + $seam->action_attempts->get( + $action_attempt->action_attempt_id, + wait_for_action_attempt: true, + ); + $this->fail("Expected ActionAttemptFailedError"); + } catch (ActionAttemptFailedError $error) { + $this->assertSame("Failed", $error->getMessage()); + $this->assertSame("foo", $error->getErrorCode()); + $this->assertSame("error", $error->getActionAttempt()->status); + $this->assertSame( + $action_attempt->action_attempt_id, + $error->getActionAttempt()->action_attempt_id, + ); + $this->assertInstanceOf(ActionAttemptError::class, $error); + } + } + + public function testTimesOutWhileTheActionAttemptIsPending(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + $action_attempt = $this->pending_action_attempt($seam); + + try { + $seam->action_attempts->get( + $action_attempt->action_attempt_id, + wait_for_action_attempt: [ + "timeout" => 0.2, + "polling_interval" => 5.0, + ], + ); + $this->fail("Expected ActionAttemptTimeoutError"); + } catch (ActionAttemptTimeoutError $error) { + $this->assertSame( + $action_attempt->action_attempt_id, + $error->getActionAttempt()->action_attempt_id, + ); + $this->assertStringContainsString( + "Timed out waiting for action attempt", + $error->getMessage(), + ); + } + } + + public function testPollsOnceWhenTheIntervalOutlastsTheTimeout(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + $action_attempt = $this->pending_action_attempt($seam); + + $started_at = microtime(true); + + try { + $seam->action_attempts->get( + $action_attempt->action_attempt_id, + wait_for_action_attempt: [ + "timeout" => 0.3, + "polling_interval" => 30.0, + ], + ); + $this->fail("Expected ActionAttemptTimeoutError"); + } catch (ActionAttemptTimeoutError) { + $elapsed = microtime(true) - $started_at; + + $this->assertGreaterThanOrEqual(0.3, $elapsed); + $this->assertLessThan(5.0, $elapsed); + } + } + + public function testResolvesWhenTheIntervalOutlastsTheTimeout(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + $action_attempt = $this->pending_action_attempt($seam); + $this->set_status($seam, $action_attempt, "success"); + + $resolved = $seam->action_attempts->get( + $action_attempt->action_attempt_id, + wait_for_action_attempt: [ + "timeout" => 0.3, + "polling_interval" => 30.0, + ], + ); + + $this->assertSame("success", $resolved->status); + } + + /** + * @dataProvider invalidWaitOptions + */ + public function testRejectsInvalidWaitOptions( + array $wait_for_action_attempt, + string $expected_message, + ): void { + $seam = $this->seam(wait_for_action_attempt: false); + $action_attempt = $this->pending_action_attempt($seam); + + $this->expectException(InvalidOptionsError::class); + $this->expectExceptionMessage($expected_message); + + $seam->action_attempts->get( + $action_attempt->action_attempt_id, + wait_for_action_attempt: $wait_for_action_attempt, + ); + } + + public static function invalidWaitOptions(): array + { + return [ + "zero polling_interval" => [ + ["polling_interval" => 0], + "polling_interval option must be greater than zero", + ], + "negative polling_interval" => [ + ["polling_interval" => -5], + "polling_interval option must be greater than zero", + ], + "negative timeout" => [ + ["timeout" => -1], + "timeout option must not be negative", + ], + ]; + } + + /** + * Resolving fetches the action attempt through the HTTP client rather + * than the route client, so enabling the option on the route that reads + * action attempts cannot recurse. + */ + public function testPollSendsTheIdAsAQueryNotABody(): void + { + $recorder = new RecordingClient([ + RecordingClient::json(200, [ + "action_attempt" => [ + "action_attempt_id" => "aa_1", + "status" => "pending", + ], + ]), + RecordingClient::json(200, [ + "action_attempt" => [ + "action_attempt_id" => "aa_1", + "status" => "success", + ], + ]), + ]); + + $seam = Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: $recorder->guzzle_options(), + retries: 0, + ); + + $resolved = $seam->action_attempts->get("aa_1", [ + "timeout" => 5.0, + "polling_interval" => 0.01, + ]); + + $this->assertSame("success", $resolved->status); + + $poll = $recorder->request(1); + + $this->assertSame("GET", $poll->getMethod()); + $this->assertSame("/action_attempts/get", $poll->getUri()->getPath()); + $this->assertSame( + "action_attempt_id=aa_1&_strict=true", + $poll->getUri()->getQuery(), + ); + $this->assertSame("", (string) $poll->getBody()); + } + + public function testActionAttemptsGetDoesNotRecurse(): void + { + $seam = $this->seam(wait_for_action_attempt: false); + + $action_attempt = $seam->locks->unlock_door( + $this->seed["august_device_1"], + ); + $this->set_status($seam, $action_attempt, "success"); + + $resolved = $seam->action_attempts->get( + $action_attempt->action_attempt_id, + wait_for_action_attempt: [ + "timeout" => 1.0, + "polling_interval" => 0.05, + ], + ); + + $this->assertSame("success", $resolved->status); + } +} diff --git a/tests/WorkspacesProxyTest.php b/tests/WorkspacesProxyTest.php new file mode 100644 index 00000000..53d4ea64 --- /dev/null +++ b/tests/WorkspacesProxyTest.php @@ -0,0 +1,89 @@ +assertSame( + self::describe($generated), + self::describe($proxied), + "WorkspacesProxy::{$method} has drifted from WorkspacesClient::{$method}", + ); + } + + public static function proxiedMethods(): array + { + return [ + "create" => ["create"], + "list" => ["list"], + ]; + } + + /** + * @return array + */ + private static function describe(\ReflectionMethod $method): array + { + $described = [ + "@return" => (string) $method->getReturnType(), + ]; + + foreach ($method->getParameters() as $parameter) { + $described[$parameter->getName()] = sprintf( + "%s%s", + (string) $parameter->getType(), + $parameter->isDefaultValueAvailable() ? " = default" : "", + ); + } + + return $described; + } + + public function testCreateForwardsTheNullSentinel(): void + { + $recorder = new RecordingClient([ + RecordingClient::json(200, [ + "workspace" => [ + "workspace_id" => "ws_1", + "name" => "Sentinel Workspace", + ], + ]), + ]); + + $seam = SeamWithoutWorkspace::from_personal_access_token( + "seam_at_token", + endpoint: "https://example.com", + guzzle_options: $recorder->guzzle_options(), + ); + + $workspace = $seam->workspaces->create( + name: "Sentinel Workspace", + connect_partner_name: NullValue::NULL, + ); + + $this->assertSame("Sentinel Workspace", $workspace->name); + + $body = $recorder->body(); + + $this->assertSame("Sentinel Workspace", $body->name); + $this->assertNull($body->connect_partner_name); + $this->assertTrue(property_exists($body, "connect_partner_name")); + } +} diff --git a/version.ts b/version.ts index e64f1f83..495a2c71 100644 --- a/version.ts +++ b/version.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url' import { $ } from 'execa' -const versionFile = './src/Utils/PackageVersion.php' +const versionFile = './src/Version.php' const versionPattern = /public const VERSION = "[^"]*";/