From 476f5698797e98d7c4b2b84b1e999eebf08fdab9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 01:05:25 +0000 Subject: [PATCH 1/7] feat: generate status-dependent action attempt properties as nullable Bump @seamapi/blueprint to ^1.10.0 and consume its per-property actionAttemptStatuses annotation. An action attempt property that the API only populates for some statuses now generates as a nullable C# type, with documentation naming the statuses that populate it. In practice every variant's result becomes nullable, documented as null unless the action attempt status is success; error was already declared nullable on the union base. The generated sources are regenerated and formatted by the Generate workflow on push. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp --- codegen/lib/build-model.ts | 23 ++++++++++++++++++++- package-lock.json | 8 ++++---- package.json | 2 +- test/Seam.Test/SerializationTests.cs | 30 ++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/codegen/lib/build-model.ts b/codegen/lib/build-model.ts index 48cc65d1..2d5f4e94 100644 --- a/codegen/lib/build-model.ts +++ b/codegen/lib/build-model.ts @@ -591,6 +591,27 @@ export const buildModelFile = ( return { name, file: { decls: [built.main, ...built.siblings] } } } +// An action attempt property annotated with actionAttemptStatuses is only +// populated for the listed statuses and is null for every other status. The +// C# model flattens the per-status variants into one class per action type, +// so such a property widens to a nullable type with documentation naming the +// statuses that populate it. An unannotated property is the same for every +// status and normalizes unchanged. +const normalizeActionAttemptProperty = (property: Property): Field => { + const field = normalizeProperty(property) + const statuses = property.actionAttemptStatuses + if (statuses == null) return field + const statusList = statuses.map((status) => `\`${status}\``).join(' or ') + const note = + statuses.length === 0 + ? 'Always null.' + : `Null unless the action attempt \`status\` is ${statusList}.` + const description = [field.description, note] + .filter((part) => part !== '') + .join('\n\n') + return { ...field, isNullable: true, description } +} + export const buildActionAttemptFile = ( actionAttempts: ActionAttempt[], ): { name: string; file: CsModelFile } => { @@ -602,7 +623,7 @@ export const buildActionAttemptFile = ( 'action_type', actionAttempts.map((actionAttempt) => ({ value: actionAttempt.actionAttemptType, - fields: actionAttempt.properties.map(normalizeProperty), + fields: actionAttempt.properties.map(normalizeActionAttemptProperty), description: actionAttempt.description, ...(actionAttempt.isDeprecated ? { diff --git a/package-lock.json b/package-lock.json index b9818987..5eec64b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.0.0-beta.4", "license": "MIT", "devDependencies": { - "@seamapi/blueprint": "^1.8.0", + "@seamapi/blueprint": "^1.10.0", "@seamapi/fake-seam-connect": "2.0.5", "@seamapi/smith": "^1.1.0", "@seamapi/types": "1.1034.0", @@ -789,9 +789,9 @@ "license": "MIT" }, "node_modules/@seamapi/blueprint": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@seamapi/blueprint/-/blueprint-1.8.0.tgz", - "integrity": "sha512-NUghBmYaKreBeBxwPIB2O9hjIFZtEjVj73tAsuKdJR8t4BxlKK1I0XDQXxo3ZsH2QezNlbdo9/0MoX/33VXuhQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@seamapi/blueprint/-/blueprint-1.10.0.tgz", + "integrity": "sha512-XyP6zvbhv5naWEa9T9utNLi3FVVmvUB1Htih/IjUqS3Uz0FrIIBWy7Myk5+s4uylEt+wTdtSBaA1fGOF/6ILmA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 562e67ac..1ce5344d 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "packageManager": "npm@11.19.0", "devDependencies": { - "@seamapi/blueprint": "^1.8.0", + "@seamapi/blueprint": "^1.10.0", "@seamapi/fake-seam-connect": "2.0.5", "@seamapi/smith": "^1.1.0", "@seamapi/types": "1.1034.0", diff --git a/test/Seam.Test/SerializationTests.cs b/test/Seam.Test/SerializationTests.cs index 3cbb78e2..87fbcc6b 100644 --- a/test/Seam.Test/SerializationTests.cs +++ b/test/Seam.Test/SerializationTests.cs @@ -107,6 +107,36 @@ public void UnknownActionAttemptStatusDeserializesToUnrecognized() Assert.Equal(ActionAttemptStatus.Unrecognized, actionAttempt.Status); } + [Fact] + public void PendingActionAttemptDeserializesWithNullResultAndError() + { + var actionAttempt = Deserialize( + """ + {"action_type":"LOCK_DOOR","action_attempt_id":"attempt1","status":"pending","result":null,"error":null} + """ + ); + + var lockDoor = Assert.IsType(actionAttempt); + Assert.Equal(ActionAttemptStatus.Pending, lockDoor.Status); + Assert.Null(lockDoor.Result); + Assert.Null(lockDoor.Error); + } + + [Fact] + public void SuccessfulActionAttemptDeserializesWithResult() + { + var actionAttempt = Deserialize( + """ + {"action_type":"LOCK_DOOR","action_attempt_id":"attempt1","status":"success","error":null,"result":{"was_confirmed_by_device":true}} + """ + ); + + var lockDoor = Assert.IsType(actionAttempt); + Assert.Equal(ActionAttemptStatus.Success, lockDoor.Status); + Assert.NotNull(lockDoor.Result); + Assert.True(lockDoor.Result.WasConfirmedByDevice); + } + [Fact] public void UnsetOptionalParametersAreOmitted() { From 41097ea190f93f527187a792449baed54d9abe32 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Thu, 27 Aug 2026 01:06:04 +0000 Subject: [PATCH 2/7] ci: Generate code --- src/Seam/Models/ActionAttempt.cs | 84 ++- src/Seam/Routes/AccessGrants.cs | 66 +- src/Seam/Routes/AccessMethods.cs | 14 +- src/Seam/Routes/AcsEncodersSimulate.cs | 18 +- src/Seam/Routes/Customers.cs | 918 ++++++++++++------------- src/Seam/Routes/Thermostats.cs | 18 +- src/Seam/Routes/ThermostatsSimulate.cs | 24 +- src/Seam/Routes/UserIdentities.cs | 14 +- 8 files changed, 599 insertions(+), 557 deletions(-) diff --git a/src/Seam/Models/ActionAttempt.cs b/src/Seam/Models/ActionAttempt.cs index 0a7bbca2..152b6f91 100644 --- a/src/Seam/Models/ActionAttempt.cs +++ b/src/Seam/Models/ActionAttempt.cs @@ -70,9 +70,11 @@ public sealed record ActionAttemptLockDoor : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptLockDoorResult Result { get; init; } = default!; + public ActionAttemptLockDoorResult? Result { get; init; } } public sealed record ActionAttemptLockDoorResult @@ -94,9 +96,11 @@ public sealed record ActionAttemptUnlockDoor : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptUnlockDoorResult Result { get; init; } = default!; + public ActionAttemptUnlockDoorResult? Result { get; init; } } public sealed record ActionAttemptUnlockDoorResult @@ -118,9 +122,11 @@ public sealed record ActionAttemptScanCredential : ActionAttempt /// /// 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 unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptScanCredentialResult Result { get; init; } = default!; + public ActionAttemptScanCredentialResult? Result { get; init; } } public sealed record ActionAttemptScanCredentialResult @@ -762,9 +768,11 @@ public sealed record ActionAttemptEncodeCredential : ActionAttempt /// /// Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptEncodeCredentialResult Result { get; init; } = default!; + public ActionAttemptEncodeCredentialResult? Result { get; init; } } public sealed record ActionAttemptEncodeCredentialResult @@ -1221,9 +1229,11 @@ public sealed record ActionAttemptScanToAssignCredential : ActionAttempt /// /// Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptScanToAssignCredentialResult Result { get; init; } = default!; + public ActionAttemptScanToAssignCredentialResult? Result { get; init; } } public sealed record ActionAttemptScanToAssignCredentialResult @@ -1685,9 +1695,11 @@ public sealed record ActionAttemptAssignCredential : ActionAttempt /// /// Result of assigning a credential. If successful, includes the updated access method with the assigned credential. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptAssignCredentialResult Result { get; init; } = default!; + public ActionAttemptAssignCredentialResult? Result { get; init; } } public sealed record ActionAttemptAssignCredentialResult @@ -2009,9 +2021,11 @@ public sealed record ActionAttemptResetSandboxWorkspace : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptResetSandboxWorkspaceResult Result { get; init; } = default!; + public ActionAttemptResetSandboxWorkspaceResult? Result { get; init; } } public sealed record ActionAttemptResetSandboxWorkspaceResult { } @@ -2026,9 +2040,11 @@ public sealed record ActionAttemptSetFanMode : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSetFanModeResult Result { get; init; } = default!; + public ActionAttemptSetFanModeResult? Result { get; init; } } public sealed record ActionAttemptSetFanModeResult { } @@ -2043,9 +2059,11 @@ public sealed record ActionAttemptSetHvacMode : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSetHvacModeResult Result { get; init; } = default!; + public ActionAttemptSetHvacModeResult? Result { get; init; } } public sealed record ActionAttemptSetHvacModeResult { } @@ -2060,9 +2078,11 @@ public sealed record ActionAttemptActivateClimatePreset : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptActivateClimatePresetResult Result { get; init; } = default!; + public ActionAttemptActivateClimatePresetResult? Result { get; init; } } public sealed record ActionAttemptActivateClimatePresetResult { } @@ -2077,9 +2097,11 @@ public sealed record ActionAttemptSimulateKeypadCodeEntry : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSimulateKeypadCodeEntryResult Result { get; init; } = default!; + public ActionAttemptSimulateKeypadCodeEntryResult? Result { get; init; } } public sealed record ActionAttemptSimulateKeypadCodeEntryResult { } @@ -2094,9 +2116,11 @@ public sealed record ActionAttemptSimulateManualLockViaKeypad : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSimulateManualLockViaKeypadResult Result { get; init; } = default!; + public ActionAttemptSimulateManualLockViaKeypadResult? Result { get; init; } } public sealed record ActionAttemptSimulateManualLockViaKeypadResult { } @@ -2111,9 +2135,11 @@ public sealed record ActionAttemptPushThermostatPrograms : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptPushThermostatProgramsResult Result { get; init; } = default!; + public ActionAttemptPushThermostatProgramsResult? Result { get; init; } } public sealed record ActionAttemptPushThermostatProgramsResult { } @@ -2128,9 +2154,11 @@ public sealed record ActionAttemptConfigureAutoLock : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptConfigureAutoLockResult Result { get; init; } = default!; + public ActionAttemptConfigureAutoLockResult? Result { get; init; } } public sealed record ActionAttemptConfigureAutoLockResult { } @@ -2142,9 +2170,11 @@ public sealed record ActionAttemptSyncAccessCodes : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSyncAccessCodesResult Result { get; init; } = default!; + public ActionAttemptSyncAccessCodesResult? Result { get; init; } } public sealed record ActionAttemptSyncAccessCodesResult { } @@ -2156,9 +2186,11 @@ public sealed record ActionAttemptCreateAccessCode : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptCreateAccessCodeResult Result { get; init; } = default!; + public ActionAttemptCreateAccessCodeResult? Result { get; init; } } public sealed record ActionAttemptCreateAccessCodeResult @@ -2177,9 +2209,11 @@ public sealed record ActionAttemptDeleteAccessCode : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptDeleteAccessCodeResult Result { get; init; } = default!; + public ActionAttemptDeleteAccessCodeResult? Result { get; init; } } public sealed record ActionAttemptDeleteAccessCodeResult { } @@ -2191,9 +2225,11 @@ public sealed record ActionAttemptUpdateAccessCode : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptUpdateAccessCodeResult Result { get; init; } = default!; + public ActionAttemptUpdateAccessCodeResult? Result { get; init; } } public sealed record ActionAttemptUpdateAccessCodeResult @@ -2212,9 +2248,11 @@ public sealed record ActionAttemptCreateNoiseThreshold : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptCreateNoiseThresholdResult Result { get; init; } = default!; + public ActionAttemptCreateNoiseThresholdResult? Result { get; init; } } public sealed record ActionAttemptCreateNoiseThresholdResult @@ -2233,9 +2271,11 @@ public sealed record ActionAttemptDeleteNoiseThreshold : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptDeleteNoiseThresholdResult Result { get; init; } = default!; + public ActionAttemptDeleteNoiseThresholdResult? Result { get; init; } } public sealed record ActionAttemptDeleteNoiseThresholdResult { } @@ -2247,9 +2287,11 @@ public sealed record ActionAttemptUpdateNoiseThreshold : ActionAttempt /// /// Result of the action. + /// + /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptUpdateNoiseThresholdResult Result { get; init; } = default!; + public ActionAttemptUpdateNoiseThresholdResult? Result { get; init; } } public sealed record ActionAttemptUpdateNoiseThresholdResult diff --git a/src/Seam/Routes/AccessGrants.cs b/src/Seam/Routes/AccessGrants.cs index cda514b4..0317b73d 100644 --- a/src/Seam/Routes/AccessGrants.cs +++ b/src/Seam/Routes/AccessGrants.cs @@ -33,18 +33,6 @@ ActionAttemptWait waitForActionAttemptDefault /// public sealed record CreateRequest { - /// - /// ID of user identity for whom access is being granted. - /// - [JsonPropertyName("user_identity_id")] - public string? UserIdentityId { get; init; } - - /// - /// When used, creates a new user identity with the given details, and grants them access. - /// - [JsonPropertyName("user_identity")] - public CreateRequestUserIdentity? UserIdentity { get; init; } - /// /// Unique key for the access grant within the workspace. /// @@ -115,33 +103,18 @@ public sealed record CreateRequest /// [JsonPropertyName("starts_at")] public string? StartsAt { get; init; } - } - public sealed record CreateRequestUserIdentity - { /// - /// Unique email address for the user identity. - /// - [JsonPropertyName("email_address")] - public Optional EmailAddress { get; init; } - - /// - /// Full name of the user associated with the user identity. - /// - [JsonPropertyName("full_name")] - public Optional FullName { get; init; } - - /// - /// Unique phone number for the user identity in [E.164 format](https://www.itu.int/rec/T-REC-E.164/en) (for example, +15555550100). + /// When used, creates a new user identity with the given details, and grants them access. /// - [JsonPropertyName("phone_number")] - public Optional PhoneNumber { get; init; } + [JsonPropertyName("user_identity")] + public CreateRequestUserIdentity? UserIdentity { get; init; } /// - /// Unique key for the user identity. + /// ID of user identity for whom access is being granted. /// - [JsonPropertyName("user_identity_key")] - public Optional UserIdentityKey { get; init; } + [JsonPropertyName("user_identity_id")] + public string? UserIdentityId { get; init; } } public sealed record CreateRequestLocation @@ -204,6 +177,33 @@ public enum ModeEnum public CreateRequestRequestedAccessMethods.ModeEnum? Mode { get; init; } } + public sealed record CreateRequestUserIdentity + { + /// + /// Unique email address for the user identity. + /// + [JsonPropertyName("email_address")] + public Optional EmailAddress { get; init; } + + /// + /// Full name of the user associated with the user identity. + /// + [JsonPropertyName("full_name")] + public Optional FullName { get; init; } + + /// + /// Unique phone number for the user identity in [E.164 format](https://www.itu.int/rec/T-REC-E.164/en) (for example, +15555550100). + /// + [JsonPropertyName("phone_number")] + public Optional PhoneNumber { get; init; } + + /// + /// Unique key for the user identity. + /// + [JsonPropertyName("user_identity_key")] + public Optional UserIdentityKey { get; init; } + } + public sealed record CreateResponse { /// diff --git a/src/Seam/Routes/AccessMethods.cs b/src/Seam/Routes/AccessMethods.cs index 5d610cc5..c09949a0 100644 --- a/src/Seam/Routes/AccessMethods.cs +++ b/src/Seam/Routes/AccessMethods.cs @@ -92,18 +92,18 @@ public async Task AssignCardAsync( /// public sealed record DeleteRequest { - /// - /// ID of access method to delete. - /// - [JsonPropertyName("access_method_id")] - public string? AccessMethodId { get; init; } - /// /// ID of access grant whose access methods should be deleted. /// [JsonPropertyName("access_grant_id")] public string? AccessGrantId { get; init; } + /// + /// ID of access method to delete. + /// + [JsonPropertyName("access_method_id")] + public string? AccessMethodId { get; init; } + /// /// Reservation key of the access grant whose access methods should be deleted. /// @@ -112,7 +112,7 @@ public sealed record DeleteRequest internal void Validate() { - if (AccessMethodId == null && AccessGrantId == null && ReservationKey == null) + if (AccessGrantId == null && AccessMethodId == null && ReservationKey == null) { throw new ArgumentException( "At least one parameter is required for /access_methods/delete" diff --git a/src/Seam/Routes/AcsEncodersSimulate.cs b/src/Seam/Routes/AcsEncodersSimulate.cs index b941a4ac..aaaba409 100644 --- a/src/Seam/Routes/AcsEncodersSimulate.cs +++ b/src/Seam/Routes/AcsEncodersSimulate.cs @@ -52,6 +52,12 @@ public enum ErrorCodeEnum ActionAttemptExpired = 4, } + /// + /// ID of the `acs_credential` that will fail to be encoded onto a card in the next request. + /// + [JsonPropertyName("acs_credential_id")] + public string? AcsCredentialId { get; init; } + /// /// ID of the `acs_encoder` that will be used in the next request to encode the `acs_credential`. /// @@ -63,12 +69,6 @@ public enum ErrorCodeEnum /// [JsonPropertyName("error_code")] public NextCredentialEncodeWillFailRequest.ErrorCodeEnum? ErrorCode { get; init; } - - /// - /// ID of the `acs_credential` that will fail to be encoded onto a card in the next request. - /// - [JsonPropertyName("acs_credential_id")] - public string? AcsCredentialId { get; init; } } /// @@ -159,6 +159,9 @@ public enum ErrorCodeEnum ActionAttemptExpired = 3, } + [JsonPropertyName("acs_credential_id_on_seam")] + public string? AcsCredentialIdOnSeam { get; init; } + /// /// ID of the `acs_encoder` that will fail to scan the `acs_credential` in the next request. /// @@ -167,9 +170,6 @@ public enum ErrorCodeEnum [JsonPropertyName("error_code")] public NextCredentialScanWillFailRequest.ErrorCodeEnum? ErrorCode { get; init; } - - [JsonPropertyName("acs_credential_id_on_seam")] - public string? AcsCredentialIdOnSeam { get; init; } } /// diff --git a/src/Seam/Routes/Customers.cs b/src/Seam/Routes/Customers.cs index 8133bcca..7116ac29 100644 --- a/src/Seam/Routes/Customers.cs +++ b/src/Seam/Routes/Customers.cs @@ -86,6 +86,9 @@ public enum NavigationModeEnum Restricted = 2, } + [JsonPropertyName("customer_data")] + public CreatePortalRequestCustomerData? CustomerData { get; init; } + /// /// Filter configuration for resources based on their custom_metadata. Each filter specifies a field, operation, and value to match against resource custom_metadata. /// @@ -142,434 +145,249 @@ public enum NavigationModeEnum /// [JsonPropertyName("read_only")] public bool? ReadOnly { get; init; } - - [JsonPropertyName("customer_data")] - public CreatePortalRequestCustomerData? CustomerData { get; init; } } - public sealed record CreatePortalRequestCustomerResourcesFilters + public sealed record CreatePortalRequestCustomerData { /// - /// The comparison operation. Currently only '=' is supported. + /// List of access grants. /// - [JsonConverter(typeof(SeamStringEnumConverter))] - public enum OperationEnum - { - [EnumMember(Value = "unrecognized")] - Unrecognized = 0, - - [EnumMember(Value = "=")] - empty = 1, - } + [JsonPropertyName("access_grants")] + public List? AccessGrants { get; init; } /// - /// The custom_metadata field name to filter on. + /// List of bookings. /// - [JsonPropertyName("field")] - public string? Field { get; init; } + [JsonPropertyName("bookings")] + public List? Bookings { get; init; } /// - /// The comparison operation. Currently only '=' is supported. + /// List of buildings. /// - [JsonPropertyName("operation")] - public CreatePortalRequestCustomerResourcesFilters.OperationEnum? Operation { get; init; } + [JsonPropertyName("buildings")] + public List? Buildings { get; init; } /// - /// The value to compare against. + /// List of shared common areas. /// - [JsonPropertyName("value")] - public string? Value { get; init; } - } - - public sealed record CreatePortalRequestDeepLink - { - [JsonConverter(typeof(SeamStringEnumConverter))] - public enum ResourceTypeEnum - { - [EnumMember(Value = "unrecognized")] - Unrecognized = 0, - - [EnumMember(Value = "reservation")] - Reservation = 1, - - [EnumMember(Value = "space")] - Space = 2, + [JsonPropertyName("common_areas")] + public List? CommonAreas { get; init; } - [EnumMember(Value = "device")] - Device = 3, - } + /// + /// Your unique identifier for the customer. + /// + [JsonPropertyName("customer_key")] + public string? CustomerKey { get; init; } - [JsonPropertyName("resource_key")] - public string? ResourceKey { get; init; } + /// + /// List of gym or fitness facilities. + /// + [JsonPropertyName("facilities")] + public List? Facilities { get; init; } - [JsonPropertyName("resource_type")] - public CreatePortalRequestDeepLink.ResourceTypeEnum? ResourceType { get; init; } + /// + /// List of guests. + /// + [JsonPropertyName("guests")] + public List? Guests { get; init; } - [JsonPropertyName("resource_id")] - public string? ResourceId { get; init; } - } + /// + /// List of property listings. + /// + [JsonPropertyName("listings")] + public List? Listings { get; init; } - public sealed record CreatePortalRequestFeatures - { /// - /// Configuration for the configure feature. + /// List of short-term rental properties. /// - [JsonPropertyName("configure")] - public CreatePortalRequestFeaturesConfigure? Configure { get; init; } + [JsonPropertyName("properties")] + public List? Properties { get; init; } /// - /// Configuration for the connect accounts feature. + /// List of property listings. /// - [JsonPropertyName("connect")] - public CreatePortalRequestFeaturesConnect? Connect { get; init; } + [JsonPropertyName("property_listings")] + public List? PropertyListings { get; init; } /// - /// Configuration for the manage feature. + /// List of reservations. /// - [JsonPropertyName("manage")] - public CreatePortalRequestFeaturesManage? Manage { get; init; } + [JsonPropertyName("reservations")] + public List? Reservations { get; init; } /// - /// Configuration for the manage devices feature. - /// --- - /// deprecated: Use `manage` instead. - /// --- + /// List of residents. /// - [JsonPropertyName("manage_devices")] - public CreatePortalRequestFeaturesManageDevices? ManageDevices { get; init; } + [JsonPropertyName("residents")] + public List? Residents { get; init; } /// - /// Configuration for the organize feature. + /// List of hotel or hospitality rooms. /// - [JsonPropertyName("organize")] - public CreatePortalRequestFeaturesOrganize? Organize { get; init; } - } + [JsonPropertyName("rooms")] + public List? Rooms { get; init; } - public sealed record CreatePortalRequestFeaturesConfigure - { /// - /// Indicates whether the customer can customize the access automation rules for their properties. + /// List of general sites or areas. /// - [JsonPropertyName("allow_access_automation_rule_customization")] - public bool? AllowAccessAutomationRuleCustomization { get; init; } + [JsonPropertyName("sites")] + public List? Sites { get; init; } /// - /// Indicates whether the customer can customize the climate automation rules for their properties. + /// List of general spaces or areas. /// - [JsonPropertyName("allow_climate_automation_rule_customization")] - public bool? AllowClimateAutomationRuleCustomization { get; init; } + [JsonPropertyName("spaces")] + public List? Spaces { get; init; } /// - /// Indicates whether the customer can customize the Instant Key profile for their properties. + /// List of staff members. /// - [JsonPropertyName("allow_instant_key_customization")] - public bool? AllowInstantKeyCustomization { get; init; } + [JsonPropertyName("staff_members")] + public List? StaffMembers { get; init; } /// - /// Whether to exclude this feature from the portal. + /// List of tenants. /// - [JsonPropertyName("exclude")] - public bool? Exclude { get; init; } - } + [JsonPropertyName("tenants")] + public List? Tenants { get; init; } - public sealed record CreatePortalRequestFeaturesConnect - { /// - /// List of provider keys to allow for the connect feature. These providers will be shown when the customer tries to connect an account. + /// List of multi-family residential units. /// - [JsonPropertyName("accepted_providers")] - public List? AcceptedProviders { get; init; } + [JsonPropertyName("units")] + public List? Units { get; init; } /// - /// Whether to exclude this feature from the portal. + /// List of user identities. /// - [JsonPropertyName("exclude")] - public bool? Exclude { get; init; } + [JsonPropertyName("user_identities")] + public List? UserIdentities { get; init; } /// - /// List of provider keys to exclude from the connect feature. These providers will not be shown when the customer tries to connect an account. + /// List of users. /// - [JsonPropertyName("excluded_providers")] - public List? ExcludedProviders { get; init; } + [JsonPropertyName("users")] + public List? Users { get; init; } } - public sealed record CreatePortalRequestFeaturesManage + public sealed record CreatePortalRequestCustomerDataAccessGrants { /// - /// Custom copy for the confirmation modal shown before unmanaged devices are added to a space and begin being managed (and billed). Only takes effect when the MANAGE_DEVICES_CONFIRMATION_MODAL feature flag is enabled for the workspace. Any omitted string falls back to a localized default. + /// Your unique identifier for the access grant. /// - [JsonPropertyName("device_management_confirmation")] - public CreatePortalRequestFeaturesManageDeviceManagementConfirmation? DeviceManagementConfirmation { get; init; } + [JsonPropertyName("access_grant_key")] + public string? AccessGrantKey { get; init; } /// - /// Configuration for event type filtering in the manage feature. + /// Building keys associated with the access grant. /// - [JsonPropertyName("events")] - public CreatePortalRequestFeaturesManageEvents? Events { get; init; } + [JsonPropertyName("building_keys")] + public List? BuildingKeys { get; init; } /// - /// Whether to exclude this feature from the portal. + /// Common area keys associated with the access grant. /// - [JsonPropertyName("exclude")] - public bool? Exclude { get; init; } + [JsonPropertyName("common_area_keys")] + public List? CommonAreaKeys { get; init; } /// - /// Indicates whether the customer can manage reservations for their properties. + /// Ending date and time for the access grant. /// - [JsonPropertyName("exclude_reservation_management")] - public bool? ExcludeReservationManagement { get; init; } + [JsonPropertyName("ends_at")] + public string? EndsAt { get; init; } /// - /// Indicates whether to exclude technical details from reservation views. + /// Facility keys associated with the access grant. /// - [JsonPropertyName("exclude_reservation_technical_details")] - public bool? ExcludeReservationTechnicalDetails { get; init; } + [JsonPropertyName("facility_keys")] + public List? FacilityKeys { get; init; } /// - /// Indicates whether the customer can manage staff for their properties. + /// Guest key associated with the access grant. /// - [JsonPropertyName("exclude_staff_management")] - public bool? ExcludeStaffManagement { get; init; } - } + [JsonPropertyName("guest_key")] + public string? GuestKey { get; init; } - public sealed record CreatePortalRequestFeaturesManageDeviceManagementConfirmation - { /// - /// Custom body text for the confirmation modal. May include the {count} token, which is replaced with the number of devices that will begin being managed. + /// Listing keys associated with the access grant. /// - [JsonPropertyName("body")] - public string? Body { get; init; } + [JsonPropertyName("listing_keys")] + public List? ListingKeys { get; init; } /// - /// Custom label for the cancel button. + /// Your name for this access grant resource. /// - [JsonPropertyName("cancel_button_label")] - public string? CancelButtonLabel { get; init; } + [JsonPropertyName("name")] + public string? Name { get; init; } /// - /// Custom label for the confirm button. + /// Preferred PIN code to use when creating access for this reservation. /// - [JsonPropertyName("confirm_button_label")] - public string? ConfirmButtonLabel { get; init; } + [JsonPropertyName("preferred_code")] + public string? PreferredCode { get; init; } /// - /// Custom title for the confirmation modal. + /// Property keys associated with the access grant. /// - [JsonPropertyName("title")] - public string? Title { get; init; } - } + [JsonPropertyName("property_keys")] + public List? PropertyKeys { get; init; } - public sealed record CreatePortalRequestFeaturesManageEvents - { /// - /// List of event types to show in the events filter. When set, only these event types will be available. Leave empty to show all events. + /// Resident key associated with the access grant. /// - [JsonPropertyName("allowed_events")] - public List? AllowedEvents { get; init; } + [JsonPropertyName("resident_key")] + public string? ResidentKey { get; init; } /// - /// List of event types that are pre-selected in the events filter when the user first loads the events tab. + /// Room keys associated with the access grant. /// - [JsonPropertyName("default_events")] - public List? DefaultEvents { get; init; } - } + [JsonPropertyName("room_keys")] + public List? RoomKeys { get; init; } - public sealed record CreatePortalRequestFeaturesManageDevices - { /// - /// Whether to exclude this feature from the portal. + /// Space keys associated with the access grant. /// - [JsonPropertyName("exclude")] - public bool? Exclude { get; init; } - } - - public sealed record CreatePortalRequestFeaturesOrganize - { - /// - /// Whether to exclude this feature from the portal. - /// - [JsonPropertyName("exclude")] - public bool? Exclude { get; init; } - } - - public sealed record CreatePortalRequestLandingPage - { - [JsonPropertyName("manage")] - public CreatePortalRequestLandingPageManage? Manage { get; init; } - } - - public sealed record CreatePortalRequestLandingPageManage - { - [JsonPropertyName("space_key")] - public string? SpaceKey { get; init; } - - [JsonPropertyName("property_key")] - public string? PropertyKey { get; init; } - - [JsonPropertyName("room_key")] - public string? RoomKey { get; init; } - - [JsonPropertyName("common_area_key")] - public string? CommonAreaKey { get; init; } - - [JsonPropertyName("unit_key")] - public string? UnitKey { get; init; } - - [JsonPropertyName("facility_key")] - public string? FacilityKey { get; init; } - - [JsonPropertyName("building_key")] - public string? BuildingKey { get; init; } - - [JsonPropertyName("listing_key")] - public string? ListingKey { get; init; } - - [JsonPropertyName("property_listing_key")] - public string? PropertyListingKey { get; init; } - - [JsonPropertyName("site_key")] - public string? SiteKey { get; init; } - - [JsonPropertyName("reservation_key")] - public string? ReservationKey { get; init; } - - [JsonPropertyName("booking_key")] - public string? BookingKey { get; init; } - - [JsonPropertyName("access_grant_key")] - public string? AccessGrantKey { get; init; } - } - - public sealed record CreatePortalRequestCustomerData - { - /// - /// List of access grants. - /// - [JsonPropertyName("access_grants")] - public List? AccessGrants { get; init; } - - /// - /// List of bookings. - /// - [JsonPropertyName("bookings")] - public List? Bookings { get; init; } - - /// - /// List of buildings. - /// - [JsonPropertyName("buildings")] - public List? Buildings { get; init; } - - /// - /// List of shared common areas. - /// - [JsonPropertyName("common_areas")] - public List? CommonAreas { get; init; } - - /// - /// Your unique identifier for the customer. - /// - [JsonPropertyName("customer_key")] - public string? CustomerKey { get; init; } - - /// - /// List of gym or fitness facilities. - /// - [JsonPropertyName("facilities")] - public List? Facilities { get; init; } - - /// - /// List of guests. - /// - [JsonPropertyName("guests")] - public List? Guests { get; init; } - - /// - /// List of property listings. - /// - [JsonPropertyName("listings")] - public List? Listings { get; init; } - - /// - /// List of short-term rental properties. - /// - [JsonPropertyName("properties")] - public List? Properties { get; init; } - - /// - /// List of property listings. - /// - [JsonPropertyName("property_listings")] - public List? PropertyListings { get; init; } - - /// - /// List of reservations. - /// - [JsonPropertyName("reservations")] - public List? Reservations { get; init; } - - /// - /// List of residents. - /// - [JsonPropertyName("residents")] - public List? Residents { get; init; } - - /// - /// List of hotel or hospitality rooms. - /// - [JsonPropertyName("rooms")] - public List? Rooms { get; init; } - - /// - /// List of general sites or areas. - /// - [JsonPropertyName("sites")] - public List? Sites { get; init; } - - /// - /// List of general spaces or areas. - /// - [JsonPropertyName("spaces")] - public List? Spaces { get; init; } + [JsonPropertyName("space_keys")] + public List? SpaceKeys { get; init; } /// - /// List of staff members. + /// Starting date and time for the access grant. /// - [JsonPropertyName("staff_members")] - public List? StaffMembers { get; init; } + [JsonPropertyName("starts_at")] + public string? StartsAt { get; init; } /// - /// List of tenants. + /// Tenant key associated with the access grant. /// - [JsonPropertyName("tenants")] - public List? Tenants { get; init; } + [JsonPropertyName("tenant_key")] + public string? TenantKey { get; init; } /// - /// List of multi-family residential units. + /// Unit keys associated with the access grant. /// - [JsonPropertyName("units")] - public List? Units { get; init; } + [JsonPropertyName("unit_keys")] + public List? UnitKeys { get; init; } /// - /// List of user identities. + /// User identity key associated with the access grant. /// - [JsonPropertyName("user_identities")] - public List? UserIdentities { get; init; } + [JsonPropertyName("user_identity_key")] + public string? UserIdentityKey { get; init; } /// - /// List of users. + /// User key associated with the access grant. /// - [JsonPropertyName("users")] - public List? Users { get; init; } + [JsonPropertyName("user_key")] + public string? UserKey { get; init; } } - public sealed record CreatePortalRequestCustomerDataAccessGrants + public sealed record CreatePortalRequestCustomerDataBookings { /// - /// Your unique identifier for the access grant. + /// Your unique identifier for the booking. /// - [JsonPropertyName("access_grant_key")] - public string? AccessGrantKey { get; init; } + [JsonPropertyName("booking_key")] + public string? BookingKey { get; init; } /// /// Building keys associated with the access grant. @@ -674,181 +492,70 @@ public sealed record CreatePortalRequestCustomerDataAccessGrants public string? UserKey { get; init; } } - public sealed record CreatePortalRequestCustomerDataBookings + public sealed record CreatePortalRequestCustomerDataBuildings { /// - /// Your unique identifier for the booking. - /// - [JsonPropertyName("booking_key")] - public string? BookingKey { get; init; } - - /// - /// Building keys associated with the access grant. + /// Your unique identifier for the building. /// - [JsonPropertyName("building_keys")] - public List? BuildingKeys { get; init; } + [JsonPropertyName("building_key")] + public string? BuildingKey { get; init; } /// - /// Common area keys associated with the access grant. + /// Your display name for this location resource. /// - [JsonPropertyName("common_area_keys")] - public List? CommonAreaKeys { get; init; } + [JsonPropertyName("name")] + public string? Name { get; init; } + } + public sealed record CreatePortalRequestCustomerDataCommonAreas + { /// - /// Ending date and time for the access grant. + /// Your unique identifier for the common area. /// - [JsonPropertyName("ends_at")] - public string? EndsAt { get; init; } + [JsonPropertyName("common_area_key")] + public string? CommonAreaKey { get; init; } /// - /// Facility keys associated with the access grant. + /// Your display name for this location resource. /// - [JsonPropertyName("facility_keys")] - public List? FacilityKeys { get; init; } + [JsonPropertyName("name")] + public string? Name { get; init; } /// - /// Guest key associated with the access grant. + /// Your unique identifier for the site. /// - [JsonPropertyName("guest_key")] - public string? GuestKey { get; init; } + [JsonPropertyName("parent_site_key")] + public string? ParentSiteKey { get; init; } + } + public sealed record CreatePortalRequestCustomerDataFacilities + { /// - /// Listing keys associated with the access grant. + /// Your unique identifier for the facility. /// - [JsonPropertyName("listing_keys")] - public List? ListingKeys { get; init; } + [JsonPropertyName("facility_key")] + public string? FacilityKey { get; init; } /// - /// Your name for this access grant resource. + /// Your display name for this location resource. /// [JsonPropertyName("name")] public string? Name { get; init; } + } + public sealed record CreatePortalRequestCustomerDataGuests + { /// - /// Preferred PIN code to use when creating access for this reservation. + /// Email address associated with the user identity. /// - [JsonPropertyName("preferred_code")] - public string? PreferredCode { get; init; } + [JsonPropertyName("email_address")] + public string? EmailAddress { get; init; } /// - /// Property keys associated with the access grant. + /// Your unique identifier for the guest. /// - [JsonPropertyName("property_keys")] - public List? PropertyKeys { get; init; } - - /// - /// Resident key associated with the access grant. - /// - [JsonPropertyName("resident_key")] - public string? ResidentKey { get; init; } - - /// - /// Room keys associated with the access grant. - /// - [JsonPropertyName("room_keys")] - public List? RoomKeys { get; init; } - - /// - /// Space keys associated with the access grant. - /// - [JsonPropertyName("space_keys")] - public List? SpaceKeys { get; init; } - - /// - /// Starting date and time for the access grant. - /// - [JsonPropertyName("starts_at")] - public string? StartsAt { get; init; } - - /// - /// Tenant key associated with the access grant. - /// - [JsonPropertyName("tenant_key")] - public string? TenantKey { get; init; } - - /// - /// Unit keys associated with the access grant. - /// - [JsonPropertyName("unit_keys")] - public List? UnitKeys { get; init; } - - /// - /// User identity key associated with the access grant. - /// - [JsonPropertyName("user_identity_key")] - public string? UserIdentityKey { get; init; } - - /// - /// User key associated with the access grant. - /// - [JsonPropertyName("user_key")] - public string? UserKey { get; init; } - } - - public sealed record CreatePortalRequestCustomerDataBuildings - { - /// - /// Your unique identifier for the building. - /// - [JsonPropertyName("building_key")] - public string? BuildingKey { get; init; } - - /// - /// Your display name for this location resource. - /// - [JsonPropertyName("name")] - public string? Name { get; init; } - } - - public sealed record CreatePortalRequestCustomerDataCommonAreas - { - /// - /// Your unique identifier for the common area. - /// - [JsonPropertyName("common_area_key")] - public string? CommonAreaKey { get; init; } - - /// - /// Your display name for this location resource. - /// - [JsonPropertyName("name")] - public string? Name { get; init; } - - /// - /// Your unique identifier for the site. - /// - [JsonPropertyName("parent_site_key")] - public string? ParentSiteKey { get; init; } - } - - public sealed record CreatePortalRequestCustomerDataFacilities - { - /// - /// Your unique identifier for the facility. - /// - [JsonPropertyName("facility_key")] - public string? FacilityKey { get; init; } - - /// - /// Your display name for this location resource. - /// - [JsonPropertyName("name")] - public string? Name { get; init; } - } - - public sealed record CreatePortalRequestCustomerDataGuests - { - /// - /// Email address associated with the user identity. - /// - [JsonPropertyName("email_address")] - public string? EmailAddress { get; init; } - - /// - /// Your unique identifier for the guest. - /// - [JsonPropertyName("guest_key")] - public string? GuestKey { get; init; } + [JsonPropertyName("guest_key")] + public string? GuestKey { get; init; } /// /// Your display name for this user identity resource. @@ -1364,6 +1071,299 @@ public sealed record CreatePortalRequestCustomerDataUsers public string? UserKey { get; init; } } + public sealed record CreatePortalRequestCustomerResourcesFilters + { + /// + /// The comparison operation. Currently only '=' is supported. + /// + [JsonConverter(typeof(SeamStringEnumConverter))] + public enum OperationEnum + { + [EnumMember(Value = "unrecognized")] + Unrecognized = 0, + + [EnumMember(Value = "=")] + empty = 1, + } + + /// + /// The custom_metadata field name to filter on. + /// + [JsonPropertyName("field")] + public string? Field { get; init; } + + /// + /// The comparison operation. Currently only '=' is supported. + /// + [JsonPropertyName("operation")] + public CreatePortalRequestCustomerResourcesFilters.OperationEnum? Operation { get; init; } + + /// + /// The value to compare against. + /// + [JsonPropertyName("value")] + public string? Value { get; init; } + } + + public sealed record CreatePortalRequestDeepLink + { + [JsonConverter(typeof(SeamStringEnumConverter))] + public enum ResourceTypeEnum + { + [EnumMember(Value = "unrecognized")] + Unrecognized = 0, + + [EnumMember(Value = "reservation")] + Reservation = 1, + + [EnumMember(Value = "space")] + Space = 2, + + [EnumMember(Value = "device")] + Device = 3, + } + + [JsonPropertyName("resource_id")] + public string? ResourceId { get; init; } + + [JsonPropertyName("resource_key")] + public string? ResourceKey { get; init; } + + [JsonPropertyName("resource_type")] + public CreatePortalRequestDeepLink.ResourceTypeEnum? ResourceType { get; init; } + } + + public sealed record CreatePortalRequestFeatures + { + /// + /// Configuration for the configure feature. + /// + [JsonPropertyName("configure")] + public CreatePortalRequestFeaturesConfigure? Configure { get; init; } + + /// + /// Configuration for the connect accounts feature. + /// + [JsonPropertyName("connect")] + public CreatePortalRequestFeaturesConnect? Connect { get; init; } + + /// + /// Configuration for the manage feature. + /// + [JsonPropertyName("manage")] + public CreatePortalRequestFeaturesManage? Manage { get; init; } + + /// + /// Configuration for the manage devices feature. + /// --- + /// deprecated: Use `manage` instead. + /// --- + /// + [JsonPropertyName("manage_devices")] + public CreatePortalRequestFeaturesManageDevices? ManageDevices { get; init; } + + /// + /// Configuration for the organize feature. + /// + [JsonPropertyName("organize")] + public CreatePortalRequestFeaturesOrganize? Organize { get; init; } + } + + public sealed record CreatePortalRequestFeaturesConfigure + { + /// + /// Indicates whether the customer can customize the access automation rules for their properties. + /// + [JsonPropertyName("allow_access_automation_rule_customization")] + public bool? AllowAccessAutomationRuleCustomization { get; init; } + + /// + /// Indicates whether the customer can customize the climate automation rules for their properties. + /// + [JsonPropertyName("allow_climate_automation_rule_customization")] + public bool? AllowClimateAutomationRuleCustomization { get; init; } + + /// + /// Indicates whether the customer can customize the Instant Key profile for their properties. + /// + [JsonPropertyName("allow_instant_key_customization")] + public bool? AllowInstantKeyCustomization { get; init; } + + /// + /// Whether to exclude this feature from the portal. + /// + [JsonPropertyName("exclude")] + public bool? Exclude { get; init; } + } + + public sealed record CreatePortalRequestFeaturesConnect + { + /// + /// List of provider keys to allow for the connect feature. These providers will be shown when the customer tries to connect an account. + /// + [JsonPropertyName("accepted_providers")] + public List? AcceptedProviders { get; init; } + + /// + /// Whether to exclude this feature from the portal. + /// + [JsonPropertyName("exclude")] + public bool? Exclude { get; init; } + + /// + /// List of provider keys to exclude from the connect feature. These providers will not be shown when the customer tries to connect an account. + /// + [JsonPropertyName("excluded_providers")] + public List? ExcludedProviders { get; init; } + } + + public sealed record CreatePortalRequestFeaturesManage + { + /// + /// Custom copy for the confirmation modal shown before unmanaged devices are added to a space and begin being managed (and billed). Only takes effect when the MANAGE_DEVICES_CONFIRMATION_MODAL feature flag is enabled for the workspace. Any omitted string falls back to a localized default. + /// + [JsonPropertyName("device_management_confirmation")] + public CreatePortalRequestFeaturesManageDeviceManagementConfirmation? DeviceManagementConfirmation { get; init; } + + /// + /// Configuration for event type filtering in the manage feature. + /// + [JsonPropertyName("events")] + public CreatePortalRequestFeaturesManageEvents? Events { get; init; } + + /// + /// Whether to exclude this feature from the portal. + /// + [JsonPropertyName("exclude")] + public bool? Exclude { get; init; } + + /// + /// Indicates whether the customer can manage reservations for their properties. + /// + [JsonPropertyName("exclude_reservation_management")] + public bool? ExcludeReservationManagement { get; init; } + + /// + /// Indicates whether to exclude technical details from reservation views. + /// + [JsonPropertyName("exclude_reservation_technical_details")] + public bool? ExcludeReservationTechnicalDetails { get; init; } + + /// + /// Indicates whether the customer can manage staff for their properties. + /// + [JsonPropertyName("exclude_staff_management")] + public bool? ExcludeStaffManagement { get; init; } + } + + public sealed record CreatePortalRequestFeaturesManageDeviceManagementConfirmation + { + /// + /// Custom body text for the confirmation modal. May include the {count} token, which is replaced with the number of devices that will begin being managed. + /// + [JsonPropertyName("body")] + public string? Body { get; init; } + + /// + /// Custom label for the cancel button. + /// + [JsonPropertyName("cancel_button_label")] + public string? CancelButtonLabel { get; init; } + + /// + /// Custom label for the confirm button. + /// + [JsonPropertyName("confirm_button_label")] + public string? ConfirmButtonLabel { get; init; } + + /// + /// Custom title for the confirmation modal. + /// + [JsonPropertyName("title")] + public string? Title { get; init; } + } + + public sealed record CreatePortalRequestFeaturesManageEvents + { + /// + /// List of event types to show in the events filter. When set, only these event types will be available. Leave empty to show all events. + /// + [JsonPropertyName("allowed_events")] + public List? AllowedEvents { get; init; } + + /// + /// List of event types that are pre-selected in the events filter when the user first loads the events tab. + /// + [JsonPropertyName("default_events")] + public List? DefaultEvents { get; init; } + } + + public sealed record CreatePortalRequestFeaturesManageDevices + { + /// + /// Whether to exclude this feature from the portal. + /// + [JsonPropertyName("exclude")] + public bool? Exclude { get; init; } + } + + public sealed record CreatePortalRequestFeaturesOrganize + { + /// + /// Whether to exclude this feature from the portal. + /// + [JsonPropertyName("exclude")] + public bool? Exclude { get; init; } + } + + public sealed record CreatePortalRequestLandingPage + { + [JsonPropertyName("manage")] + public CreatePortalRequestLandingPageManage? Manage { get; init; } + } + + public sealed record CreatePortalRequestLandingPageManage + { + [JsonPropertyName("access_grant_key")] + public string? AccessGrantKey { get; init; } + + [JsonPropertyName("booking_key")] + public string? BookingKey { get; init; } + + [JsonPropertyName("building_key")] + public string? BuildingKey { get; init; } + + [JsonPropertyName("common_area_key")] + public string? CommonAreaKey { get; init; } + + [JsonPropertyName("facility_key")] + public string? FacilityKey { get; init; } + + [JsonPropertyName("listing_key")] + public string? ListingKey { get; init; } + + [JsonPropertyName("property_key")] + public string? PropertyKey { get; init; } + + [JsonPropertyName("property_listing_key")] + public string? PropertyListingKey { get; init; } + + [JsonPropertyName("reservation_key")] + public string? ReservationKey { get; init; } + + [JsonPropertyName("room_key")] + public string? RoomKey { get; init; } + + [JsonPropertyName("site_key")] + public string? SiteKey { get; init; } + + [JsonPropertyName("space_key")] + public string? SpaceKey { get; init; } + + [JsonPropertyName("unit_key")] + public string? UnitKey { get; init; } + } + public sealed record CreatePortalResponse { /// diff --git a/src/Seam/Routes/Thermostats.cs b/src/Seam/Routes/Thermostats.cs index c27b1f1f..633e8707 100644 --- a/src/Seam/Routes/Thermostats.cs +++ b/src/Seam/Routes/Thermostats.cs @@ -917,15 +917,6 @@ public enum HvacModeSettingEnum Eco = 5, } - /// - /// ID of the thermostat device for which you want to set the HVAC mode. - /// - [JsonPropertyName("device_id")] - public required string DeviceId { get; init; } - - [JsonPropertyName("hvac_mode_setting")] - public required SetHvacModeRequest.HvacModeSettingEnum HvacModeSetting { get; init; } - /// /// [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. /// @@ -938,6 +929,12 @@ public enum HvacModeSettingEnum [JsonPropertyName("cooling_set_point_fahrenheit")] public float? CoolingSetPointFahrenheit { get; init; } + /// + /// ID of the thermostat device for which you want to set the HVAC mode. + /// + [JsonPropertyName("device_id")] + public required string DeviceId { get; init; } + /// /// [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. /// @@ -949,6 +946,9 @@ public enum HvacModeSettingEnum /// [JsonPropertyName("heating_set_point_fahrenheit")] public float? HeatingSetPointFahrenheit { get; init; } + + [JsonPropertyName("hvac_mode_setting")] + public required SetHvacModeRequest.HvacModeSettingEnum HvacModeSetting { get; init; } } public sealed record SetHvacModeResponse diff --git a/src/Seam/Routes/ThermostatsSimulate.cs b/src/Seam/Routes/ThermostatsSimulate.cs index 533cf2fc..a49b8ae4 100644 --- a/src/Seam/Routes/ThermostatsSimulate.cs +++ b/src/Seam/Routes/ThermostatsSimulate.cs @@ -52,18 +52,6 @@ public enum HvacModeEnum HeatCool = 4, } - /// - /// ID of the thermostat device for which you want to simulate having adjusted the HVAC mode. - /// - [JsonPropertyName("device_id")] - public required string DeviceId { get; init; } - - /// - /// HVAC mode that you want to simulate. - /// - [JsonPropertyName("hvac_mode")] - public required HvacModeAdjustedRequest.HvacModeEnum HvacMode { get; init; } - /// /// Cooling [set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °C that you want to simulate. You must set `cooling_set_point_celsius` or `cooling_set_point_fahrenheit`. /// @@ -76,6 +64,12 @@ public enum HvacModeEnum [JsonPropertyName("cooling_set_point_fahrenheit")] public float? CoolingSetPointFahrenheit { get; init; } + /// + /// ID of the thermostat device for which you want to simulate having adjusted the HVAC mode. + /// + [JsonPropertyName("device_id")] + public required string DeviceId { get; init; } + /// /// Heating [set point](https://docs.seam.co/capability-guides/thermostats/understanding-thermostat-concepts/set-points) in °C that you want to simulate. You must set `heating_set_point_celsius` or `heating_set_point_fahrenheit`. /// @@ -87,6 +81,12 @@ public enum HvacModeEnum /// [JsonPropertyName("heating_set_point_fahrenheit")] public float? HeatingSetPointFahrenheit { get; init; } + + /// + /// HVAC mode that you want to simulate. + /// + [JsonPropertyName("hvac_mode")] + public required HvacModeAdjustedRequest.HvacModeEnum HvacMode { get; init; } } /// diff --git a/src/Seam/Routes/UserIdentities.cs b/src/Seam/Routes/UserIdentities.cs index f2813941..6b69ed8e 100644 --- a/src/Seam/Routes/UserIdentities.cs +++ b/src/Seam/Routes/UserIdentities.cs @@ -617,18 +617,18 @@ public sealed record MergeRequest [JsonPropertyName("merged_user_identity_ids")] public List? MergedUserIdentityIds { get; init; } - /// - /// ID of the primary user identity to keep. - /// - [JsonPropertyName("user_identity_id")] - public string? UserIdentityId { get; init; } - /// /// Keys of the user identities to merge into the primary user identity. These user identities are deleted. /// [JsonPropertyName("merged_user_identity_keys")] public List? MergedUserIdentityKeys { get; init; } + /// + /// ID of the primary user identity to keep. + /// + [JsonPropertyName("user_identity_id")] + public string? UserIdentityId { get; init; } + /// /// Key of the primary user identity to keep. /// @@ -639,8 +639,8 @@ internal void Validate() { if ( MergedUserIdentityIds == null - && UserIdentityId == null && MergedUserIdentityKeys == null + && UserIdentityId == null && UserIdentityKey == null ) { From 60330ccf6a110391592a100f1d57ccc708c93e1a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 03:01:20 +0000 Subject: [PATCH 3/7] refactor: remove narration comment Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp --- codegen/lib/build-model.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/codegen/lib/build-model.ts b/codegen/lib/build-model.ts index 2d5f4e94..1fb68e41 100644 --- a/codegen/lib/build-model.ts +++ b/codegen/lib/build-model.ts @@ -591,12 +591,6 @@ export const buildModelFile = ( return { name, file: { decls: [built.main, ...built.siblings] } } } -// An action attempt property annotated with actionAttemptStatuses is only -// populated for the listed statuses and is null for every other status. The -// C# model flattens the per-status variants into one class per action type, -// so such a property widens to a nullable type with documentation naming the -// statuses that populate it. An unannotated property is the same for every -// status and normalizes unchanged. const normalizeActionAttemptProperty = (property: Property): Field => { const field = normalizeProperty(property) const statuses = property.actionAttemptStatuses From 4e7231b003e0b214cef7bd3c24e74ed2837c8dea Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 03:34:34 +0000 Subject: [PATCH 4/7] feat: generate status-discriminated action attempt subclasses Each action type is now a nested union discriminated on status: a pending subclass with no error or result members, a success subclass with a non-null result, an error subclass with a non-null error, and an unrecognized fallback preserving the raw payload. Property placement comes from the actionAttemptStatuses annotation, so dereferencing a status-dependent property without narrowing to its status subclass does not compile. SeamActionAttemptFailedException reads the error from the status subclass that carries it and falls back when none does. The generated sources are regenerated and formatted by the Generate workflow on push. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp --- codegen/layouts/partials/oneof-union.hbs | 9 +- codegen/lib/build-model.ts | 196 ++++++++++++++---- codegen/lib/class-model.ts | 15 +- .../Exceptions/SeamActionAttemptException.cs | 13 +- test/Seam.Test/SerializationTests.cs | 42 +++- 5 files changed, 221 insertions(+), 54 deletions(-) diff --git a/codegen/layouts/partials/oneof-union.hbs b/codegen/layouts/partials/oneof-union.hbs index 07c0ba98..c0688ca3 100644 --- a/codegen/layouts/partials/oneof-union.hbs +++ b/codegen/layouts/partials/oneof-union.hbs @@ -1,13 +1,16 @@ +{{> documentation}} [JsonConverter(typeof(SeamUnionConverter))] [SeamUnion("{{discriminatorSnake}}")] {{#each knownSubTypes}} [SeamUnionVariant("{{value}}", typeof({{typeName}}))] {{/each}} [SeamUnionFallback(typeof({{unrecognizedTypeName}}))] -public abstract record {{className}} +public abstract record {{className}}{{#if baseClass}} : {{baseClass}}{{/if}} { +{{#unless inheritsDiscriminator}} /// The value of the {{discriminatorSnake}} discriminator. public abstract string {{discriminatorPascal}} { get; } +{{/unless}} {{#each baseProps}} {{> data-member this}} @@ -15,5 +18,9 @@ public abstract string {{discriminatorPascal}} { get; } } {{#each subclasses}} +{{#if (eq kind "union")}} +{{> oneof-union this}} +{{else}} {{> model-class this}} +{{/if}} {{/each}} diff --git a/codegen/lib/build-model.ts b/codegen/lib/build-model.ts index 1fb68e41..eeadd933 100644 --- a/codegen/lib/build-model.ts +++ b/codegen/lib/build-model.ts @@ -21,7 +21,9 @@ import type { ActionAttempt, + ActionAttemptStatus, Endpoint, + EnumProperty, EventResource, Parameter, Property, @@ -96,9 +98,15 @@ type Kind = interface Variant { value: string + // Used for the shared-property lifting computation, and to build the + // variant subclass unless buildAsUnion is set. fields: Field[] description?: string deprecationMessage?: string + // Builds the variant as a nested union discriminated on another property + // instead of a plain subclass. Receives the names declared on (or omitted + // by) the outer base, which the inner union must not redeclare. + buildAsUnion?: (subName: string, omitNames: Set) => CsUnion } const normalizeEnumValues = ( @@ -305,8 +313,15 @@ interface BuildClassOptions { // nullable parameters); models deserialize leniently. resourceType: 'response' | 'request' | 'model' // When set, the class is a discriminated-union subclass: the discriminator - // property is emitted as a get-only override with a constant value. - discriminator?: { name: string; value: string; base: string } + // property is emitted as a get-only override with a constant value, unless + // declareProperty is false (the discriminator property is inherited from an + // ancestor class). + discriminator?: { + name: string + value: string + base: string + declareProperty?: boolean + } // Field names declared concretely on the union base; omitted from subclasses. omitNames?: Set | undefined documentation?: string @@ -430,7 +445,7 @@ const buildClass = ( if (omitNames?.has(field.name) ?? false) continue properties.push(mapField(field)) } - if (discriminator != null) { + if (discriminator != null && (discriminator.declareProperty ?? true)) { properties.unshift({ pascalName: pascalCase(discriminator.name), snakeName: snakeCase(discriminator.name), @@ -459,9 +474,18 @@ interface BuildUnionOptions { resourceType: 'response' | 'request' | 'model' // Field names removed from every variant in favor of `extraBaseProps` // declared on the base with a shared type, e.g. the action attempt - // status/error contract the runtime resolver depends on. + // status contract the runtime resolver depends on. omitFieldNames?: string[] extraBaseProps?: CsProperty[] + // See CsUnion for these. + baseClass?: string + inheritsDiscriminator?: boolean + // Properties declared on the base before the lifted and extra properties, + // e.g. the outer discriminator's get-only override when the union is itself + // a variant of an outer union. + leadingBaseProps?: CsProperty[] + documentation?: string + obsoleteMessage?: string } const buildUnion = ( @@ -470,7 +494,16 @@ const buildUnion = ( variants: Variant[], options: BuildUnionOptions, ): CsUnion => { - const { resourceType, omitFieldNames = [], extraBaseProps = [] } = options + const { + resourceType, + omitFieldNames = [], + extraBaseProps = [], + baseClass, + inheritsDiscriminator, + leadingBaseProps = [], + documentation, + obsoleteMessage, + } = options const omitted = new Set(omitFieldNames) // Lift properties shared by every variant onto the base so consumers can @@ -507,6 +540,7 @@ const buildUnion = ( ]) const baseProps: CsProperty[] = [ + ...leadingBaseProps, ...liftedFields.map((field): CsProperty => { const type = primType(field) as string return { @@ -528,17 +562,25 @@ const buildUnion = ( ...extraBaseProps, ] - const subclasses: CsClass[] = [] + const subclasses: Array = [] const known: Array<[string, string]> = [] for (const variant of variants) { const subName = pascalCase(className + pascalCase(variant.value)) + if (variant.buildAsUnion != null) { + subclasses.push( + variant.buildAsUnion(subName, new Set([...omitNames, discriminator])), + ) + known.push([subName, variant.value]) + continue + } const built = buildClass(subName, variant.fields, { resourceType, discriminator: { name: discriminator, value: variant.value, base: className, + ...(inheritsDiscriminator ? { declareProperty: false } : {}), }, omitNames, ...(variant.description != null @@ -559,6 +601,7 @@ const buildUnion = ( name: discriminator, value: 'unrecognized', base: className, + ...(inheritsDiscriminator ? { declareProperty: false } : {}), }, }) subclasses.push({ ...fallback.main, isUnrecognizedFallback: true }) @@ -568,12 +611,16 @@ const buildUnion = ( return { kind: 'union', className, + ...(baseClass != null ? { baseClass } : {}), discriminatorSnake: discriminator, discriminatorPascal: pascalCase(discriminator), + ...(inheritsDiscriminator ? { inheritsDiscriminator } : {}), knownSubTypes, unrecognizedTypeName, baseProps, subclasses, + ...(documentation != null ? { documentation } : {}), + ...(obsoleteMessage != null ? { obsoleteMessage } : {}), } } @@ -591,44 +638,118 @@ export const buildModelFile = ( return { name, file: { decls: [built.main, ...built.siblings] } } } -const normalizeActionAttemptProperty = (property: Property): Field => { +// The types the action attempt runtime (resolver and exceptions) owns rather +// than the generator: status everywhere, and the error wire shape wherever an +// action attempt declares an error property. +const actionAttemptRuntimeTypes: Record = { + error: 'ActionAttemptError', +} + +const normalizeActionAttemptField = (property: Property): Field => { + const runtimeType = actionAttemptRuntimeTypes[property.name] const field = normalizeProperty(property) - const statuses = property.actionAttemptStatuses - if (statuses == null) return field - const statusList = statuses.map((status) => `\`${status}\``).join(' or ') - const note = - statuses.length === 0 - ? 'Always null.' - : `Null unless the action attempt \`status\` is ${statusList}.` - const description = [field.description, note] - .filter((part) => part !== '') - .join('\n\n') - return { ...field, isNullable: true, description } + if (runtimeType == null) return field + return { ...field, kind: { t: 'ref', cs: runtimeType } } } +const findActionAttemptStatusProperty = ( + actionAttempt: ActionAttempt, +): EnumProperty | undefined => + actionAttempt.properties.find( + (property): property is EnumProperty => + property.name === 'status' && property.format === 'enum', + ) + +// One variant per status from the status enum. A property whose +// actionAttemptStatuses annotation lists the status keeps its normal +// non-nullable type on that status subclass; one whose annotation does not +// list it is absent there, so dereferencing it without narrowing to the +// right status subclass does not compile. +const buildActionAttemptStatusUnion = ( + actionAttempt: ActionAttempt, + statusProperty: EnumProperty, + subName: string, + omitNames: Set, +): CsUnion => + buildUnion( + subName, + statusProperty.name, + statusProperty.values.map(({ name }) => { + const status = name as ActionAttemptStatus + return { + value: status, + fields: actionAttempt.properties.flatMap((property) => { + if (property === statusProperty || omitNames.has(property.name)) { + return [] + } + const statuses = property.actionAttemptStatuses + if (statuses != null && !statuses.includes(status)) return [] + return [normalizeActionAttemptField(property)] + }), + } + }), + { + resourceType: 'model', + baseClass: 'ActionAttempt', + inheritsDiscriminator: true, + leadingBaseProps: [ + { + pascalName: 'ActionType', + snakeName: 'action_type', + type: 'string', + isRequired: false, + isOverride: true, + getOnly: true, + initializer: `"${actionAttempt.actionAttemptType}"`, + }, + ], + documentation: actionAttempt.description, + ...(actionAttempt.isDeprecated + ? { + obsoleteMessage: actionAttempt.deprecationMessage || 'Deprecated.', + } + : {}), + }, + ) + export const buildActionAttemptFile = ( actionAttempts: ActionAttempt[], ): { name: string; file: CsModelFile } => { - // The status and error of every action attempt share one wire shape, so they - // are declared once on the base with the runtime-owned ActionAttemptStatus - // and ActionAttemptError types the action attempt resolver depends on. + // The status of every action attempt shares one wire shape, so it is + // declared once on the base with the runtime-owned ActionAttemptStatus type + // the action attempt resolver depends on. Each action type then nests a + // union discriminated on status. const union = buildUnion( 'ActionAttempt', 'action_type', - actionAttempts.map((actionAttempt) => ({ - value: actionAttempt.actionAttemptType, - fields: actionAttempt.properties.map(normalizeActionAttemptProperty), - description: actionAttempt.description, - ...(actionAttempt.isDeprecated - ? { - deprecationMessage: - actionAttempt.deprecationMessage || 'Deprecated.', - } - : {}), - })), + actionAttempts.map((actionAttempt) => { + const statusProperty = findActionAttemptStatusProperty(actionAttempt) + return { + value: actionAttempt.actionAttemptType, + fields: actionAttempt.properties.map(normalizeActionAttemptField), + description: actionAttempt.description, + ...(actionAttempt.isDeprecated + ? { + deprecationMessage: + actionAttempt.deprecationMessage || 'Deprecated.', + } + : {}), + ...(statusProperty == null + ? {} + : { + buildAsUnion: (subName: string, omitNames: Set) => + buildActionAttemptStatusUnion( + actionAttempt, + statusProperty, + subName, + omitNames, + ), + }), + } + }), { resourceType: 'model', - omitFieldNames: ['status', 'error'], + omitFieldNames: ['status'], extraBaseProps: [ { pascalName: 'Status', @@ -639,15 +760,6 @@ export const buildActionAttemptFile = ( getOnly: false, documentation: 'The status of the action attempt.', }, - { - pascalName: 'Error', - snakeName: 'error', - type: 'ActionAttemptError?', - isRequired: false, - isOverride: false, - getOnly: false, - documentation: 'The error of a failed action attempt, or null.', - }, ], }, ) diff --git a/codegen/lib/class-model.ts b/codegen/lib/class-model.ts index 4beaa522..e677b890 100644 --- a/codegen/lib/class-model.ts +++ b/codegen/lib/class-model.ts @@ -70,8 +70,16 @@ export interface CsClass { export interface CsUnion { kind: 'union' className: string + // Set when the union base is itself a variant of an outer union: the base + // extends the outer union's base class and declares the outer + // discriminator's get-only override in baseProps. + baseClass?: string discriminatorSnake: string discriminatorPascal: string + // The discriminator wire property is already declared by an ancestor class + // (e.g. status on the action attempt base), so the union base declares no + // abstract discriminator property and the subclasses declare no override. + inheritsDiscriminator?: boolean // [SeamUnionVariant] attributes, in variant definition order. knownSubTypes: Array<{ typeName: string; value: string }> unrecognizedTypeName: string @@ -80,8 +88,11 @@ export interface CsUnion { // inherit them rather than redeclare them. baseProps: CsProperty[] // Concrete subclasses followed by the Unrecognized fallback, in definition - // order. - subclasses: CsClass[] + // order. A subclass may itself be a union discriminated on another + // property. + subclasses: Array + documentation?: string + obsoleteMessage?: string } export type CsDecl = CsClass | CsUnion diff --git a/src/Seam/Exceptions/SeamActionAttemptException.cs b/src/Seam/Exceptions/SeamActionAttemptException.cs index 0c164e2b..4132d8bd 100644 --- a/src/Seam/Exceptions/SeamActionAttemptException.cs +++ b/src/Seam/Exceptions/SeamActionAttemptException.cs @@ -22,13 +22,22 @@ protected SeamActionAttemptException(string message, Models.ActionAttempt action public class SeamActionAttemptFailedException : SeamActionAttemptException { public SeamActionAttemptFailedException(Models.ActionAttempt actionAttempt) - : base(actionAttempt.Error?.Message ?? "Action attempt failed", actionAttempt) + : base(GetError(actionAttempt)?.Message ?? "Action attempt failed", actionAttempt) { - Code = actionAttempt.Error?.Type ?? "unknown_error"; + Code = GetError(actionAttempt)?.Type ?? "unknown_error"; } /// The action attempt error type. public string Code { get; } + + /// + /// The error property is declared on the status subclasses that carry it, not on + /// , and is absent entirely on an attempt with an + /// unrecognized action type or status. + /// + private static Models.ActionAttemptError? GetError(Models.ActionAttempt actionAttempt) => + actionAttempt.GetType().GetProperty("Error")?.GetValue(actionAttempt) + as Models.ActionAttemptError; } /// diff --git a/test/Seam.Test/SerializationTests.cs b/test/Seam.Test/SerializationTests.cs index 87fbcc6b..8a70abc0 100644 --- a/test/Seam.Test/SerializationTests.cs +++ b/test/Seam.Test/SerializationTests.cs @@ -63,7 +63,7 @@ public void KnownActionTypeDeserializesToItsVariant() """ ); - Assert.IsType(actionAttempt); + Assert.IsType(actionAttempt); Assert.Equal(ActionAttemptStatus.Success, actionAttempt.Status); Assert.Equal("UNLOCK_DOOR", actionAttempt.ActionType); } @@ -104,11 +104,13 @@ public void UnknownActionAttemptStatusDeserializesToUnrecognized() """ ); + var unrecognized = Assert.IsType(actionAttempt); Assert.Equal(ActionAttemptStatus.Unrecognized, actionAttempt.Status); + Assert.Equal("not_a_status", unrecognized.RawJson.GetProperty("status").GetString()); } [Fact] - public void PendingActionAttemptDeserializesWithNullResultAndError() + public void PendingActionAttemptDeserializesToThePendingSubclass() { var actionAttempt = Deserialize( """ @@ -116,10 +118,8 @@ public void PendingActionAttemptDeserializesWithNullResultAndError() """ ); - var lockDoor = Assert.IsType(actionAttempt); + var lockDoor = Assert.IsType(actionAttempt); Assert.Equal(ActionAttemptStatus.Pending, lockDoor.Status); - Assert.Null(lockDoor.Result); - Assert.Null(lockDoor.Error); } [Fact] @@ -131,12 +131,40 @@ public void SuccessfulActionAttemptDeserializesWithResult() """ ); - var lockDoor = Assert.IsType(actionAttempt); + var lockDoor = Assert.IsType(actionAttempt); Assert.Equal(ActionAttemptStatus.Success, lockDoor.Status); - Assert.NotNull(lockDoor.Result); Assert.True(lockDoor.Result.WasConfirmedByDevice); } + [Fact] + public void FailedActionAttemptDeserializesWithError() + { + var actionAttempt = Deserialize( + """ + {"action_type":"LOCK_DOOR","action_attempt_id":"attempt1","status":"error","result":null,"error":{"type":"foo","message":"Failed"}} + """ + ); + + var lockDoor = Assert.IsType(actionAttempt); + Assert.Equal(ActionAttemptStatus.Error, lockDoor.Status); + Assert.Equal("Failed", lockDoor.Error.Message); + Assert.Equal("foo", lockDoor.Error.Type); + } + + [Fact] + public void FailedActionAttemptExceptionFallsBackWithoutAnErrorObject() + { + var actionAttempt = Deserialize( + """ + {"action_type":"FUTURE_ACTION","action_attempt_id":"attempt1","status":"error"} + """ + ); + + var exception = new SeamActionAttemptFailedException(actionAttempt); + Assert.Equal("Action attempt failed", exception.Message); + Assert.Equal("unknown_error", exception.Code); + } + [Fact] public void UnsetOptionalParametersAreOmitted() { From 55b4dc95fd8f474b8dfad0bf5194add237aca7d5 Mon Sep 17 00:00:00 2001 From: Seam Bot Date: Thu, 27 Aug 2026 03:35:02 +0000 Subject: [PATCH 5/7] ci: Generate code --- .../Exceptions/SeamActionAttemptException.cs | 2 +- src/Seam/Models/ActionAttempt.cs | 934 ++++++++++++++---- 2 files changed, 758 insertions(+), 178 deletions(-) diff --git a/src/Seam/Exceptions/SeamActionAttemptException.cs b/src/Seam/Exceptions/SeamActionAttemptException.cs index 4132d8bd..1904367a 100644 --- a/src/Seam/Exceptions/SeamActionAttemptException.cs +++ b/src/Seam/Exceptions/SeamActionAttemptException.cs @@ -37,7 +37,7 @@ public SeamActionAttemptFailedException(Models.ActionAttempt actionAttempt) /// private static Models.ActionAttemptError? GetError(Models.ActionAttempt actionAttempt) => actionAttempt.GetType().GetProperty("Error")?.GetValue(actionAttempt) - as Models.ActionAttemptError; + as Models.ActionAttemptError; } /// diff --git a/src/Seam/Models/ActionAttempt.cs b/src/Seam/Models/ActionAttempt.cs index 152b6f91..04dc951c 100644 --- a/src/Seam/Models/ActionAttempt.cs +++ b/src/Seam/Models/ActionAttempt.cs @@ -52,32 +52,33 @@ public abstract record ActionAttempt /// [JsonPropertyName("status")] public ActionAttemptStatus Status { get; init; } - - /// - /// The error of a failed action attempt, or null. - /// - [JsonPropertyName("error")] - public ActionAttemptError? Error { get; init; } } /// /// Locking a door is pending. /// - public sealed record ActionAttemptLockDoor : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptLockDoorSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptLockDoorPending))] + [SeamUnionVariant("error", typeof(ActionAttemptLockDoorError))] + [SeamUnionFallback(typeof(ActionAttemptLockDoorUnrecognized))] + public abstract record ActionAttemptLockDoor : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "LOCK_DOOR"; + } + public sealed record ActionAttemptLockDoorSuccess : ActionAttemptLockDoor + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptLockDoorResult? Result { get; init; } + public ActionAttemptLockDoorSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptLockDoorResult + public sealed record ActionAttemptLockDoorSuccessResult { /// /// Indicates whether the device confirmed that the lock action occurred. @@ -86,24 +87,51 @@ public sealed record ActionAttemptLockDoorResult public bool? WasConfirmedByDevice { get; init; } } + public sealed record ActionAttemptLockDoorPending : ActionAttemptLockDoor { } + + public sealed record ActionAttemptLockDoorError : ActionAttemptLockDoor + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptLockDoorUnrecognized + : ActionAttemptLockDoor, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + /// /// Unlocking a door is pending. /// - public sealed record ActionAttemptUnlockDoor : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptUnlockDoorSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptUnlockDoorPending))] + [SeamUnionVariant("error", typeof(ActionAttemptUnlockDoorError))] + [SeamUnionFallback(typeof(ActionAttemptUnlockDoorUnrecognized))] + public abstract record ActionAttemptUnlockDoor : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "UNLOCK_DOOR"; + } + public sealed record ActionAttemptUnlockDoorSuccess : ActionAttemptUnlockDoor + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptUnlockDoorResult? Result { get; init; } + public ActionAttemptUnlockDoorSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptUnlockDoorResult + public sealed record ActionAttemptUnlockDoorSuccessResult { /// /// Indicates whether the device confirmed that the unlock action occurred. @@ -112,45 +140,73 @@ public sealed record ActionAttemptUnlockDoorResult public bool? WasConfirmedByDevice { get; init; } } + public sealed record ActionAttemptUnlockDoorPending : ActionAttemptUnlockDoor { } + + public sealed record ActionAttemptUnlockDoorError : ActionAttemptUnlockDoor + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptUnlockDoorUnrecognized + : ActionAttemptUnlockDoor, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + /// /// Reading credential data from the physical encoder is pending. /// - public sealed record ActionAttemptScanCredential : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptScanCredentialSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptScanCredentialPending))] + [SeamUnionVariant("error", typeof(ActionAttemptScanCredentialError))] + [SeamUnionFallback(typeof(ActionAttemptScanCredentialUnrecognized))] + public abstract record ActionAttemptScanCredential : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "SCAN_CREDENTIAL"; + } + public sealed record ActionAttemptScanCredentialSuccess : ActionAttemptScanCredential + { /// /// 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 unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptScanCredentialResult? Result { get; init; } + public ActionAttemptScanCredentialSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptScanCredentialResult + public sealed record ActionAttemptScanCredentialSuccessResult { /// /// Snapshot of credential data read from the physical encoder. /// [JsonPropertyName("acs_credential_on_encoder")] - public ActionAttemptScanCredentialResultAcsCredentialOnEncoder? AcsCredentialOnEncoder { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnEncoder? AcsCredentialOnEncoder { get; init; } /// /// Corresponding credential data as stored on Seam and the access system. /// [JsonPropertyName("acs_credential_on_seam")] - public ActionAttemptScanCredentialResultAcsCredentialOnSeam? AcsCredentialOnSeam { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeam? AcsCredentialOnSeam { get; init; } /// /// 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. /// [JsonPropertyName("warnings")] - public List Warnings { get; init; } = default!; + public List Warnings { get; init; } = + default!; } - public sealed record ActionAttemptScanCredentialResultAcsCredentialOnEncoder + public sealed record ActionAttemptScanCredentialSuccessResultAcsCredentialOnEncoder { /// /// A number or string that physically identifies the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). @@ -186,10 +242,10 @@ public sealed record ActionAttemptScanCredentialResultAcsCredentialOnEncoder /// Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("visionline_metadata")] - public ActionAttemptScanCredentialResultAcsCredentialOnEncoderVisionlineMetadata? VisionlineMetadata { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnEncoderVisionlineMetadata? VisionlineMetadata { get; init; } } - public sealed record ActionAttemptScanCredentialResultAcsCredentialOnEncoderVisionlineMetadata + public sealed record ActionAttemptScanCredentialSuccessResultAcsCredentialOnEncoderVisionlineMetadata { /// /// Format of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). @@ -217,7 +273,7 @@ public enum CardFormatEnum /// Format of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("card_format")] - public ActionAttemptScanCredentialResultAcsCredentialOnEncoderVisionlineMetadata.CardFormatEnum? CardFormat { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnEncoderVisionlineMetadata.CardFormatEnum? CardFormat { get; init; } /// /// Holder of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). @@ -280,7 +336,7 @@ public enum CardFormatEnum public bool? PendingAutoUpdate { get; init; } } - public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeam + public sealed record ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeam { /// /// Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. @@ -360,7 +416,7 @@ public enum ExternalTypeEnum /// Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. /// [JsonPropertyName("access_method")] - public ActionAttemptScanCredentialResultAcsCredentialOnSeam.AccessMethodEnum AccessMethod { get; init; } = + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeam.AccessMethodEnum AccessMethod { get; init; } = default!; /// @@ -391,13 +447,13 @@ public enum ExternalTypeEnum /// Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("akiles_metadata")] - public ActionAttemptScanCredentialResultAcsCredentialOnSeamAkilesMetadata? AkilesMetadata { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamAkilesMetadata? AkilesMetadata { get; init; } /// /// Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("assa_abloy_vostio_metadata")] - public ActionAttemptScanCredentialResultAcsCredentialOnSeamAssaAbloyVostioMetadata? AssaAbloyVostioMetadata { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamAssaAbloyVostioMetadata? AssaAbloyVostioMetadata { get; init; } /// /// Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). @@ -439,14 +495,14 @@ public enum ExternalTypeEnum /// Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("errors")] - public List Errors { get; init; } = + public List Errors { get; init; } = default!; /// /// 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`. /// [JsonPropertyName("external_type")] - public ActionAttemptScanCredentialResultAcsCredentialOnSeam.ExternalTypeEnum? ExternalType { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeam.ExternalTypeEnum? ExternalType { get; init; } /// /// Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. @@ -515,13 +571,13 @@ public enum ExternalTypeEnum /// Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("visionline_metadata")] - public ActionAttemptScanCredentialResultAcsCredentialOnSeamVisionlineMetadata? VisionlineMetadata { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamVisionlineMetadata? VisionlineMetadata { get; init; } /// /// Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("warnings")] - public List Warnings { get; init; } = + public List Warnings { get; init; } = default!; /// @@ -531,7 +587,7 @@ public enum ExternalTypeEnum public string WorkspaceId { get; init; } = default!; } - public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeamAkilesMetadata + public sealed record ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamAkilesMetadata { /// /// ID of the Akiles member PIN. @@ -540,7 +596,7 @@ public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeamAkilesM public string? MemberPinId { get; init; } } - public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeamAssaAbloyVostioMetadata + public sealed record ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamAssaAbloyVostioMetadata { /// /// 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. @@ -579,7 +635,7 @@ public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeamAssaAbl public List? OverrideGuestAcsEntranceIds { get; init; } } - public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeamErrors + public sealed record ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamErrors { /// /// Date and time at which Seam created the error. @@ -594,7 +650,7 @@ public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeamErrors public string Message { get; init; } = default!; } - public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeamVisionlineMetadata + public sealed record ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamVisionlineMetadata { /// /// Card function type in the Visionline access system. @@ -622,7 +678,7 @@ public enum CardFunctionTypeEnum /// Card function type in the Visionline access system. /// [JsonPropertyName("card_function_type")] - public ActionAttemptScanCredentialResultAcsCredentialOnSeamVisionlineMetadata.CardFunctionTypeEnum? CardFunctionType { get; init; } + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamVisionlineMetadata.CardFunctionTypeEnum? CardFunctionType { get; init; } /// /// ID of the card in the Visionline access system. @@ -661,7 +717,7 @@ public enum CardFunctionTypeEnum public List? JoinerAcsCredentialIds { get; init; } } - public sealed record ActionAttemptScanCredentialResultAcsCredentialOnSeamWarnings + public sealed record ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamWarnings { /// /// Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. @@ -710,7 +766,7 @@ public enum WarningCodeEnum /// Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. /// [JsonPropertyName("warning_code")] - public ActionAttemptScanCredentialResultAcsCredentialOnSeamWarnings.WarningCodeEnum WarningCode { get; init; } = + public ActionAttemptScanCredentialSuccessResultAcsCredentialOnSeamWarnings.WarningCodeEnum WarningCode { get; init; } = default!; /// @@ -726,7 +782,7 @@ public enum WarningCodeEnum public string? OriginalCode { get; init; } } - public sealed record ActionAttemptScanCredentialResultWarnings + public sealed record ActionAttemptScanCredentialSuccessResultWarnings { /// /// Indicates a warning related to scanning a credential. @@ -748,7 +804,7 @@ public enum WarningCodeEnum /// Indicates a warning related to scanning a credential. /// [JsonPropertyName("warning_code")] - public ActionAttemptScanCredentialResultWarnings.WarningCodeEnum WarningCode { get; init; } = + public ActionAttemptScanCredentialSuccessResultWarnings.WarningCodeEnum WarningCode { get; init; } = default!; /// @@ -758,24 +814,48 @@ public enum WarningCodeEnum public string WarningMessage { get; init; } = default!; } + public sealed record ActionAttemptScanCredentialPending : ActionAttemptScanCredential { } + + public sealed record ActionAttemptScanCredentialError : ActionAttemptScanCredential + { + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptScanCredentialUnrecognized + : ActionAttemptScanCredential, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + /// /// Encoding credential data from the physical encoder onto a card is pending. /// - public sealed record ActionAttemptEncodeCredential : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptEncodeCredentialSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptEncodeCredentialPending))] + [SeamUnionVariant("error", typeof(ActionAttemptEncodeCredentialError))] + [SeamUnionFallback(typeof(ActionAttemptEncodeCredentialUnrecognized))] + public abstract record ActionAttemptEncodeCredential : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "ENCODE_CREDENTIAL"; + } + public sealed record ActionAttemptEncodeCredentialSuccess : ActionAttemptEncodeCredential + { /// /// Result of an encoding attempt. If the attempt was successful, includes the credential data that was encoded onto the card. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptEncodeCredentialResult? Result { get; init; } + public ActionAttemptEncodeCredentialSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptEncodeCredentialResult + public sealed record ActionAttemptEncodeCredentialSuccessResult { /// /// Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. @@ -855,7 +935,7 @@ public enum ExternalTypeEnum /// Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. /// [JsonPropertyName("access_method")] - public ActionAttemptEncodeCredentialResult.AccessMethodEnum AccessMethod { get; init; } = + public ActionAttemptEncodeCredentialSuccessResult.AccessMethodEnum AccessMethod { get; init; } = default!; /// @@ -886,13 +966,13 @@ public enum ExternalTypeEnum /// Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("akiles_metadata")] - public ActionAttemptEncodeCredentialResultAkilesMetadata? AkilesMetadata { get; init; } + public ActionAttemptEncodeCredentialSuccessResultAkilesMetadata? AkilesMetadata { get; init; } /// /// Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("assa_abloy_vostio_metadata")] - public ActionAttemptEncodeCredentialResultAssaAbloyVostioMetadata? AssaAbloyVostioMetadata { get; init; } + public ActionAttemptEncodeCredentialSuccessResultAssaAbloyVostioMetadata? AssaAbloyVostioMetadata { get; init; } /// /// Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). @@ -934,13 +1014,14 @@ public enum ExternalTypeEnum /// Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("errors")] - public List Errors { get; init; } = default!; + public List Errors { get; init; } = + default!; /// /// 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`. /// [JsonPropertyName("external_type")] - public ActionAttemptEncodeCredentialResult.ExternalTypeEnum? ExternalType { get; init; } + public ActionAttemptEncodeCredentialSuccessResult.ExternalTypeEnum? ExternalType { get; init; } /// /// Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. @@ -1009,13 +1090,14 @@ public enum ExternalTypeEnum /// Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("visionline_metadata")] - public ActionAttemptEncodeCredentialResultVisionlineMetadata? VisionlineMetadata { get; init; } + public ActionAttemptEncodeCredentialSuccessResultVisionlineMetadata? VisionlineMetadata { get; init; } /// /// Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("warnings")] - public List Warnings { get; init; } = default!; + public List Warnings { get; init; } = + default!; /// /// ID of the workspace that contains the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). @@ -1024,7 +1106,7 @@ public enum ExternalTypeEnum public string WorkspaceId { get; init; } = default!; } - public sealed record ActionAttemptEncodeCredentialResultAkilesMetadata + public sealed record ActionAttemptEncodeCredentialSuccessResultAkilesMetadata { /// /// ID of the Akiles member PIN. @@ -1033,7 +1115,7 @@ public sealed record ActionAttemptEncodeCredentialResultAkilesMetadata public string? MemberPinId { get; init; } } - public sealed record ActionAttemptEncodeCredentialResultAssaAbloyVostioMetadata + public sealed record ActionAttemptEncodeCredentialSuccessResultAssaAbloyVostioMetadata { /// /// 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. @@ -1072,7 +1154,7 @@ public sealed record ActionAttemptEncodeCredentialResultAssaAbloyVostioMetadata public List? OverrideGuestAcsEntranceIds { get; init; } } - public sealed record ActionAttemptEncodeCredentialResultErrors + public sealed record ActionAttemptEncodeCredentialSuccessResultErrors { /// /// Date and time at which Seam created the error. @@ -1087,7 +1169,7 @@ public sealed record ActionAttemptEncodeCredentialResultErrors public string Message { get; init; } = default!; } - public sealed record ActionAttemptEncodeCredentialResultVisionlineMetadata + public sealed record ActionAttemptEncodeCredentialSuccessResultVisionlineMetadata { /// /// Card function type in the Visionline access system. @@ -1115,7 +1197,7 @@ public enum CardFunctionTypeEnum /// Card function type in the Visionline access system. /// [JsonPropertyName("card_function_type")] - public ActionAttemptEncodeCredentialResultVisionlineMetadata.CardFunctionTypeEnum? CardFunctionType { get; init; } + public ActionAttemptEncodeCredentialSuccessResultVisionlineMetadata.CardFunctionTypeEnum? CardFunctionType { get; init; } /// /// ID of the card in the Visionline access system. @@ -1154,7 +1236,7 @@ public enum CardFunctionTypeEnum public List? JoinerAcsCredentialIds { get; init; } } - public sealed record ActionAttemptEncodeCredentialResultWarnings + public sealed record ActionAttemptEncodeCredentialSuccessResultWarnings { /// /// Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. @@ -1203,7 +1285,7 @@ public enum WarningCodeEnum /// Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. /// [JsonPropertyName("warning_code")] - public ActionAttemptEncodeCredentialResultWarnings.WarningCodeEnum WarningCode { get; init; } = + public ActionAttemptEncodeCredentialSuccessResultWarnings.WarningCodeEnum WarningCode { get; init; } = default!; /// @@ -1219,24 +1301,49 @@ public enum WarningCodeEnum public string? OriginalCode { get; init; } } + public sealed record ActionAttemptEncodeCredentialPending : ActionAttemptEncodeCredential { } + + public sealed record ActionAttemptEncodeCredentialError : ActionAttemptEncodeCredential + { + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptEncodeCredentialUnrecognized + : ActionAttemptEncodeCredential, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + /// /// Scanning a physical card and assigning the credential is pending. /// - public sealed record ActionAttemptScanToAssignCredential : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptScanToAssignCredentialSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptScanToAssignCredentialPending))] + [SeamUnionVariant("error", typeof(ActionAttemptScanToAssignCredentialError))] + [SeamUnionFallback(typeof(ActionAttemptScanToAssignCredentialUnrecognized))] + public abstract record ActionAttemptScanToAssignCredential : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "SCAN_TO_ASSIGN_CREDENTIAL"; + } + public sealed record ActionAttemptScanToAssignCredentialSuccess + : ActionAttemptScanToAssignCredential + { /// /// Result of a scan to assign attempt. If the attempt was successful, includes the credential data that was scanned and assigned. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptScanToAssignCredentialResult? Result { get; init; } + public ActionAttemptScanToAssignCredentialSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptScanToAssignCredentialResult + public sealed record ActionAttemptScanToAssignCredentialSuccessResult { /// /// Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. @@ -1316,7 +1423,7 @@ public enum ExternalTypeEnum /// Access method for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). Supported values: `code`, `card`, `mobile_key`, `cloud_key`. /// [JsonPropertyName("access_method")] - public ActionAttemptScanToAssignCredentialResult.AccessMethodEnum AccessMethod { get; init; } = + public ActionAttemptScanToAssignCredentialSuccessResult.AccessMethodEnum AccessMethod { get; init; } = default!; /// @@ -1347,13 +1454,13 @@ public enum ExternalTypeEnum /// Akiles-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("akiles_metadata")] - public ActionAttemptScanToAssignCredentialResultAkilesMetadata? AkilesMetadata { get; init; } + public ActionAttemptScanToAssignCredentialSuccessResultAkilesMetadata? AkilesMetadata { get; init; } /// /// Vostio-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("assa_abloy_vostio_metadata")] - public ActionAttemptScanToAssignCredentialResultAssaAbloyVostioMetadata? AssaAbloyVostioMetadata { get; init; } + public ActionAttemptScanToAssignCredentialSuccessResultAssaAbloyVostioMetadata? AssaAbloyVostioMetadata { get; init; } /// /// Number of the card associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). @@ -1395,14 +1502,14 @@ public enum ExternalTypeEnum /// Errors associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("errors")] - public List Errors { get; init; } = + public List Errors { get; init; } = default!; /// /// 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`. /// [JsonPropertyName("external_type")] - public ActionAttemptScanToAssignCredentialResult.ExternalTypeEnum? ExternalType { get; init; } + public ActionAttemptScanToAssignCredentialSuccessResult.ExternalTypeEnum? ExternalType { get; init; } /// /// Display name that corresponds to the brand-specific terminology for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials) type. @@ -1474,13 +1581,13 @@ public enum ExternalTypeEnum /// Visionline-specific metadata for the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("visionline_metadata")] - public ActionAttemptScanToAssignCredentialResultVisionlineMetadata? VisionlineMetadata { get; init; } + public ActionAttemptScanToAssignCredentialSuccessResultVisionlineMetadata? VisionlineMetadata { get; init; } /// /// Warnings associated with the [credential](https://docs.seam.co/low-level-apis/access-systems/managing-credentials). /// [JsonPropertyName("warnings")] - public List Warnings { get; init; } = + public List Warnings { get; init; } = default!; /// @@ -1490,7 +1597,7 @@ public enum ExternalTypeEnum public string WorkspaceId { get; init; } = default!; } - public sealed record ActionAttemptScanToAssignCredentialResultAkilesMetadata + public sealed record ActionAttemptScanToAssignCredentialSuccessResultAkilesMetadata { /// /// ID of the Akiles member PIN. @@ -1499,7 +1606,7 @@ public sealed record ActionAttemptScanToAssignCredentialResultAkilesMetadata public string? MemberPinId { get; init; } } - public sealed record ActionAttemptScanToAssignCredentialResultAssaAbloyVostioMetadata + public sealed record ActionAttemptScanToAssignCredentialSuccessResultAssaAbloyVostioMetadata { /// /// 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. @@ -1538,7 +1645,7 @@ public sealed record ActionAttemptScanToAssignCredentialResultAssaAbloyVostioMet public List? OverrideGuestAcsEntranceIds { get; init; } } - public sealed record ActionAttemptScanToAssignCredentialResultErrors + public sealed record ActionAttemptScanToAssignCredentialSuccessResultErrors { /// /// Date and time at which Seam created the error. @@ -1553,7 +1660,7 @@ public sealed record ActionAttemptScanToAssignCredentialResultErrors public string Message { get; init; } = default!; } - public sealed record ActionAttemptScanToAssignCredentialResultVisionlineMetadata + public sealed record ActionAttemptScanToAssignCredentialSuccessResultVisionlineMetadata { /// /// Card function type in the Visionline access system. @@ -1581,7 +1688,7 @@ public enum CardFunctionTypeEnum /// Card function type in the Visionline access system. /// [JsonPropertyName("card_function_type")] - public ActionAttemptScanToAssignCredentialResultVisionlineMetadata.CardFunctionTypeEnum? CardFunctionType { get; init; } + public ActionAttemptScanToAssignCredentialSuccessResultVisionlineMetadata.CardFunctionTypeEnum? CardFunctionType { get; init; } /// /// ID of the card in the Visionline access system. @@ -1620,7 +1727,7 @@ public enum CardFunctionTypeEnum public List? JoinerAcsCredentialIds { get; init; } } - public sealed record ActionAttemptScanToAssignCredentialResultWarnings + public sealed record ActionAttemptScanToAssignCredentialSuccessResultWarnings { /// /// Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. @@ -1669,7 +1776,7 @@ public enum WarningCodeEnum /// Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. /// [JsonPropertyName("warning_code")] - public ActionAttemptScanToAssignCredentialResultWarnings.WarningCodeEnum WarningCode { get; init; } = + public ActionAttemptScanToAssignCredentialSuccessResultWarnings.WarningCodeEnum WarningCode { get; init; } = default!; /// @@ -1685,24 +1792,50 @@ public enum WarningCodeEnum public string? OriginalCode { get; init; } } + public sealed record ActionAttemptScanToAssignCredentialPending + : ActionAttemptScanToAssignCredential { } + + public sealed record ActionAttemptScanToAssignCredentialError + : ActionAttemptScanToAssignCredential + { + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptScanToAssignCredentialUnrecognized + : ActionAttemptScanToAssignCredential, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + /// /// Assigning a credential to an access method is pending. /// - public sealed record ActionAttemptAssignCredential : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptAssignCredentialSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptAssignCredentialPending))] + [SeamUnionVariant("error", typeof(ActionAttemptAssignCredentialError))] + [SeamUnionFallback(typeof(ActionAttemptAssignCredentialUnrecognized))] + public abstract record ActionAttemptAssignCredential : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "ASSIGN_CREDENTIAL"; + } + public sealed record ActionAttemptAssignCredentialSuccess : ActionAttemptAssignCredential + { /// /// Result of assigning a credential. If successful, includes the updated access method with the assigned credential. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptAssignCredentialResult? Result { get; init; } + public ActionAttemptAssignCredentialSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptAssignCredentialResult + public sealed record ActionAttemptAssignCredentialSuccessResult { /// /// Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. @@ -1772,7 +1905,8 @@ public enum ModeEnum /// Errors associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). /// [JsonPropertyName("errors")] - public List Errors { get; init; } = default!; + public List Errors { get; init; } = + default!; /// /// URL of the Instant Key for mobile key access methods. @@ -1820,20 +1954,21 @@ public enum ModeEnum /// Access method mode. Supported values: `code`, `card`, `mobile_key`, `cloud_key`. /// [JsonPropertyName("mode")] - public ActionAttemptAssignCredentialResult.ModeEnum Mode { get; init; } = default!; + public ActionAttemptAssignCredentialSuccessResult.ModeEnum Mode { get; init; } = default!; /// /// Pending mutations for the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). Indicates operations that are in progress. /// [JsonPropertyName("pending_mutations")] - public List PendingMutations { get; init; } = + public List PendingMutations { get; init; } = default!; /// /// Warnings associated with the [access method](https://docs.seam.co/use-cases/granting-access/creating-an-access-grant). /// [JsonPropertyName("warnings")] - public List Warnings { get; init; } = default!; + public List Warnings { get; init; } = + default!; /// /// ID of the Seam workspace associated with the access method. @@ -1842,7 +1977,7 @@ public enum ModeEnum public string WorkspaceId { get; init; } = default!; } - public sealed record ActionAttemptAssignCredentialResultErrors + public sealed record ActionAttemptAssignCredentialSuccessResultErrors { /// /// Unique identifier of the type of error. Enables quick recognition and categorization of the issue. @@ -1867,7 +2002,7 @@ public enum ErrorCodeEnum /// Unique identifier of the type of error. Enables quick recognition and categorization of the issue. /// [JsonPropertyName("error_code")] - public ActionAttemptAssignCredentialResultErrors.ErrorCodeEnum ErrorCode { get; init; } = + public ActionAttemptAssignCredentialSuccessResultErrors.ErrorCodeEnum ErrorCode { get; init; } = default!; /// @@ -1877,7 +2012,7 @@ public enum ErrorCodeEnum public string Message { get; init; } = default!; } - public sealed record ActionAttemptAssignCredentialResultPendingMutations + public sealed record ActionAttemptAssignCredentialSuccessResultPendingMutations { /// /// Mutation code to indicate that Seam is in the process of updating the access times for this access method. @@ -1908,7 +2043,7 @@ public enum MutationCodeEnum /// Previous access time configuration. /// [JsonPropertyName("from")] - public ActionAttemptAssignCredentialResultPendingMutationsFrom From { get; init; } = + public ActionAttemptAssignCredentialSuccessResultPendingMutationsFrom From { get; init; } = default!; /// @@ -1921,17 +2056,18 @@ public enum MutationCodeEnum /// Mutation code to indicate that Seam is in the process of updating the access times for this access method. /// [JsonPropertyName("mutation_code")] - public ActionAttemptAssignCredentialResultPendingMutations.MutationCodeEnum MutationCode { get; init; } = + public ActionAttemptAssignCredentialSuccessResultPendingMutations.MutationCodeEnum MutationCode { get; init; } = default!; /// /// New access time configuration. /// [JsonPropertyName("to")] - public ActionAttemptAssignCredentialResultPendingMutationsTo To { get; init; } = default!; + public ActionAttemptAssignCredentialSuccessResultPendingMutationsTo To { get; init; } = + default!; } - public sealed record ActionAttemptAssignCredentialResultPendingMutationsFrom + public sealed record ActionAttemptAssignCredentialSuccessResultPendingMutationsFrom { /// /// Previous end time for access. @@ -1946,7 +2082,7 @@ public sealed record ActionAttemptAssignCredentialResultPendingMutationsFrom public string? StartsAt { get; init; } } - public sealed record ActionAttemptAssignCredentialResultPendingMutationsTo + public sealed record ActionAttemptAssignCredentialSuccessResultPendingMutationsTo { /// /// New end time for access. @@ -1961,7 +2097,7 @@ public sealed record ActionAttemptAssignCredentialResultPendingMutationsTo public string? StartsAt { get; init; } } - public sealed record ActionAttemptAssignCredentialResultWarnings + public sealed record ActionAttemptAssignCredentialSuccessResultWarnings { /// /// Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. @@ -2001,7 +2137,7 @@ public enum WarningCodeEnum /// Unique identifier of the type of warning. Enables quick recognition and categorization of the issue. /// [JsonPropertyName("warning_code")] - public ActionAttemptAssignCredentialResultWarnings.WarningCodeEnum WarningCode { get; init; } = + public ActionAttemptAssignCredentialSuccessResultWarnings.WarningCodeEnum WarningCode { get; init; } = default!; /// @@ -2011,189 +2147,472 @@ public enum WarningCodeEnum public string? OriginalAccessMethodId { get; init; } } + public sealed record ActionAttemptAssignCredentialPending : ActionAttemptAssignCredential { } + + public sealed record ActionAttemptAssignCredentialError : ActionAttemptAssignCredential + { + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptAssignCredentialUnrecognized + : ActionAttemptAssignCredential, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + /// /// Resetting a sandbox workspace is pending. /// - public sealed record ActionAttemptResetSandboxWorkspace : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptResetSandboxWorkspaceSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptResetSandboxWorkspacePending))] + [SeamUnionVariant("error", typeof(ActionAttemptResetSandboxWorkspaceError))] + [SeamUnionFallback(typeof(ActionAttemptResetSandboxWorkspaceUnrecognized))] + public abstract record ActionAttemptResetSandboxWorkspace : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "RESET_SANDBOX_WORKSPACE"; + } + public sealed record ActionAttemptResetSandboxWorkspaceSuccess + : ActionAttemptResetSandboxWorkspace + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptResetSandboxWorkspaceResult? Result { get; init; } + public ActionAttemptResetSandboxWorkspaceSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptResetSandboxWorkspaceResult { } + public sealed record ActionAttemptResetSandboxWorkspaceSuccessResult { } + + public sealed record ActionAttemptResetSandboxWorkspacePending + : ActionAttemptResetSandboxWorkspace { } + + public sealed record ActionAttemptResetSandboxWorkspaceError + : ActionAttemptResetSandboxWorkspace + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptResetSandboxWorkspaceUnrecognized + : ActionAttemptResetSandboxWorkspace, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } /// /// Setting the fan mode is pending. /// - public sealed record ActionAttemptSetFanMode : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptSetFanModeSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptSetFanModePending))] + [SeamUnionVariant("error", typeof(ActionAttemptSetFanModeError))] + [SeamUnionFallback(typeof(ActionAttemptSetFanModeUnrecognized))] + public abstract record ActionAttemptSetFanMode : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "SET_FAN_MODE"; + } + public sealed record ActionAttemptSetFanModeSuccess : ActionAttemptSetFanMode + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSetFanModeResult? Result { get; init; } + public ActionAttemptSetFanModeSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptSetFanModeResult { } + public sealed record ActionAttemptSetFanModeSuccessResult { } + + public sealed record ActionAttemptSetFanModePending : ActionAttemptSetFanMode { } + + public sealed record ActionAttemptSetFanModeError : ActionAttemptSetFanMode + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptSetFanModeUnrecognized + : ActionAttemptSetFanMode, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } /// /// Setting the HVAC mode is pending. /// - public sealed record ActionAttemptSetHvacMode : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptSetHvacModeSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptSetHvacModePending))] + [SeamUnionVariant("error", typeof(ActionAttemptSetHvacModeError))] + [SeamUnionFallback(typeof(ActionAttemptSetHvacModeUnrecognized))] + public abstract record ActionAttemptSetHvacMode : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "SET_HVAC_MODE"; + } + public sealed record ActionAttemptSetHvacModeSuccess : ActionAttemptSetHvacMode + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSetHvacModeResult? Result { get; init; } + public ActionAttemptSetHvacModeSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptSetHvacModeResult { } + public sealed record ActionAttemptSetHvacModeSuccessResult { } + + public sealed record ActionAttemptSetHvacModePending : ActionAttemptSetHvacMode { } + + public sealed record ActionAttemptSetHvacModeError : ActionAttemptSetHvacMode + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptSetHvacModeUnrecognized + : ActionAttemptSetHvacMode, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } /// /// Activating a climate preset is pending. /// - public sealed record ActionAttemptActivateClimatePreset : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptActivateClimatePresetSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptActivateClimatePresetPending))] + [SeamUnionVariant("error", typeof(ActionAttemptActivateClimatePresetError))] + [SeamUnionFallback(typeof(ActionAttemptActivateClimatePresetUnrecognized))] + public abstract record ActionAttemptActivateClimatePreset : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "ACTIVATE_CLIMATE_PRESET"; + } + public sealed record ActionAttemptActivateClimatePresetSuccess + : ActionAttemptActivateClimatePreset + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptActivateClimatePresetResult? Result { get; init; } + public ActionAttemptActivateClimatePresetSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptActivateClimatePresetResult { } + public sealed record ActionAttemptActivateClimatePresetSuccessResult { } + + public sealed record ActionAttemptActivateClimatePresetPending + : ActionAttemptActivateClimatePreset { } + + public sealed record ActionAttemptActivateClimatePresetError + : ActionAttemptActivateClimatePreset + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptActivateClimatePresetUnrecognized + : ActionAttemptActivateClimatePreset, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } /// /// Simulating a keypad code entry is pending. /// - public sealed record ActionAttemptSimulateKeypadCodeEntry : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptSimulateKeypadCodeEntrySuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptSimulateKeypadCodeEntryPending))] + [SeamUnionVariant("error", typeof(ActionAttemptSimulateKeypadCodeEntryError))] + [SeamUnionFallback(typeof(ActionAttemptSimulateKeypadCodeEntryUnrecognized))] + public abstract record ActionAttemptSimulateKeypadCodeEntry : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "SIMULATE_KEYPAD_CODE_ENTRY"; + } + public sealed record ActionAttemptSimulateKeypadCodeEntrySuccess + : ActionAttemptSimulateKeypadCodeEntry + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSimulateKeypadCodeEntryResult? Result { get; init; } + public ActionAttemptSimulateKeypadCodeEntrySuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptSimulateKeypadCodeEntryResult { } + public sealed record ActionAttemptSimulateKeypadCodeEntrySuccessResult { } + + public sealed record ActionAttemptSimulateKeypadCodeEntryPending + : ActionAttemptSimulateKeypadCodeEntry { } + + public sealed record ActionAttemptSimulateKeypadCodeEntryError + : ActionAttemptSimulateKeypadCodeEntry + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptSimulateKeypadCodeEntryUnrecognized + : ActionAttemptSimulateKeypadCodeEntry, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } /// /// Simulating a manual lock action using a keypad is pending. /// - public sealed record ActionAttemptSimulateManualLockViaKeypad : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptSimulateManualLockViaKeypadSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptSimulateManualLockViaKeypadPending))] + [SeamUnionVariant("error", typeof(ActionAttemptSimulateManualLockViaKeypadError))] + [SeamUnionFallback(typeof(ActionAttemptSimulateManualLockViaKeypadUnrecognized))] + public abstract record ActionAttemptSimulateManualLockViaKeypad : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "SIMULATE_MANUAL_LOCK_VIA_KEYPAD"; + } + public sealed record ActionAttemptSimulateManualLockViaKeypadSuccess + : ActionAttemptSimulateManualLockViaKeypad + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSimulateManualLockViaKeypadResult? Result { get; init; } + public ActionAttemptSimulateManualLockViaKeypadSuccessResult Result { get; init; } = + default!; } - public sealed record ActionAttemptSimulateManualLockViaKeypadResult { } + public sealed record ActionAttemptSimulateManualLockViaKeypadSuccessResult { } + + public sealed record ActionAttemptSimulateManualLockViaKeypadPending + : ActionAttemptSimulateManualLockViaKeypad { } + + public sealed record ActionAttemptSimulateManualLockViaKeypadError + : ActionAttemptSimulateManualLockViaKeypad + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptSimulateManualLockViaKeypadUnrecognized + : ActionAttemptSimulateManualLockViaKeypad, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } /// /// Pushing thermostat weekly programs is pending. /// - public sealed record ActionAttemptPushThermostatPrograms : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptPushThermostatProgramsSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptPushThermostatProgramsPending))] + [SeamUnionVariant("error", typeof(ActionAttemptPushThermostatProgramsError))] + [SeamUnionFallback(typeof(ActionAttemptPushThermostatProgramsUnrecognized))] + public abstract record ActionAttemptPushThermostatPrograms : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "PUSH_THERMOSTAT_PROGRAMS"; + } + public sealed record ActionAttemptPushThermostatProgramsSuccess + : ActionAttemptPushThermostatPrograms + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptPushThermostatProgramsResult? Result { get; init; } + public ActionAttemptPushThermostatProgramsSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptPushThermostatProgramsResult { } + public sealed record ActionAttemptPushThermostatProgramsSuccessResult { } + + public sealed record ActionAttemptPushThermostatProgramsPending + : ActionAttemptPushThermostatPrograms { } + + public sealed record ActionAttemptPushThermostatProgramsError + : ActionAttemptPushThermostatPrograms + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptPushThermostatProgramsUnrecognized + : ActionAttemptPushThermostatPrograms, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } /// /// Configuring the auto-lock is pending. /// - public sealed record ActionAttemptConfigureAutoLock : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptConfigureAutoLockSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptConfigureAutoLockPending))] + [SeamUnionVariant("error", typeof(ActionAttemptConfigureAutoLockError))] + [SeamUnionFallback(typeof(ActionAttemptConfigureAutoLockUnrecognized))] + public abstract record ActionAttemptConfigureAutoLock : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "CONFIGURE_AUTO_LOCK"; + } + public sealed record ActionAttemptConfigureAutoLockSuccess : ActionAttemptConfigureAutoLock + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptConfigureAutoLockResult? Result { get; init; } + public ActionAttemptConfigureAutoLockSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptConfigureAutoLockResult { } + public sealed record ActionAttemptConfigureAutoLockSuccessResult { } + + public sealed record ActionAttemptConfigureAutoLockPending : ActionAttemptConfigureAutoLock { } - public sealed record ActionAttemptSyncAccessCodes : ActionAttempt + public sealed record ActionAttemptConfigureAutoLockError : ActionAttemptConfigureAutoLock + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptConfigureAutoLockUnrecognized + : ActionAttemptConfigureAutoLock, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptSyncAccessCodesSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptSyncAccessCodesPending))] + [SeamUnionVariant("error", typeof(ActionAttemptSyncAccessCodesError))] + [SeamUnionFallback(typeof(ActionAttemptSyncAccessCodesUnrecognized))] + public abstract record ActionAttemptSyncAccessCodes : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "SYNC_ACCESS_CODES"; + } + public sealed record ActionAttemptSyncAccessCodesSuccess : ActionAttemptSyncAccessCodes + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptSyncAccessCodesResult? Result { get; init; } + public ActionAttemptSyncAccessCodesSuccessResult Result { get; init; } = default!; + } + + public sealed record ActionAttemptSyncAccessCodesSuccessResult { } + + public sealed record ActionAttemptSyncAccessCodesPending : ActionAttemptSyncAccessCodes { } + + public sealed record ActionAttemptSyncAccessCodesError : ActionAttemptSyncAccessCodes + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; } - public sealed record ActionAttemptSyncAccessCodesResult { } + public sealed record ActionAttemptSyncAccessCodesUnrecognized + : ActionAttemptSyncAccessCodes, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } - public sealed record ActionAttemptCreateAccessCode : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptCreateAccessCodeSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptCreateAccessCodePending))] + [SeamUnionVariant("error", typeof(ActionAttemptCreateAccessCodeError))] + [SeamUnionFallback(typeof(ActionAttemptCreateAccessCodeUnrecognized))] + public abstract record ActionAttemptCreateAccessCode : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "CREATE_ACCESS_CODE"; + } + public sealed record ActionAttemptCreateAccessCodeSuccess : ActionAttemptCreateAccessCode + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptCreateAccessCodeResult? Result { get; init; } + public ActionAttemptCreateAccessCodeSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptCreateAccessCodeResult + public sealed record ActionAttemptCreateAccessCodeSuccessResult { /// /// Created access code. @@ -2202,37 +2621,91 @@ public sealed record ActionAttemptCreateAccessCodeResult public object AccessCode { get; init; } = default!; } - public sealed record ActionAttemptDeleteAccessCode : ActionAttempt + public sealed record ActionAttemptCreateAccessCodePending : ActionAttemptCreateAccessCode { } + + public sealed record ActionAttemptCreateAccessCodeError : ActionAttemptCreateAccessCode + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptCreateAccessCodeUnrecognized + : ActionAttemptCreateAccessCode, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptDeleteAccessCodeSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptDeleteAccessCodePending))] + [SeamUnionVariant("error", typeof(ActionAttemptDeleteAccessCodeError))] + [SeamUnionFallback(typeof(ActionAttemptDeleteAccessCodeUnrecognized))] + public abstract record ActionAttemptDeleteAccessCode : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "DELETE_ACCESS_CODE"; + } + public sealed record ActionAttemptDeleteAccessCodeSuccess : ActionAttemptDeleteAccessCode + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptDeleteAccessCodeResult? Result { get; init; } + public ActionAttemptDeleteAccessCodeSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptDeleteAccessCodeResult { } + public sealed record ActionAttemptDeleteAccessCodeSuccessResult { } + + public sealed record ActionAttemptDeleteAccessCodePending : ActionAttemptDeleteAccessCode { } - public sealed record ActionAttemptUpdateAccessCode : ActionAttempt + public sealed record ActionAttemptDeleteAccessCodeError : ActionAttemptDeleteAccessCode + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptDeleteAccessCodeUnrecognized + : ActionAttemptDeleteAccessCode, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptUpdateAccessCodeSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptUpdateAccessCodePending))] + [SeamUnionVariant("error", typeof(ActionAttemptUpdateAccessCodeError))] + [SeamUnionFallback(typeof(ActionAttemptUpdateAccessCodeUnrecognized))] + public abstract record ActionAttemptUpdateAccessCode : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "UPDATE_ACCESS_CODE"; + } + public sealed record ActionAttemptUpdateAccessCodeSuccess : ActionAttemptUpdateAccessCode + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptUpdateAccessCodeResult? Result { get; init; } + public ActionAttemptUpdateAccessCodeSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptUpdateAccessCodeResult + public sealed record ActionAttemptUpdateAccessCodeSuccessResult { /// /// Updated access code. @@ -2241,21 +2714,49 @@ public sealed record ActionAttemptUpdateAccessCodeResult public object? AccessCode { get; init; } } - public sealed record ActionAttemptCreateNoiseThreshold : ActionAttempt + public sealed record ActionAttemptUpdateAccessCodePending : ActionAttemptUpdateAccessCode { } + + public sealed record ActionAttemptUpdateAccessCodeError : ActionAttemptUpdateAccessCode + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptUpdateAccessCodeUnrecognized + : ActionAttemptUpdateAccessCode, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptCreateNoiseThresholdSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptCreateNoiseThresholdPending))] + [SeamUnionVariant("error", typeof(ActionAttemptCreateNoiseThresholdError))] + [SeamUnionFallback(typeof(ActionAttemptCreateNoiseThresholdUnrecognized))] + public abstract record ActionAttemptCreateNoiseThreshold : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "CREATE_NOISE_THRESHOLD"; + } + public sealed record ActionAttemptCreateNoiseThresholdSuccess + : ActionAttemptCreateNoiseThreshold + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptCreateNoiseThresholdResult? Result { get; init; } + public ActionAttemptCreateNoiseThresholdSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptCreateNoiseThresholdResult + public sealed record ActionAttemptCreateNoiseThresholdSuccessResult { /// /// Created noise threshold. @@ -2264,37 +2765,95 @@ public sealed record ActionAttemptCreateNoiseThresholdResult public object NoiseThreshold { get; init; } = default!; } - public sealed record ActionAttemptDeleteNoiseThreshold : ActionAttempt + public sealed record ActionAttemptCreateNoiseThresholdPending + : ActionAttemptCreateNoiseThreshold { } + + public sealed record ActionAttemptCreateNoiseThresholdError : ActionAttemptCreateNoiseThreshold + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptCreateNoiseThresholdUnrecognized + : ActionAttemptCreateNoiseThreshold, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptDeleteNoiseThresholdSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptDeleteNoiseThresholdPending))] + [SeamUnionVariant("error", typeof(ActionAttemptDeleteNoiseThresholdError))] + [SeamUnionFallback(typeof(ActionAttemptDeleteNoiseThresholdUnrecognized))] + public abstract record ActionAttemptDeleteNoiseThreshold : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "DELETE_NOISE_THRESHOLD"; + } + public sealed record ActionAttemptDeleteNoiseThresholdSuccess + : ActionAttemptDeleteNoiseThreshold + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptDeleteNoiseThresholdResult? Result { get; init; } + public ActionAttemptDeleteNoiseThresholdSuccessResult Result { get; init; } = default!; + } + + public sealed record ActionAttemptDeleteNoiseThresholdSuccessResult { } + + public sealed record ActionAttemptDeleteNoiseThresholdPending + : ActionAttemptDeleteNoiseThreshold { } + + public sealed record ActionAttemptDeleteNoiseThresholdError : ActionAttemptDeleteNoiseThreshold + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; } - public sealed record ActionAttemptDeleteNoiseThresholdResult { } + public sealed record ActionAttemptDeleteNoiseThresholdUnrecognized + : ActionAttemptDeleteNoiseThreshold, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } - public sealed record ActionAttemptUpdateNoiseThreshold : ActionAttempt + [JsonConverter(typeof(SeamUnionConverter))] + [SeamUnion("status")] + [SeamUnionVariant("success", typeof(ActionAttemptUpdateNoiseThresholdSuccess))] + [SeamUnionVariant("pending", typeof(ActionAttemptUpdateNoiseThresholdPending))] + [SeamUnionVariant("error", typeof(ActionAttemptUpdateNoiseThresholdError))] + [SeamUnionFallback(typeof(ActionAttemptUpdateNoiseThresholdUnrecognized))] + public abstract record ActionAttemptUpdateNoiseThreshold : ActionAttempt { [JsonPropertyName("action_type")] public override string ActionType { get; } = "UPDATE_NOISE_THRESHOLD"; + } + public sealed record ActionAttemptUpdateNoiseThresholdSuccess + : ActionAttemptUpdateNoiseThreshold + { /// /// Result of the action. - /// - /// Null unless the action attempt `status` is `success`. /// [JsonPropertyName("result")] - public ActionAttemptUpdateNoiseThresholdResult? Result { get; init; } + public ActionAttemptUpdateNoiseThresholdSuccessResult Result { get; init; } = default!; } - public sealed record ActionAttemptUpdateNoiseThresholdResult + public sealed record ActionAttemptUpdateNoiseThresholdSuccessResult { /// /// Updated noise threshold. @@ -2303,6 +2862,27 @@ public sealed record ActionAttemptUpdateNoiseThresholdResult public object NoiseThreshold { get; init; } = default!; } + public sealed record ActionAttemptUpdateNoiseThresholdPending + : ActionAttemptUpdateNoiseThreshold { } + + public sealed record ActionAttemptUpdateNoiseThresholdError : ActionAttemptUpdateNoiseThreshold + { + /// + /// Error associated with the action. + /// + [JsonPropertyName("error")] + public ActionAttemptError Error { get; init; } = default!; + } + + public sealed record ActionAttemptUpdateNoiseThresholdUnrecognized + : ActionAttemptUpdateNoiseThreshold, + ISeamUnrecognizedVariant + { + /// The complete raw JSON of the unrecognized payload. + [JsonIgnore] + public JsonElement RawJson { get; set; } + } + public sealed record ActionAttemptUnrecognized : ActionAttempt, ISeamUnrecognizedVariant { [JsonPropertyName("action_type")] From 2284dadf4e3ec608e7de1da5144c52a603964305 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 03:56:26 +0000 Subject: [PATCH 6/7] refactor: remove narration comments Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp --- codegen/lib/build-model.ts | 24 ++----------------- codegen/lib/class-model.ts | 9 +------ .../Exceptions/SeamActionAttemptException.cs | 5 ---- test/Seam.Test/SerializationTests.cs | 1 - 4 files changed, 3 insertions(+), 36 deletions(-) diff --git a/codegen/lib/build-model.ts b/codegen/lib/build-model.ts index eeadd933..38a0410f 100644 --- a/codegen/lib/build-model.ts +++ b/codegen/lib/build-model.ts @@ -98,14 +98,9 @@ type Kind = interface Variant { value: string - // Used for the shared-property lifting computation, and to build the - // variant subclass unless buildAsUnion is set. fields: Field[] description?: string deprecationMessage?: string - // Builds the variant as a nested union discriminated on another property - // instead of a plain subclass. Receives the names declared on (or omitted - // by) the outer base, which the inner union must not redeclare. buildAsUnion?: (subName: string, omitNames: Set) => CsUnion } @@ -313,9 +308,7 @@ interface BuildClassOptions { // nullable parameters); models deserialize leniently. resourceType: 'response' | 'request' | 'model' // When set, the class is a discriminated-union subclass: the discriminator - // property is emitted as a get-only override with a constant value, unless - // declareProperty is false (the discriminator property is inherited from an - // ancestor class). + // property is emitted as a get-only override with a constant value. discriminator?: { name: string value: string @@ -477,12 +470,8 @@ interface BuildUnionOptions { // status contract the runtime resolver depends on. omitFieldNames?: string[] extraBaseProps?: CsProperty[] - // See CsUnion for these. baseClass?: string inheritsDiscriminator?: boolean - // Properties declared on the base before the lifted and extra properties, - // e.g. the outer discriminator's get-only override when the union is itself - // a variant of an outer union. leadingBaseProps?: CsProperty[] documentation?: string obsoleteMessage?: string @@ -638,9 +627,6 @@ export const buildModelFile = ( return { name, file: { decls: [built.main, ...built.siblings] } } } -// The types the action attempt runtime (resolver and exceptions) owns rather -// than the generator: status everywhere, and the error wire shape wherever an -// action attempt declares an error property. const actionAttemptRuntimeTypes: Record = { error: 'ActionAttemptError', } @@ -660,11 +646,6 @@ const findActionAttemptStatusProperty = ( property.name === 'status' && property.format === 'enum', ) -// One variant per status from the status enum. A property whose -// actionAttemptStatuses annotation lists the status keeps its normal -// non-nullable type on that status subclass; one whose annotation does not -// list it is absent there, so dereferencing it without narrowing to the -// right status subclass does not compile. const buildActionAttemptStatusUnion = ( actionAttempt: ActionAttempt, statusProperty: EnumProperty, @@ -717,8 +698,7 @@ export const buildActionAttemptFile = ( ): { name: string; file: CsModelFile } => { // The status of every action attempt shares one wire shape, so it is // declared once on the base with the runtime-owned ActionAttemptStatus type - // the action attempt resolver depends on. Each action type then nests a - // union discriminated on status. + // the action attempt resolver depends on. const union = buildUnion( 'ActionAttempt', 'action_type', diff --git a/codegen/lib/class-model.ts b/codegen/lib/class-model.ts index e677b890..ec4fe195 100644 --- a/codegen/lib/class-model.ts +++ b/codegen/lib/class-model.ts @@ -70,15 +70,9 @@ export interface CsClass { export interface CsUnion { kind: 'union' className: string - // Set when the union base is itself a variant of an outer union: the base - // extends the outer union's base class and declares the outer - // discriminator's get-only override in baseProps. baseClass?: string discriminatorSnake: string discriminatorPascal: string - // The discriminator wire property is already declared by an ancestor class - // (e.g. status on the action attempt base), so the union base declares no - // abstract discriminator property and the subclasses declare no override. inheritsDiscriminator?: boolean // [SeamUnionVariant] attributes, in variant definition order. knownSubTypes: Array<{ typeName: string; value: string }> @@ -88,8 +82,7 @@ export interface CsUnion { // inherit them rather than redeclare them. baseProps: CsProperty[] // Concrete subclasses followed by the Unrecognized fallback, in definition - // order. A subclass may itself be a union discriminated on another - // property. + // order. subclasses: Array documentation?: string obsoleteMessage?: string diff --git a/src/Seam/Exceptions/SeamActionAttemptException.cs b/src/Seam/Exceptions/SeamActionAttemptException.cs index 4132d8bd..2ca163c5 100644 --- a/src/Seam/Exceptions/SeamActionAttemptException.cs +++ b/src/Seam/Exceptions/SeamActionAttemptException.cs @@ -30,11 +30,6 @@ public SeamActionAttemptFailedException(Models.ActionAttempt actionAttempt) /// The action attempt error type. public string Code { get; } - /// - /// The error property is declared on the status subclasses that carry it, not on - /// , and is absent entirely on an attempt with an - /// unrecognized action type or status. - /// private static Models.ActionAttemptError? GetError(Models.ActionAttempt actionAttempt) => actionAttempt.GetType().GetProperty("Error")?.GetValue(actionAttempt) as Models.ActionAttemptError; diff --git a/test/Seam.Test/SerializationTests.cs b/test/Seam.Test/SerializationTests.cs index 8a70abc0..092f824c 100644 --- a/test/Seam.Test/SerializationTests.cs +++ b/test/Seam.Test/SerializationTests.cs @@ -46,7 +46,6 @@ public void UnknownActionTypeDeserializesToUnrecognizedVariant() Assert.Equal("attempt1", unrecognized.ActionAttemptId); Assert.Equal(ActionAttemptStatus.Pending, unrecognized.Status); - // The raw payload of an unrecognized variant is preserved, not discarded. Assert.Equal( "BRAND_NEW_ACTION", unrecognized.RawJson.GetProperty("action_type").GetString() From 62941cd9cce8b86b45a957cb66c8a9cfea234fbc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 16:02:39 +0000 Subject: [PATCH 7/7] docs: document status-dependent action attempt values Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01EdWS7o3htQ9cNxhCWL5Frp --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index f48c39be..2abf4542 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,31 @@ succeeds and returns the finished attempt, raising var actionAttempt = await seam.Locks.UnlockDoorAsync(new() { DeviceId = deviceId }); ``` +Each action attempt deserializes to a subclass for its `action_type` and +`status` pair, e.g. `ActionAttemptUnlockDoorSuccess`. The `Error` and `Result` +properties are declared only on the status subclass that populates them, so +pattern match on the subclass to read them: + +```csharp +var actionAttempt = await seam.Locks.UnlockDoorAsync( + new() { DeviceId = deviceId }, + waitForActionAttempt: false +); + +switch (actionAttempt) +{ + case ActionAttemptUnlockDoorSuccess success: + Console.WriteLine(success.Result.WasConfirmedByDevice); + break; + case ActionAttemptUnlockDoorError error: + Console.WriteLine(error.Error.Message); + break; + case ActionAttemptUnlockDoorPending: + Console.WriteLine("Still pending"); + break; +} +``` + Configure or disable waiting per client or per call with `ActionAttemptWait`: ```csharp