From 0c735c1585488eb4cb5a6bb500ddb1397242ee82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 10:58:43 +0000 Subject: [PATCH 01/16] Initial plan From 9b89729c8d825876595651b5afefae4c356aa611 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:00:16 +0000 Subject: [PATCH 02/16] Add common_properties_error_client_test.rs with tests for create_for_user_defined_error and get_for_predefined_error Co-authored-by: antkmsft <41349689+antkmsft@users.noreply.github.com> --- .../common_properties_error_client_test.rs | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs new file mode 100644 index 000000000..9fc792d7c --- /dev/null +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Licensed under the MIT License. See License.txt in the project root for license information. + +use azure_core::credentials::{AccessToken, TokenCredential, TokenRequestOptions}; +use azure_core::time::OffsetDateTime; +use azure_core::Result; +use spector_armcommon::models::{ConfidentialResource, ConfidentialResourceProperties}; +use spector_armcommon::CommonPropertiesClient; +use std::collections::HashMap; +use std::sync::Arc; + +#[derive(Debug)] +struct FakeTokenCredential { + pub token: String, +} + +impl FakeTokenCredential { + pub fn new(token: String) -> Self { + FakeTokenCredential { token } + } +} + +#[async_trait::async_trait] +impl TokenCredential for FakeTokenCredential { + async fn get_token( + &self, + _scopes: &[&str], + _options: Option>, + ) -> Result { + Ok(AccessToken::new( + self.token.clone(), + OffsetDateTime::now_utc(), + )) + } +} + +fn create_client() -> CommonPropertiesClient { + CommonPropertiesClient::new( + "http://localhost:3000", + Arc::new(FakeTokenCredential::new("fake_token".to_string())), + "00000000-0000-0000-0000-000000000000".to_string(), + None, + ) + .unwrap() +} + +fn get_valid_confidential_resource() -> ConfidentialResource { + ConfidentialResource { + id: Some("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.CommonProperties/confidentialResources/resource".to_string()), + location: Some("eastus".to_string()), + name: Some("resource".to_string()), + properties: Some(ConfidentialResourceProperties { + provisioning_state: Some("Succeeded".to_string()), + username: Some("testuser".to_string()), + }), + tags: Some(HashMap::from([( + "tagKey1".to_string(), + "tagValue1".to_string(), + )])), + type_prop: Some("Azure.ResourceManager.CommonProperties/confidentialResources".to_string()), + ..Default::default() + } +} + +#[tokio::test] +async fn create_for_user_defined_error() { + let resource = ConfidentialResource { + location: Some("eastus".to_string()), + properties: Some(ConfidentialResourceProperties { + username: Some("testuser".to_string()), + ..Default::default() + }), + ..Default::default() + }; + + let client = create_client(); + let resp = client + .get_common_properties_error_client() + .create_for_user_defined_error("test-rg", "resource", resource.try_into().unwrap(), None) + .await + .unwrap(); + + let confidential_resource: ConfidentialResource = resp.into_model().unwrap(); + let expected_resource = get_valid_confidential_resource(); + + assert_eq!(expected_resource.id, confidential_resource.id); + assert_eq!(expected_resource.location, confidential_resource.location); + assert_eq!(expected_resource.name, confidential_resource.name); + assert_eq!(expected_resource.tags, confidential_resource.tags); + assert_eq!(expected_resource.type_prop, confidential_resource.type_prop); + + let expected_properties = expected_resource.properties.unwrap(); + let confidential_properties = confidential_resource.properties.unwrap(); + assert_eq!( + expected_properties.provisioning_state, + confidential_properties.provisioning_state, + ); + assert_eq!( + expected_properties.username, + confidential_properties.username, + ); +} + +#[tokio::test] +async fn get_for_predefined_error() { + let client = create_client(); + let resp = client + .get_common_properties_error_client() + .get_for_predefined_error("test-rg", "resource", None) + .await + .unwrap(); + + let confidential_resource: ConfidentialResource = resp.into_model().unwrap(); + let expected_resource = get_valid_confidential_resource(); + + assert_eq!(expected_resource.id, confidential_resource.id); + assert_eq!(expected_resource.location, confidential_resource.location); + assert_eq!(expected_resource.name, confidential_resource.name); + assert_eq!(expected_resource.tags, confidential_resource.tags); + assert_eq!(expected_resource.type_prop, confidential_resource.type_prop); + + let expected_properties = expected_resource.properties.unwrap(); + let confidential_properties = confidential_resource.properties.unwrap(); + assert_eq!( + expected_properties.provisioning_state, + confidential_properties.provisioning_state, + ); + assert_eq!( + expected_properties.username, + confidential_properties.username, + ); +} From d7b7dc51e5e87b3f9f6a4eb25176d23798f99faa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:08:31 +0000 Subject: [PATCH 03/16] Fix trailing whitespace in test file Co-authored-by: antkmsft <41349689+antkmsft@users.noreply.github.com> --- .../tests/common_properties_error_client_test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index 9fc792d7c..311d261f1 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -83,7 +83,7 @@ async fn create_for_user_defined_error() { let confidential_resource: ConfidentialResource = resp.into_model().unwrap(); let expected_resource = get_valid_confidential_resource(); - + assert_eq!(expected_resource.id, confidential_resource.id); assert_eq!(expected_resource.location, confidential_resource.location); assert_eq!(expected_resource.name, confidential_resource.name); @@ -113,7 +113,7 @@ async fn get_for_predefined_error() { let confidential_resource: ConfidentialResource = resp.into_model().unwrap(); let expected_resource = get_valid_confidential_resource(); - + assert_eq!(expected_resource.id, confidential_resource.id); assert_eq!(expected_resource.location, confidential_resource.location); assert_eq!(expected_resource.name, confidential_resource.name); From 5a1bbe011711e2fdb42d6511c1a240584f5387dd Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Tue, 10 Feb 2026 14:10:24 -0800 Subject: [PATCH 04/16] Make fixes --- .../common_properties_error_client_test.rs | 51 +++---------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index 311d261f1..64596207d 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -3,6 +3,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. use azure_core::credentials::{AccessToken, TokenCredential, TokenRequestOptions}; +use azure_core::http::StatusCode; use azure_core::time::OffsetDateTime; use azure_core::Result; use spector_armcommon::models::{ConfidentialResource, ConfidentialResourceProperties}; @@ -75,59 +76,21 @@ async fn create_for_user_defined_error() { }; let client = create_client(); - let resp = client + let rsp = client .get_common_properties_error_client() .create_for_user_defined_error("test-rg", "resource", resource.try_into().unwrap(), None) - .await - .unwrap(); + .await; - let confidential_resource: ConfidentialResource = resp.into_model().unwrap(); - let expected_resource = get_valid_confidential_resource(); - - assert_eq!(expected_resource.id, confidential_resource.id); - assert_eq!(expected_resource.location, confidential_resource.location); - assert_eq!(expected_resource.name, confidential_resource.name); - assert_eq!(expected_resource.tags, confidential_resource.tags); - assert_eq!(expected_resource.type_prop, confidential_resource.type_prop); - - let expected_properties = expected_resource.properties.unwrap(); - let confidential_properties = confidential_resource.properties.unwrap(); - assert_eq!( - expected_properties.provisioning_state, - confidential_properties.provisioning_state, - ); - assert_eq!( - expected_properties.username, - confidential_properties.username, - ); + assert_eq!(rsp.unwrap_err().http_status(), Some(StatusCode::BadRequest)); } #[tokio::test] async fn get_for_predefined_error() { let client = create_client(); - let resp = client + let rsp = client .get_common_properties_error_client() .get_for_predefined_error("test-rg", "resource", None) - .await - .unwrap(); - - let confidential_resource: ConfidentialResource = resp.into_model().unwrap(); - let expected_resource = get_valid_confidential_resource(); - - assert_eq!(expected_resource.id, confidential_resource.id); - assert_eq!(expected_resource.location, confidential_resource.location); - assert_eq!(expected_resource.name, confidential_resource.name); - assert_eq!(expected_resource.tags, confidential_resource.tags); - assert_eq!(expected_resource.type_prop, confidential_resource.type_prop); + .await; - let expected_properties = expected_resource.properties.unwrap(); - let confidential_properties = confidential_resource.properties.unwrap(); - assert_eq!( - expected_properties.provisioning_state, - confidential_properties.provisioning_state, - ); - assert_eq!( - expected_properties.username, - confidential_properties.username, - ); + assert_eq!(rsp.unwrap_err().http_status(), Some(StatusCode::NotFound)); } From 99477d15f7e973d8e017cf1d5caf75f0218b8c08 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Tue, 10 Feb 2026 15:06:25 -0800 Subject: [PATCH 05/16] Clippy --- .../common_properties_error_client_test.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index 64596207d..57f327479 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -46,24 +46,6 @@ fn create_client() -> CommonPropertiesClient { .unwrap() } -fn get_valid_confidential_resource() -> ConfidentialResource { - ConfidentialResource { - id: Some("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Azure.ResourceManager.CommonProperties/confidentialResources/resource".to_string()), - location: Some("eastus".to_string()), - name: Some("resource".to_string()), - properties: Some(ConfidentialResourceProperties { - provisioning_state: Some("Succeeded".to_string()), - username: Some("testuser".to_string()), - }), - tags: Some(HashMap::from([( - "tagKey1".to_string(), - "tagValue1".to_string(), - )])), - type_prop: Some("Azure.ResourceManager.CommonProperties/confidentialResources".to_string()), - ..Default::default() - } -} - #[tokio::test] async fn create_for_user_defined_error() { let resource = ConfidentialResource { From a0cfacafe1bbee16e8f7bb98e335d384a666f41a Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Tue, 10 Feb 2026 15:32:00 -0800 Subject: [PATCH 06/16] Clippy --- .../tests/common_properties_error_client_test.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index 57f327479..c3220d96a 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -8,7 +8,6 @@ use azure_core::time::OffsetDateTime; use azure_core::Result; use spector_armcommon::models::{ConfidentialResource, ConfidentialResourceProperties}; use spector_armcommon::CommonPropertiesClient; -use std::collections::HashMap; use std::sync::Arc; #[derive(Debug)] From 3515a362eb2a367748f6eca79e10f2548c7cdd3a Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Thu, 12 Feb 2026 11:43:49 -0800 Subject: [PATCH 07/16] Remove code duplication --- .../common-properties/tests/common/mod.rs | 44 ++++++++++++++++ .../common_properties_error_client_test.rs | 46 ++-------------- ...properties_managed_identity_client_test.rs | 52 +++---------------- 3 files changed, 55 insertions(+), 87 deletions(-) create mode 100644 packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common/mod.rs diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common/mod.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common/mod.rs new file mode 100644 index 000000000..019b29181 --- /dev/null +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common/mod.rs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Licensed under the MIT License. See License.txt in the project root for license information. + +use azure_core::credentials::{AccessToken, TokenCredential, TokenRequestOptions}; +use azure_core::time::OffsetDateTime; +use azure_core::Result; +use spector_armcommon::CommonPropertiesClient; +use std::sync::Arc; + +#[derive(Debug)] +struct FakeTokenCredential { + pub token: String, +} + +impl FakeTokenCredential { + pub fn new(token: String) -> Self { + FakeTokenCredential { token } + } +} + +#[async_trait::async_trait] +impl TokenCredential for FakeTokenCredential { + async fn get_token( + &self, + _scopes: &[&str], + _options: Option>, + ) -> Result { + Ok(AccessToken::new( + self.token.clone(), + OffsetDateTime::now_utc(), + )) + } +} + +pub fn create_client() -> CommonPropertiesClient { + CommonPropertiesClient::new( + "http://localhost:3000", + Arc::new(FakeTokenCredential::new("fake_token".to_string())), + "00000000-0000-0000-0000-000000000000".to_string(), + None, + ) + .unwrap() +} diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index c3220d96a..8f6379e1c 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -2,48 +2,10 @@ // // Licensed under the MIT License. See License.txt in the project root for license information. -use azure_core::credentials::{AccessToken, TokenCredential, TokenRequestOptions}; +mod common; + use azure_core::http::StatusCode; -use azure_core::time::OffsetDateTime; -use azure_core::Result; use spector_armcommon::models::{ConfidentialResource, ConfidentialResourceProperties}; -use spector_armcommon::CommonPropertiesClient; -use std::sync::Arc; - -#[derive(Debug)] -struct FakeTokenCredential { - pub token: String, -} - -impl FakeTokenCredential { - pub fn new(token: String) -> Self { - FakeTokenCredential { token } - } -} - -#[async_trait::async_trait] -impl TokenCredential for FakeTokenCredential { - async fn get_token( - &self, - _scopes: &[&str], - _options: Option>, - ) -> Result { - Ok(AccessToken::new( - self.token.clone(), - OffsetDateTime::now_utc(), - )) - } -} - -fn create_client() -> CommonPropertiesClient { - CommonPropertiesClient::new( - "http://localhost:3000", - Arc::new(FakeTokenCredential::new("fake_token".to_string())), - "00000000-0000-0000-0000-000000000000".to_string(), - None, - ) - .unwrap() -} #[tokio::test] async fn create_for_user_defined_error() { @@ -56,7 +18,7 @@ async fn create_for_user_defined_error() { ..Default::default() }; - let client = create_client(); + let client = common::create_client(); let rsp = client .get_common_properties_error_client() .create_for_user_defined_error("test-rg", "resource", resource.try_into().unwrap(), None) @@ -67,7 +29,7 @@ async fn create_for_user_defined_error() { #[tokio::test] async fn get_for_predefined_error() { - let client = create_client(); + let client = common::create_client(); let rsp = client .get_common_properties_error_client() .get_for_predefined_error("test-rg", "resource", None) diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_managed_identity_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_managed_identity_client_test.rs index 53a8c07fb..267a09afd 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_managed_identity_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_managed_identity_client_test.rs @@ -2,51 +2,13 @@ // // Licensed under the MIT License. See License.txt in the project root for license information. -use azure_core::credentials::{AccessToken, TokenCredential, TokenRequestOptions}; -use azure_core::time::OffsetDateTime; -use azure_core::Result; +mod common; + use spector_armcommon::models::{ ManagedIdentityTrackedResource, ManagedIdentityTrackedResourceProperties, ManagedServiceIdentity, ManagedServiceIdentityType, UserAssignedIdentity, }; -use spector_armcommon::CommonPropertiesClient; use std::collections::HashMap; -use std::sync::Arc; - -#[derive(Debug)] -struct FakeTokenCredential { - pub token: String, -} - -impl FakeTokenCredential { - pub fn new(token: String) -> Self { - FakeTokenCredential { token } - } -} - -#[async_trait::async_trait] -impl TokenCredential for FakeTokenCredential { - async fn get_token( - &self, - _scopes: &[&str], - _options: Option>, - ) -> Result { - Ok(AccessToken::new( - self.token.clone(), - OffsetDateTime::now_utc(), - )) - } -} - -fn create_client() -> CommonPropertiesClient { - CommonPropertiesClient::new( - "http://localhost:3000", - Arc::new(FakeTokenCredential::new("fake_token".to_string())), - "00000000-0000-0000-0000-000000000000".to_string(), - None, - ) - .unwrap() -} fn get_valid_mi_resource() -> ManagedIdentityTrackedResource { ManagedIdentityTrackedResource { @@ -55,7 +17,7 @@ fn get_valid_mi_resource() -> ManagedIdentityTrackedResource { principal_id: Some("00000000-0000-0000-0000-000000000000".to_string()), tenant_id: Some("00000000-0000-0000-0000-000000000000".to_string()), type_prop: - Some(spector_armcommon::models::ManagedServiceIdentityType::SystemAssigned), + Some(ManagedServiceIdentityType::SystemAssigned), ..Default::default() }), location: Some("eastus".to_string()), @@ -74,14 +36,14 @@ fn get_valid_mi_resource() -> ManagedIdentityTrackedResource { async fn create_with_system_assigned() { let resource = ManagedIdentityTrackedResource { identity: Some(ManagedServiceIdentity { - type_prop: Some(spector_armcommon::models::ManagedServiceIdentityType::SystemAssigned), + type_prop: Some(ManagedServiceIdentityType::SystemAssigned), ..Default::default() }), location: Some("eastus".to_string()), ..Default::default() }; - let client = create_client(); + let client = common::create_client(); let resp = client .get_common_properties_managed_identity_client() .create_with_system_assigned("test-rg", "identity", resource.try_into().unwrap(), None) @@ -111,7 +73,7 @@ async fn create_with_system_assigned() { #[tokio::test] async fn get() { - let client = create_client(); + let client = common::create_client(); let resp = client .get_common_properties_managed_identity_client() .get("test-rg", "identity", None) @@ -153,7 +115,7 @@ async fn update_with_user_assigned_and_system_assigned() { ..Default::default() }; - let client = create_client(); + let client = common::create_client(); let resp = client .get_common_properties_managed_identity_client() .update_with_user_assigned_and_system_assigned( From f0ccaf9a3137b4f43a93dd5cbd3f690d477a7204 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 13 Feb 2026 01:47:21 -0800 Subject: [PATCH 08/16] Verify error messages in the test --- .../common_properties_error_client_test.rs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index 8f6379e1c..9d192ce3d 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -5,14 +5,28 @@ mod common; use azure_core::http::StatusCode; +use azure_core::Error; use spector_armcommon::models::{ConfidentialResource, ConfidentialResourceProperties}; +#[tokio::test] +async fn get_for_predefined_error() { + let client = common::create_client(); + let rsp = client + .get_common_properties_error_client() + .get_for_predefined_error("test-rg", "resource", None) + .await; + + let err = rsp.unwrap_err(); + assert_eq!(err.http_status(), Some(StatusCode::NotFound)); + assert_eq!(err.to_string(), "The Resource 'Azure.ResourceManager.CommonProperties/confidentialResources/confidential' under resource group 'test-rg' was not found."); +} + #[tokio::test] async fn create_for_user_defined_error() { let resource = ConfidentialResource { location: Some("eastus".to_string()), properties: Some(ConfidentialResourceProperties { - username: Some("testuser".to_string()), + username: Some("00".to_string()), ..Default::default() }), ..Default::default() @@ -24,16 +38,7 @@ async fn create_for_user_defined_error() { .create_for_user_defined_error("test-rg", "resource", resource.try_into().unwrap(), None) .await; - assert_eq!(rsp.unwrap_err().http_status(), Some(StatusCode::BadRequest)); -} - -#[tokio::test] -async fn get_for_predefined_error() { - let client = common::create_client(); - let rsp = client - .get_common_properties_error_client() - .get_for_predefined_error("test-rg", "resource", None) - .await; - - assert_eq!(rsp.unwrap_err().http_status(), Some(StatusCode::NotFound)); + let err = rsp.unwrap_err(); + assert_eq!(err.http_status(), Some(StatusCode::BadRequest)); + assert_eq!(err.to_string(), "Username should not contain only numbers."); } From c34b69861a605ee13cb408fa694f104bb30185b9 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 13 Feb 2026 13:27:53 -0800 Subject: [PATCH 09/16] Clippy --- packages/typespec-rust/src/tcgcadapter/adapter.ts | 7 ++++++- .../tests/common_properties_error_client_test.rs | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/typespec-rust/src/tcgcadapter/adapter.ts b/packages/typespec-rust/src/tcgcadapter/adapter.ts index 2618e9585..85e6469f7 100644 --- a/packages/typespec-rust/src/tcgcadapter/adapter.ts +++ b/packages/typespec-rust/src/tcgcadapter/adapter.ts @@ -173,7 +173,12 @@ export class Adapter { const processedTypes = new Set(); for (const model of this.ctx.sdkPackage.models) { - if ((model.usage & tcgc.UsageFlags.Input) === 0 && (model.usage & tcgc.UsageFlags.Output) === 0 && (model.usage & tcgc.UsageFlags.Spread) === 0) { + if ( + (model.usage & tcgc.UsageFlags.Input) === 0 + && (model.usage & tcgc.UsageFlags.Output) === 0 + && (model.usage & tcgc.UsageFlags.Spread) === 0 + && (model.usage & tcgc.UsageFlags.Exception) === 0 + ) { // skip types without input and output usage. this will include core // types unless they're explicitly referenced (e.g. a model property). // we keep the models for spread params as we internally use them. diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index 9d192ce3d..2f731a16b 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -5,7 +5,6 @@ mod common; use azure_core::http::StatusCode; -use azure_core::Error; use spector_armcommon::models::{ConfidentialResource, ConfidentialResourceProperties}; #[tokio::test] From da3c2ae456e8bab4837a2adaff547ab0572031bd Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Tue, 17 Feb 2026 08:00:13 -0800 Subject: [PATCH 10/16] Remove unintended change --- packages/typespec-rust/src/tcgcadapter/adapter.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/typespec-rust/src/tcgcadapter/adapter.ts b/packages/typespec-rust/src/tcgcadapter/adapter.ts index 85e6469f7..2618e9585 100644 --- a/packages/typespec-rust/src/tcgcadapter/adapter.ts +++ b/packages/typespec-rust/src/tcgcadapter/adapter.ts @@ -173,12 +173,7 @@ export class Adapter { const processedTypes = new Set(); for (const model of this.ctx.sdkPackage.models) { - if ( - (model.usage & tcgc.UsageFlags.Input) === 0 - && (model.usage & tcgc.UsageFlags.Output) === 0 - && (model.usage & tcgc.UsageFlags.Spread) === 0 - && (model.usage & tcgc.UsageFlags.Exception) === 0 - ) { + if ((model.usage & tcgc.UsageFlags.Input) === 0 && (model.usage & tcgc.UsageFlags.Output) === 0 && (model.usage & tcgc.UsageFlags.Spread) === 0) { // skip types without input and output usage. this will include core // types unless they're explicitly referenced (e.g. a model property). // we keep the models for spread params as we internally use them. From a805effc969319036a201ba5ae606f3b59f5fad7 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk <41349689+antkmsft@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:14:58 -0800 Subject: [PATCH 11/16] Do not report the user-defined error test, until we finalize on how to better implement it --- .../tests/common_properties_error_client_test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index 2f731a16b..228029cbb 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -21,6 +21,7 @@ async fn get_for_predefined_error() { } #[tokio::test] +#[ignore] // Follow up on custom error design async fn create_for_user_defined_error() { let resource = ConfidentialResource { location: Some("eastus".to_string()), From 6fad6155d1de454b15d37ccfafd7255bca646dbc Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Wed, 25 Feb 2026 11:40:10 -0800 Subject: [PATCH 12/16] TryFrom --- packages/typespec-rust/.scripts/tspcompile.js | 6 + packages/typespec-rust/src/codegen/context.ts | 27 ++++ packages/typespec-rust/src/codegen/models.ts | 7 + packages/typespec-rust/src/codemodel/types.ts | 3 + packages/typespec-rust/src/lib.ts | 8 ++ .../typespec-rust/src/tcgcadapter/adapter.ts | 9 +- packages/typespec-rust/test/Cargo.lock | 1 + .../common-properties/Cargo.toml | 1 + .../src/generated/models/models.rs | 108 +++++++++++++- .../src/generated/models/models_impl.rs | 133 +++++++++++++++++- .../common_properties_error_client_test.rs | 20 ++- .../common-properties/tspconfig.yaml | 3 + 12 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tspconfig.yaml diff --git a/packages/typespec-rust/.scripts/tspcompile.js b/packages/typespec-rust/.scripts/tspcompile.js index a075659a4..85467c9cb 100644 --- a/packages/typespec-rust/.scripts/tspcompile.js +++ b/packages/typespec-rust/.scripts/tspcompile.js @@ -218,6 +218,12 @@ function generate(crate, input, outputDir, additionalArgs) { additionalArgs[i] = `--option="@azure-tools/typespec-rust.${additionalArgs[i]}"`; } } + + const tspConfigPath = `${outputDir}/tspconfig.yaml`; + if (fs.existsSync(tspConfigPath)) { + additionalArgs.push(`--config=${tspConfigPath}`); + } + sem.take(async function() { // if a tsp file isn't specified, first check // for a client.tsp file. if that doesn't exist diff --git a/packages/typespec-rust/src/codegen/context.ts b/packages/typespec-rust/src/codegen/context.ts index b76fdd0de..a47701590 100644 --- a/packages/typespec-rust/src/codegen/context.ts +++ b/packages/typespec-rust/src/codegen/context.ts @@ -131,6 +131,33 @@ export class Context { return content; } + /** + * returns the impl azure_core::Error for the error type. + * if no impl is required, the empty string is returned. + * + * @param model the model for which to implement TryFrom + * @param use the use statement builder currently in scope + * @returns the impl TryFrom block for type or the empty string + */ + getTryFromForError(model: rust.Model, use: Use): string { + if ((model.flags & rust.ModelFlags.Error) !== rust.ModelFlags.Error) { + return ''; + } + + use.add('azure_core::error', 'ErrorKind'); + const indent = new helpers.indentation(); + let content = `impl TryFrom for ${helpers.getTypeDeclaration(model)} {\n`; + content += `${indent.get()}type Error = azure_core::Error;\n`; + content += `${indent.get()}fn try_from(error: azure_core::Error) -> std::result::Result {\n`; + content += `${indent.push().get()}match error.kind() {` + content += `${indent.push().get()}ErrorKind::HttpResponse { raw_response: Some(raw_response), .. } => Ok(serde_json::from_str(raw_response.body().clone().into_string()?.as_ref())?),`; + content += `${indent.get()}_ => Err(azure_core::Error::with_message(azure_core::error::ErrorKind::DataConversion, "ErrorKind was not HttpResponse and could not be parsed."))`; + content += `${indent.pop().get()}}\n`; + content += `${indent.pop().get()}}\n`; + content += '}\n\n'; + return content; + } + /** * returns the body format for the provided model * diff --git a/packages/typespec-rust/src/codegen/models.ts b/packages/typespec-rust/src/codegen/models.ts index b3e5a6df3..991a9c0dc 100644 --- a/packages/typespec-rust/src/codegen/models.ts +++ b/packages/typespec-rust/src/codegen/models.ts @@ -232,6 +232,13 @@ function emitModelImpls(crate: rust.Crate, context: Context): helpers.Module | u entries.push(forReq); } + const forErr = context.getTryFromForError(model, use); + if (forErr) { + use.addForType(model); + entries.push(forErr); + } + + const pageImpl = context.getPageImplForType(model, use); if (pageImpl) { use.addForType(model); diff --git a/packages/typespec-rust/src/codemodel/types.ts b/packages/typespec-rust/src/codemodel/types.ts index c97b66aa6..17d15e7ee 100644 --- a/packages/typespec-rust/src/codemodel/types.ts +++ b/packages/typespec-rust/src/codemodel/types.ts @@ -325,6 +325,9 @@ export enum ModelFlags { /** model is used as output from a method */ Output = 2, + + /** model is as error */ + Error = 4, } /** DateTimeEncoding is the wire format of the date/time */ diff --git a/packages/typespec-rust/src/lib.ts b/packages/typespec-rust/src/lib.ts index 097c57a18..969e157b8 100644 --- a/packages/typespec-rust/src/lib.ts +++ b/packages/typespec-rust/src/lib.ts @@ -19,6 +19,8 @@ export interface RustEmitterOptions { 'overwrite-lib-rs': boolean; /** Whether to omit documentation links in generated code. Defaults to false */ 'temp-omit-doc-links': boolean; + /** Whether to emit error types */ + 'emit-error-types': boolean; } const EmitterOptionsSchema: JSONSchemaType = { @@ -59,6 +61,12 @@ const EmitterOptionsSchema: JSONSchemaType = { default: false, description: 'Whether to omit documentation links in generated code. Defaults to false' }, + 'emit-error-types': { + type: 'boolean', + nullable: false, + default: false, + description: 'Whether to emit error types. Defaults to false' + }, }, required: [ 'crate-name', diff --git a/packages/typespec-rust/src/tcgcadapter/adapter.ts b/packages/typespec-rust/src/tcgcadapter/adapter.ts index 2618e9585..5b2df8e56 100644 --- a/packages/typespec-rust/src/tcgcadapter/adapter.ts +++ b/packages/typespec-rust/src/tcgcadapter/adapter.ts @@ -173,7 +173,11 @@ export class Adapter { const processedTypes = new Set(); for (const model of this.ctx.sdkPackage.models) { - if ((model.usage & tcgc.UsageFlags.Input) === 0 && (model.usage & tcgc.UsageFlags.Output) === 0 && (model.usage & tcgc.UsageFlags.Spread) === 0) { + let usageFlags = tcgc.UsageFlags.Input | tcgc.UsageFlags.Output | tcgc.UsageFlags.Spread; + if (this.options['emit-error-types']) { + usageFlags |= tcgc.UsageFlags.Exception; + } + if ((model.usage & usageFlags) === 0) { // skip types without input and output usage. this will include core // types unless they're explicitly referenced (e.g. a model property). // we keep the models for spread params as we internally use them. @@ -358,6 +362,9 @@ export class Adapter { if ((model.usage & tcgc.UsageFlags.Output) === tcgc.UsageFlags.Output) { modelFlags |= rust.ModelFlags.Output; } + if (this.options['emit-error-types'] && (model.usage & tcgc.UsageFlags.Exception) === tcgc.UsageFlags.Exception) { + modelFlags |= rust.ModelFlags.Error; + } rustModel = new rust.Model(modelName, model.access === 'internal' ? 'pubCrate' : 'pub', modelFlags); rustModel.docs = this.adaptDocs(model.summary, model.doc); diff --git a/packages/typespec-rust/test/Cargo.lock b/packages/typespec-rust/test/Cargo.lock index cd2fe6186..4a7ca4fc4 100644 --- a/packages/typespec-rust/test/Cargo.lock +++ b/packages/typespec-rust/test/Cargo.lock @@ -1511,6 +1511,7 @@ dependencies = [ "async-trait", "azure_core", "serde", + "serde_json", "tokio", ] diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/Cargo.toml b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/Cargo.toml index 32d3ba2c2..9967a1d72 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/Cargo.toml +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/Cargo.toml @@ -13,6 +13,7 @@ default = ["azure_core/default"] [dependencies] azure_core = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } [dev-dependencies] async-trait = { workspace = true } diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs index e3d5505da..568ca339d 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs @@ -4,10 +4,58 @@ // Code generated by Microsoft (R) Rust Code Generator. DO NOT EDIT. use super::{CreatedByType, ManagedServiceIdentityType}; -use azure_core::{fmt::SafeDebug, time::OffsetDateTime}; +use azure_core::{fmt::SafeDebug, time::OffsetDateTime, Value}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +/// Api error. +#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +pub struct ApiError { + /// The error code. + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + + /// The Api error details + #[serde(skip_serializing_if = "Option::is_none")] + pub details: Option>, + + /// The Api inner error + #[serde(skip_serializing_if = "Option::is_none")] + pub innererror: Option, + + /// The error message. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + + /// The target of the particular error. + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option, +} + +/// Api error base. +#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +pub struct ApiErrorBase { + /// The error code. + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + + /// The error message. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + + /// The target of the particular error. + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option, +} + +/// An error response. +#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +pub struct CloudError { + /// Api error. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + /// Concrete tracked resource types can be created by aliasing this type using a specific property type. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] pub struct ConfidentialResource { @@ -51,6 +99,64 @@ pub struct ConfidentialResourceProperties { pub username: Option, } +/// The resource management error additional info. +#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +pub struct ErrorAdditionalInfo { + /// The additional info. + #[serde(skip_serializing_if = "Option::is_none")] + pub info: Option, + + /// The additional info type. + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub type_prop: Option, +} + +/// The error detail. +#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +pub struct ErrorDetail { + /// The error additional info. + #[serde(rename = "additionalInfo", skip_serializing_if = "Option::is_none")] + pub additional_info: Option>, + + /// The error code. + #[serde(skip_serializing_if = "Option::is_none")] + pub code: Option, + + /// The error details. + #[serde(skip_serializing_if = "Option::is_none")] + pub details: Option>, + + /// The error message. + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, + + /// The error target. + #[serde(skip_serializing_if = "Option::is_none")] + pub target: Option, +} + +/// Error response +/// +/// Common error response for all Azure Resource Manager APIs to return error details for failed operations. +#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +pub struct ErrorResponse { + /// The error object. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Inner error details. +#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +pub struct InnerError { + /// The internal error message or exception dump. + #[serde(skip_serializing_if = "Option::is_none")] + pub errordetail: Option, + + /// The exception type. + #[serde(skip_serializing_if = "Option::is_none")] + pub exceptiontype: Option, +} + /// Concrete tracked resource types can be created by aliasing this type using a specific property type. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] pub struct ManagedIdentityTrackedResource { diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models_impl.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models_impl.rs index a81ef33a5..b1059ada1 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models_impl.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models_impl.rs @@ -3,8 +3,11 @@ // Licensed under the MIT License. See License.txt in the project root for license information. // Code generated by Microsoft (R) Rust Code Generator. DO NOT EDIT. -use super::{ConfidentialResource, ManagedIdentityTrackedResource}; -use azure_core::{http::RequestContent, json::to_json, Result}; +use super::{ + ApiError, ApiErrorBase, CloudError, ConfidentialResource, ErrorAdditionalInfo, ErrorDetail, + ErrorResponse, InnerError, ManagedIdentityTrackedResource, +}; +use azure_core::{error::ErrorKind, http::RequestContent, json::to_json, Result}; impl TryFrom for RequestContent { type Error = azure_core::Error; @@ -19,3 +22,129 @@ impl TryFrom for RequestContent for ApiError { + type Error = azure_core::Error; + fn try_from(error: azure_core::Error) -> std::result::Result { + match error.kind() { + ErrorKind::HttpResponse { + raw_response: Some(raw_response), + .. + } => Ok(serde_json::from_str( + raw_response.body().clone().into_string()?.as_ref(), + )?), + _ => Err(azure_core::Error::with_message( + azure_core::error::ErrorKind::DataConversion, + "ErrorKind was not HttpResponse and could not be parsed.", + )), + } + } +} + +impl TryFrom for ApiErrorBase { + type Error = azure_core::Error; + fn try_from(error: azure_core::Error) -> std::result::Result { + match error.kind() { + ErrorKind::HttpResponse { + raw_response: Some(raw_response), + .. + } => Ok(serde_json::from_str( + raw_response.body().clone().into_string()?.as_ref(), + )?), + _ => Err(azure_core::Error::with_message( + azure_core::error::ErrorKind::DataConversion, + "ErrorKind was not HttpResponse and could not be parsed.", + )), + } + } +} + +impl TryFrom for CloudError { + type Error = azure_core::Error; + fn try_from(error: azure_core::Error) -> std::result::Result { + match error.kind() { + ErrorKind::HttpResponse { + raw_response: Some(raw_response), + .. + } => Ok(serde_json::from_str( + raw_response.body().clone().into_string()?.as_ref(), + )?), + _ => Err(azure_core::Error::with_message( + azure_core::error::ErrorKind::DataConversion, + "ErrorKind was not HttpResponse and could not be parsed.", + )), + } + } +} + +impl TryFrom for ErrorAdditionalInfo { + type Error = azure_core::Error; + fn try_from(error: azure_core::Error) -> std::result::Result { + match error.kind() { + ErrorKind::HttpResponse { + raw_response: Some(raw_response), + .. + } => Ok(serde_json::from_str( + raw_response.body().clone().into_string()?.as_ref(), + )?), + _ => Err(azure_core::Error::with_message( + azure_core::error::ErrorKind::DataConversion, + "ErrorKind was not HttpResponse and could not be parsed.", + )), + } + } +} + +impl TryFrom for ErrorDetail { + type Error = azure_core::Error; + fn try_from(error: azure_core::Error) -> std::result::Result { + match error.kind() { + ErrorKind::HttpResponse { + raw_response: Some(raw_response), + .. + } => Ok(serde_json::from_str( + raw_response.body().clone().into_string()?.as_ref(), + )?), + _ => Err(azure_core::Error::with_message( + azure_core::error::ErrorKind::DataConversion, + "ErrorKind was not HttpResponse and could not be parsed.", + )), + } + } +} + +impl TryFrom for ErrorResponse { + type Error = azure_core::Error; + fn try_from(error: azure_core::Error) -> std::result::Result { + match error.kind() { + ErrorKind::HttpResponse { + raw_response: Some(raw_response), + .. + } => Ok(serde_json::from_str( + raw_response.body().clone().into_string()?.as_ref(), + )?), + _ => Err(azure_core::Error::with_message( + azure_core::error::ErrorKind::DataConversion, + "ErrorKind was not HttpResponse and could not be parsed.", + )), + } + } +} + +impl TryFrom for InnerError { + type Error = azure_core::Error; + fn try_from(error: azure_core::Error) -> std::result::Result { + match error.kind() { + ErrorKind::HttpResponse { + raw_response: Some(raw_response), + .. + } => Ok(serde_json::from_str( + raw_response.body().clone().into_string()?.as_ref(), + )?), + _ => Err(azure_core::Error::with_message( + azure_core::error::ErrorKind::DataConversion, + "ErrorKind was not HttpResponse and could not be parsed.", + )), + } + } +} diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs index 228029cbb..f8d0c8ef8 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tests/common_properties_error_client_test.rs @@ -5,7 +5,7 @@ mod common; use azure_core::http::StatusCode; -use spector_armcommon::models::{ConfidentialResource, ConfidentialResourceProperties}; +use spector_armcommon::models::{CloudError, ConfidentialResource, ConfidentialResourceProperties}; #[tokio::test] async fn get_for_predefined_error() { @@ -21,7 +21,6 @@ async fn get_for_predefined_error() { } #[tokio::test] -#[ignore] // Follow up on custom error design async fn create_for_user_defined_error() { let resource = ConfidentialResource { location: Some("eastus".to_string()), @@ -41,4 +40,21 @@ async fn create_for_user_defined_error() { let err = rsp.unwrap_err(); assert_eq!(err.http_status(), Some(StatusCode::BadRequest)); assert_eq!(err.to_string(), "Username should not contain only numbers."); + + let cloud_err: CloudError = err.try_into().unwrap(); + + let cloud_error = cloud_err.error.as_ref(); + assert!(cloud_error.is_some()); + assert_eq!(cloud_error.unwrap().code, Some("BadRequest".to_string())); + assert_eq!( + cloud_error.unwrap().message, + Some("Username should not contain only numbers.".to_string()) + ); + + let cloud_inner = cloud_error.unwrap().innererror.as_ref(); + assert!(cloud_inner.is_some()); + assert_eq!( + cloud_inner.unwrap().exceptiontype, + Some("general".to_string()) + ); } diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tspconfig.yaml b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tspconfig.yaml new file mode 100644 index 000000000..c6df08f53 --- /dev/null +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/tspconfig.yaml @@ -0,0 +1,3 @@ +options: + "@azure-tools/typespec-rust": + emit-error-types: true From 1bece99c27868eb01bc17733405b006d6ee26fd0 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk <41349689+antkmsft@users.noreply.github.com> Date: Wed, 25 Feb 2026 11:51:09 -0800 Subject: [PATCH 13/16] Fix typo in Error enum comment --- packages/typespec-rust/src/codemodel/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-rust/src/codemodel/types.ts b/packages/typespec-rust/src/codemodel/types.ts index 4c931beb5..63137de55 100644 --- a/packages/typespec-rust/src/codemodel/types.ts +++ b/packages/typespec-rust/src/codemodel/types.ts @@ -341,7 +341,7 @@ export enum ModelFlags { /** model is a sub-type in a polymorphic discriminated union */ PolymorphicSubtype = 4, - /** model is as error */ + /** model is an error */ Error = 8, } From 78633826a0c1c069721af772e3d0a57d9ad40403 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Wed, 25 Feb 2026 12:00:50 -0800 Subject: [PATCH 14/16] Commit generated file after merge with latest main --- .../common-properties/src/generated/models/models.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs index 50b07d459..a4aaded43 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs @@ -10,6 +10,7 @@ use std::collections::HashMap; /// Api error. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +#[non_exhaustive] pub struct ApiError { /// The error code. #[serde(skip_serializing_if = "Option::is_none")] @@ -34,6 +35,7 @@ pub struct ApiError { /// Api error base. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +#[non_exhaustive] pub struct ApiErrorBase { /// The error code. #[serde(skip_serializing_if = "Option::is_none")] @@ -50,6 +52,7 @@ pub struct ApiErrorBase { /// An error response. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +#[non_exhaustive] pub struct CloudError { /// Api error. #[serde(skip_serializing_if = "Option::is_none")] @@ -101,6 +104,7 @@ pub struct ConfidentialResourceProperties { /// The resource management error additional info. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +#[non_exhaustive] pub struct ErrorAdditionalInfo { /// The additional info. #[serde(skip_serializing_if = "Option::is_none")] @@ -113,6 +117,7 @@ pub struct ErrorAdditionalInfo { /// The error detail. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +#[non_exhaustive] pub struct ErrorDetail { /// The error additional info. #[serde(rename = "additionalInfo", skip_serializing_if = "Option::is_none")] @@ -139,6 +144,7 @@ pub struct ErrorDetail { /// /// Common error response for all Azure Resource Manager APIs to return error details for failed operations. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +#[non_exhaustive] pub struct ErrorResponse { /// The error object. #[serde(skip_serializing_if = "Option::is_none")] @@ -147,6 +153,7 @@ pub struct ErrorResponse { /// Inner error details. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] +#[non_exhaustive] pub struct InnerError { /// The internal error message or exception dump. #[serde(skip_serializing_if = "Option::is_none")] From 5195c9bfeda9a6e7eb552ec0227a863717fd3eda Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Wed, 25 Feb 2026 13:01:23 -0800 Subject: [PATCH 15/16] Fix typescript lint --- packages/typespec-rust/src/codegen/context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-rust/src/codegen/context.ts b/packages/typespec-rust/src/codegen/context.ts index 2d084fcba..0f49b2219 100644 --- a/packages/typespec-rust/src/codegen/context.ts +++ b/packages/typespec-rust/src/codegen/context.ts @@ -140,7 +140,7 @@ export class Context { * @returns the impl TryFrom block for type or the empty string */ getTryFromForError(model: rust.Model, use: Use): string { - if ((model.flags & rust.ModelFlags.Error) !== rust.ModelFlags.Error) { + if ((model.flags & rust.ModelFlags.Error) === 0) { return ''; } From 9ae8aa3be14728b800fb2bbcceb3ded054d5bae3 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Wed, 25 Feb 2026 16:32:24 -0800 Subject: [PATCH 16/16] Only generate TryFrom for the error that actually gets returned, not all sub-models recursively --- .../typespec-rust/src/tcgcadapter/adapter.ts | 31 +++++- .../src/generated/models/models.rs | 17 +--- .../src/generated/models/models_impl.rs | 95 +------------------ 3 files changed, 33 insertions(+), 110 deletions(-) diff --git a/packages/typespec-rust/src/tcgcadapter/adapter.ts b/packages/typespec-rust/src/tcgcadapter/adapter.ts index 27133f93f..2352b8e13 100644 --- a/packages/typespec-rust/src/tcgcadapter/adapter.ts +++ b/packages/typespec-rust/src/tcgcadapter/adapter.ts @@ -180,6 +180,30 @@ export class Adapter { const processedTypes = new Set(); + const getErrorModelNames = function(clients: tcgc.SdkClientType[], visitedClientNames = new Set()) : Set { + const errorModelNames = new Set(); + for (const client of clients) { + if (visitedClientNames.has(client.name)) { + continue; + } + visitedClientNames.add(client.name); + for (const errorModelName + of client.methods.flatMap(mt => mt.operation.exceptions).filter( + e => e.type?.kind === 'model').map(md => (md.type as tcgc.SdkModelType).name) + ) { + errorModelNames.add(errorModelName); + } + + for (const errorModelName of getErrorModelNames(client.children ?? [], visitedClientNames).values()) { + errorModelNames.add(errorModelName); + } + } + + return errorModelNames; + } + + const errorModelNames = getErrorModelNames(this.ctx.sdkPackage.clients); + for (const model of this.ctx.sdkPackage.models) { let usageFlags = tcgc.UsageFlags.Input | tcgc.UsageFlags.Output | tcgc.UsageFlags.Spread; if (this.options['emit-error-types']) { @@ -214,6 +238,9 @@ export class Adapter { this.getExternalType(model.external); } else { const rustModel = this.getModel(model); + if ((model.usage & tcgc.UsageFlags.Exception) !== 0 && errorModelNames.has(model.name)) { + rustModel.flags |= rust.ModelFlags.Error; + } this.crate.models.push(rustModel); } } @@ -376,14 +403,10 @@ export class Adapter { // include error and LRO polling types as output types if ( (model.usage & tcgc.UsageFlags.Output) === tcgc.UsageFlags.Output || - (model.usage & tcgc.UsageFlags.Exception) === tcgc.UsageFlags.Exception || (model.usage & tcgc.UsageFlags.LroPolling) === tcgc.UsageFlags.LroPolling ) { modelFlags |= rust.ModelFlags.Output; } - if (this.options['emit-error-types'] && (model.usage & tcgc.UsageFlags.Exception) === tcgc.UsageFlags.Exception) { - modelFlags |= rust.ModelFlags.Error; - } rustModel = new rust.Model(modelName, model.access === 'internal' ? 'pubCrate' : 'pub', modelFlags); rustModel.docs = this.adaptDocs(model.summary, model.doc); diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs index a4aaded43..69b76a73b 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models.rs @@ -9,8 +9,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Api error. -#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] -#[non_exhaustive] +#[derive(Clone, Deserialize, SafeDebug, Serialize)] pub struct ApiError { /// The error code. #[serde(skip_serializing_if = "Option::is_none")] @@ -34,8 +33,7 @@ pub struct ApiError { } /// Api error base. -#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] -#[non_exhaustive] +#[derive(Clone, Deserialize, SafeDebug, Serialize)] pub struct ApiErrorBase { /// The error code. #[serde(skip_serializing_if = "Option::is_none")] @@ -52,7 +50,6 @@ pub struct ApiErrorBase { /// An error response. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] -#[non_exhaustive] pub struct CloudError { /// Api error. #[serde(skip_serializing_if = "Option::is_none")] @@ -103,8 +100,7 @@ pub struct ConfidentialResourceProperties { } /// The resource management error additional info. -#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] -#[non_exhaustive] +#[derive(Clone, Deserialize, SafeDebug, Serialize)] pub struct ErrorAdditionalInfo { /// The additional info. #[serde(skip_serializing_if = "Option::is_none")] @@ -116,8 +112,7 @@ pub struct ErrorAdditionalInfo { } /// The error detail. -#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] -#[non_exhaustive] +#[derive(Clone, Deserialize, SafeDebug, Serialize)] pub struct ErrorDetail { /// The error additional info. #[serde(rename = "additionalInfo", skip_serializing_if = "Option::is_none")] @@ -144,7 +139,6 @@ pub struct ErrorDetail { /// /// Common error response for all Azure Resource Manager APIs to return error details for failed operations. #[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] -#[non_exhaustive] pub struct ErrorResponse { /// The error object. #[serde(skip_serializing_if = "Option::is_none")] @@ -152,8 +146,7 @@ pub struct ErrorResponse { } /// Inner error details. -#[derive(Clone, Default, Deserialize, SafeDebug, Serialize)] -#[non_exhaustive] +#[derive(Clone, Deserialize, SafeDebug, Serialize)] pub struct InnerError { /// The internal error message or exception dump. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models_impl.rs b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models_impl.rs index 9e176a0d6..920ca03f1 100644 --- a/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models_impl.rs +++ b/packages/typespec-rust/test/spector/azure/resource-manager/common-properties/src/generated/models/models_impl.rs @@ -3,10 +3,7 @@ // // Code generated by Microsoft (R) Rust Code Generator. DO NOT EDIT. -use super::{ - ApiError, ApiErrorBase, CloudError, ConfidentialResource, ErrorAdditionalInfo, ErrorDetail, - ErrorResponse, InnerError, ManagedIdentityTrackedResource, -}; +use super::{CloudError, ConfidentialResource, ErrorResponse, ManagedIdentityTrackedResource}; use azure_core::{error::ErrorKind, http::RequestContent, json::to_json, Result}; impl TryFrom for RequestContent { @@ -23,42 +20,6 @@ impl TryFrom for RequestContent for ApiError { - type Error = azure_core::Error; - fn try_from(error: azure_core::Error) -> std::result::Result { - match error.kind() { - ErrorKind::HttpResponse { - raw_response: Some(raw_response), - .. - } => Ok(serde_json::from_str( - raw_response.body().clone().into_string()?.as_ref(), - )?), - _ => Err(azure_core::Error::with_message( - azure_core::error::ErrorKind::DataConversion, - "ErrorKind was not HttpResponse and could not be parsed.", - )), - } - } -} - -impl TryFrom for ApiErrorBase { - type Error = azure_core::Error; - fn try_from(error: azure_core::Error) -> std::result::Result { - match error.kind() { - ErrorKind::HttpResponse { - raw_response: Some(raw_response), - .. - } => Ok(serde_json::from_str( - raw_response.body().clone().into_string()?.as_ref(), - )?), - _ => Err(azure_core::Error::with_message( - azure_core::error::ErrorKind::DataConversion, - "ErrorKind was not HttpResponse and could not be parsed.", - )), - } - } -} - impl TryFrom for CloudError { type Error = azure_core::Error; fn try_from(error: azure_core::Error) -> std::result::Result { @@ -77,42 +38,6 @@ impl TryFrom for CloudError { } } -impl TryFrom for ErrorAdditionalInfo { - type Error = azure_core::Error; - fn try_from(error: azure_core::Error) -> std::result::Result { - match error.kind() { - ErrorKind::HttpResponse { - raw_response: Some(raw_response), - .. - } => Ok(serde_json::from_str( - raw_response.body().clone().into_string()?.as_ref(), - )?), - _ => Err(azure_core::Error::with_message( - azure_core::error::ErrorKind::DataConversion, - "ErrorKind was not HttpResponse and could not be parsed.", - )), - } - } -} - -impl TryFrom for ErrorDetail { - type Error = azure_core::Error; - fn try_from(error: azure_core::Error) -> std::result::Result { - match error.kind() { - ErrorKind::HttpResponse { - raw_response: Some(raw_response), - .. - } => Ok(serde_json::from_str( - raw_response.body().clone().into_string()?.as_ref(), - )?), - _ => Err(azure_core::Error::with_message( - azure_core::error::ErrorKind::DataConversion, - "ErrorKind was not HttpResponse and could not be parsed.", - )), - } - } -} - impl TryFrom for ErrorResponse { type Error = azure_core::Error; fn try_from(error: azure_core::Error) -> std::result::Result { @@ -130,21 +55,3 @@ impl TryFrom for ErrorResponse { } } } - -impl TryFrom for InnerError { - type Error = azure_core::Error; - fn try_from(error: azure_core::Error) -> std::result::Result { - match error.kind() { - ErrorKind::HttpResponse { - raw_response: Some(raw_response), - .. - } => Ok(serde_json::from_str( - raw_response.body().clone().into_string()?.as_ref(), - )?), - _ => Err(azure_core::Error::with_message( - azure_core::error::ErrorKind::DataConversion, - "ErrorKind was not HttpResponse and could not be parsed.", - )), - } - } -}